サカナ未遂

プログラミング、筋トレ、子育て

現場で使えるRuby on Rails 5 速習実践ガイドを読んだ

積読していた現場Railsを読みました。

全部読むの結構時間かかったけど、知らなかったこと、忘れていたこと結構あったのでそれらをまとめまた。

Chapter1

1-7 読めると便利! Rubyっぽい書き方

この辺りのイディオム、ちょくちょく見るし便利だけどいつも忘れる。

nilガード

number ||= 10

# number || (number = 10) と同じ動き

numberに値が入っていればそのまま、nilなら10を代入する。

ぼっち演算子

&.をつけてメソッドを呼び出すとレシーバがnilでもエラーが発生しない。

obj = nil

# objはnilなのでeachを使おうとするとエラーになる。
obj.each{|x| puts x }
NoMethodError: undefined method `each' for nil:NilClass

# ぼっち演算子をつけるとエラーにならない
obj&.each{|x| puts x }
=> nil

%記法

# %wは文字列の配列を生成する
%w(foo bar piyo)
=> ["foo", "bar", "piyo"]

# %iはシンボル の配列を生成する
%i(foo bar piyo)
=> [:foo, :bar, :piyo]

Chapter3

slimを使う。

下記を追加することで、slimが使える

gem 'slim-rails'
gem 'html2slim'

コマンドを実行することで既存のファイルをslimに変換してくれる

 bundle exec erb2slim app/views/layouts/ --delete

現職ではvueを使ってるので、slim使ったことなかった。pugみたいなもんか。

i18nでエラーメッセージを日本語にする

下記コマンドでja.ymlを取得してconfig/localesにおく。

wget https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml --output-document=config/locales/ja.yml

config/initialilzers/locale.rbに以下を記載する。

Rails.application.config.i18n.default_locale = :ja

リクエストパラメータ

GETでもPOSTでもパラメータの受け取り方をプログラマが区別しなくていいようにparamsが使える。 なんとなく使ってたけど、確かにGETでもPOSTでも同じように使えるのはRailsがやってくれてたのか。

レンダーとリダイレクト

レンダーはアクションに続けてビューを表示すること。リダイレクトはアクションを処理した直後にビューを表示せず別のURLに案内すること。

simple_format

以下のコード、hメソッドでXSS対応をした上で改行を
に変換し、さらに

タグで囲んでくれる。

simple_format(h(@task.description), {}, sanitize: false, wrapper_tag: "div")

Chapter4

バージョンを1つ戻してから1つ上げる。 バージョンが戻ることの確認とかに使う。

bin/rails db:migrate:redo

マイグレーションファイルのupとdown

マイグレーション失敗時に自動で戻せない場合に必要。 add_columnとかの場合の失敗は自動で戻してくれるから不要だけど、カラムにlimit: 30とか追加する時にはdownも書いておくと戻せる。downがないと例外が発生する。

validateのスキップ

falseを設定することでvalidateをスキップできる。

task.save(validate: false)

persisted?メソッド

オブジェクトがDBに登録されているかどうか確認ができる。

redirect_toのパラメータにmodelのオブジェクト

redirect_toのパラメータにmodelオブジェクトすると、showのページに遷移する。

validatesとvalidateの違い

標準の検証はvalidatesで自前の検証処理はvalidateになる。

パスワードのハッシュ化

has_secure_passwordを使うことでパスワードをハッシュ化できる。 使うにはbcrypt(gem)が必要。 has_secure_passwordをモデルに記述すると、passwordとpassword_confimartion属性が追加される。 2つの属性が一致した時だけパスワードを登録することができる。 テーブルにはpassword_digestの名前でカラムを用意しておく必要がある。これはhas_secure_passwordを使用するときの命名ルールである。

認証のためのauthenticate

authenticatehas_secure_passwordを記述した時に自動で追加される認証のためのメソッド。引数で受け取ったパスワードをハッシュ化して、オブジェクトに保存されてるdigestと一致するか調べる。

helper_method

コントローラにhelper_methodでメソッドを定義すると、viewからでも呼び出すことができる。

skip_before_action

before_actionをスキップすることができる。

Chapter5

Rspecについて

Rspecは動く仕様書として自動テストを書くという発想で作られてる。Rspecは「テスト」ではなく「Spec」を書くという気持ちが大事とある。 「動く仕様書」か、なるほどなるほど。 とりあえず書くもんだと今まで書いてきたけど、こういうフレームワークの思想みたいなものを知ることで、納得してテストを書いていける。

SystemSpecについて

FeatureSpecの代わりっぽい?、結局SPAでどうやるのか?と調べたらいけそう。 ここを見てみた。 tech.unifa-e.com

弊社ではE2EはRequestSpecしか書いてなかったけど、SystemSpecも導入した方が良いのかな。

visit

特定のURLでアクセスする。ログイン画面にアクセスする場合とかは以下のように記述する。

visit login_path

fill_in

パラメータで指定したラベルがついたテキストフィールドに値を入れる。 以下のように記述する

fill_in 'メールアドレス', with: 'a@example.com'

shared_examples_for

itなどを共通化することができる。

shared_examples_for 'タスクが画面に表示されること' do
  it { expect(page).to have_content 'タスク' }
end

コンソールのサンドボックス

Railsコンソールにサンドボックスあるの知らなかった〜。-s付ければいいのか

bin/rails c -s

Chapter6

CSRFはシーサーフと読まれることがある。

SQLインジェクションに気を付けるパターン

whereメソッドなどでSQL文字列を直接渡して条件指定するときはSQLインジェクションに気を付ける。 こんな場合

User.where("name = '#{params[:user_name]}'")

こっちにした方がいい。

User.where('name = ?', params[:user_name])

プレースホルダを使うと、「'」とかをエスケープしてくれる。

Chapter7

ransackを使うときに気を付けること

modelに以下のコードを入れることで特定のカラムに対してしかクエリーを発行できないようにすることができる。 下のコードは関連を指定できる。[]にすることで意図しない関連を含めないようにする。

def self.ransackable_attributes(auth_object = nil)
  %w[name created_at]
end

def self.ransakable_associations(auth_object = nil)
  []
end

mailcatcher

mailcatcherは送信したメールをブラウザ上で確認することができるgem コンソールから以下のコマンドで使える

mailcatcher

config/environments/development.rbに以下の設定をすると使える

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { address: '127.0.0.1', port: 1025 }

ActiveStorage

クラウドサービスにファイルをアップロードしてActiveRecordモデルに紐付けることが簡単にできる。 config/storage.ymlに保存先を設定する。 コメントアウトされているが、AWSやGCSも簡単に設定できる。

Chapter9

redoを習慣にする。

ロールバックできないトラブルを防ぐためにマイグレーションを書いたとき、ロールバックが成功することを確認することが重要。

マイグレーションファイルからアプリケーションコードの参照は避ける。

マイグレーションコードは積み重なっていくのでいつ実行しても同じように動く必要がある。 アプリケーションコードを使用する場合、その内容が変更(モデルにバリデーションが追加されるなど)したらマイグレーションファイルの挙動も変わってしまうため、避けた方が良い。

スキーマキャッシュ

マイグレーションファイルでモデルインスタンスを作成すると、ActiveRecordスキーマをキャッシュする。同じテーブルに対して連続してマイグレーションを行う場合、キャッシュが効いて意図しない挙動になることがあるので、reset_column_informationスキーマキャッシュを更新するよう習慣づける必要がある。

Chapter10

共通機能のモジュールのMix-in

RailsでMix-inのモジュールを書く時にはActiveSupport::Concernをextendする。 クラスレベルの拡張を素のrubyに比べて少し書きやすくしてくれる。

SIT

ActiveRecordモデルの継承する場合、継承元と継承先で1つのテーブルに対応づけを行うことをSIT(Single Table Inheritance)と呼ぶ。 対応づけらえるテーブルには継承されたテーブルが必要とするカラムを全部盛り込む必要がある。

複数のモデルがカラム処理は特定処理の専門家を作る

コントローラで2つのモデルを扱う処理の場合、片方のモデルにコードを書くと、他のモデルのデータの依存性を高めるのでよくない。 2つのモデルのデータを扱う専門のクラスを作成して処理を書くと良い。

サブリソース単位でコントローラを分割

コントローラは1つのモデルに対して1つでなければならないわけではない。 サブリソースへの操作を対応する複数のコントローラに分けることができる。

所感

まとめて感じたのは特にviewで使う機能に関する知識が結構欠落していること。 SPAがスタンダードになりつつあるので出番が減ってるのは確かだが、Railsを使って仕事をしていくなら最低限は覚えておかないとなぁとしみじみ思う。 あと、Chapter10は現場でかなり参考になる情報だった。定期的に読みなそうと思う。 いつ買ったのかAmazonの購入履歴で調べたら、買ってから2年も経ってた、、、もっと早く読んどけば良かった。 今年は積読消化を頑張らないと

JavaScriptのawait式をAsync Functionの中で使用するときの注意点

JavaScriptのPromiseとかawaitとか雰囲気で乗り切ってきたけど、ちゃんと腰据えて勉強しようと思って購入したJavaScript Primer

非常に分厚いけど、読みやすくサンプルコードも豊富なので理解しやすかった。 まだまだ全部よめてないけど、非同期処理のところを集中的に読む。

第22章で気をつけないとと思った箇所

async function asyncMain(){
  await new Promise((resolve) => {
    setTimeout(resolve, 16); 
  });
};

console.log("1. asyncMain 関数を呼び出します");
asyncMain().then(() => {
  console.log("3. asyncMain関数が終了しました。");
});
console.log("2. asyncMain 関数外では次の行が同期的に呼び出される");

このコードを実行した場合、asyncMainでawaitをつかって処理をとめているけど、関数を呼び出してるメインの処理はとまらず最後のconsole.logを出力する。 あくまで関数の中ではとまっているだけである。 awaitで何でも止めれると思っていたので知ることができてよかった。

CloudRunを使ってみた

GCPのCloudRunというサービスを使うことがあったので簡単な使い方をメモっときます。

CloudRunとは

公式URLより https://cloud.google.com/run?hl=ja

コンテナ化されたアプリケーションをすばやく安全にデプロイ、スケーリングできる、フルマネージド型のコンピューティング プラットフォーム

とあります。 コンテナに入れたwebアプリを、そのままデプロイできるってことですね。 GCPのContainerRegistoryにコンテナをpushし、CloudRunでデプロイするだけで動かせます。

CloudRunの料金

https://cloud.google.com/run/pricing?hl=ja f:id:tera_chan3700:20200618212128p:plain

ちょっと試すくらいならほぼ無料で使えます。

あとContainerRegistoryの料金がかかりますが、これも微々たるものです。 https://cloud.google.com/container-registry/pricing?hl=ja 個人で試すには懐が痛まなくて好きそうです。

注意

CloudRunからCloudRun、または他のサーバレスを無限ループで呼ぶような処理があると危険です。そこは個人で気をつけてください。 例:https://blog.mmmcorp.co.jp/blog/2019/12/25/lambda-cloud-bankruptcy/ これはAWSですが、GCPでも起こりえます。

サンプル

GOで簡単なエコーサーバーを作ります

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
)

func handler(w http.ResponseWriter, r *http.Request) {
    dump, err := httputil.DumpRequest(r, true)
    if err != nil {
        http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
        return
    }
    fmt.Println(string(dump))
    fmt.Fprintf(w, "<html><body>hello Cloud Run!!!</body></html>")
}

func main() {
    var httpServer http.Server
    http.HandleFunc("/", handler)
    log.Println("start http listening :8080")
    httpServer.Addr = ":8080"

    log.Println(httpServer.ListenAndServe())
}

8080ポートにアクセスすると、「hello Cloud Run!!!」の文字列を返すサーバーです。 実行して確認します。

$ go run main.go
2020/06/18 20:01:04 start http listening :8080

サーバーが起動したのでアクセスしてみる f:id:tera_chan3700:20200618213242p:plain

レスポンスが帰ってきました。

次にこれをビルドして実行するDockerfileを作ります。

FROM golang:latest

#ディレクトリ作成
WORKDIR /go/src/go-image
#ホストOSのmain.goをWORKDIRにコピー
COPY main.go .

#バイナリを生成
RUN go install -v .

#バイナリを実行
CMD ["go-image"]

これをビルドします。

$ docker build -t gcr.io/<自分のプロジェクトID>/cloudrun-test:v1 .

ローカルで実行してみます

$ d run -p 8080:8080  gcr.io/<自分のプロジェクトID>/cloudrun-test:v1

アクセスします。 f:id:tera_chan3700:20200618213636p:plain 動きました。ちゃんとコンテナの中で起動することが確認できました。

コンテナレジストリへのpush

今回はステージングをかります。 gcloudコマンドで事前にログインしています。

GCPにpushする。

docker push gcr.io/<自分のプロジェクトID>/cloudrun-test:v1

入ったことを確認 f:id:tera_chan3700:20200618213808p:plain

CloudRunへのデプロイと実行

サービスを作成をクリック f:id:tera_chan3700:20200618213957p:plain

適当に入力して「次へ」をクリック f:id:tera_chan3700:20200618214119p:plain

さっきpushしたイメージを選択して作成ボタンをクリック f:id:tera_chan3700:20200618214315p:plain

しばらくするとデプロイが完了し、エンドポイントが作成されます。 f:id:tera_chan3700:20200618214521p:plain

ここにアクセスします。 f:id:tera_chan3700:20200618214609p:plain

動きました。 ローカルで作ったコンテナが簡単にデプロイでき、動かすことができます。 タイムアウトなど制限もありますが、ローカルで動いてるコンテナを簡単にデプロイできるのは便利です。

gRPCについて学んだこと

会社の業務でgRPC触ることがあったのですが、そもそもRPCってなんぞやって状態でしたので、勉強してみることにしました。

まずgRPCを勉強しようと思った時に、不明な用語があったのでそれらを整理してから進めることにしました。  

RPCとは

まずRPCは、Remote Procedure Call(遠隔手続き呼び出し)の略で、プログラムから別のアドレス空間にあるサブルーチンや手続きを実行することを可能にする技術のこと。

でgRPCは、Google社内で使われていたRPCフレームワークOSS化され、gRPCと名前がついたもののことだそうです。 ちなみにgRPCのgはGoogleのgではないです。バージョン毎にいろいろg縛りの言葉がついてるようです。 ここに書いてた GRPC Core: g_stands_for

Protocol Buffersとは

gRPCはデータをProtocol Buffersにシリアライズしてやりとりを行う。 Protocol BuffersもGoogleが開発したフォーマットで、XMLとの比較で、3〜10倍小さく、20〜100倍高速であると主張しているとのこと。

ProtocolBuffersは、protoファイルを作成します。 ファイルをコンパイルすると任意の言語用のコードを自動で作成してくれます。 GoやRubyなど様々な言語に対応しています。 現在はproto3です(いつから3なのか、2との違いは何なのかちゃんと調べてない)

試してみる

公式のチュートリアルの通りにやってみる。 まずサンプルをダウンロードし、ディレクトリ移動

$ git clone -b v1.28.1 https://github.com/grpc/grpc
$ cd grpc
$ cd examples/ruby

サーバーを起動する。

$ ruby greeter_server.rb

別のターミナルでクライアントを起動

$ ruby greeter_client.rb
# 結果が帰ってくる
"Greeting: Hello world"

どういう仕組みかコードを確認。

まずgreeter_client.rb

# GreeterServer is simple server that implements the Helloworld Greeter server.
class GreeterServer < Helloworld::Greeter::Service
  # say_hello implements the SayHello rpc method.
  def say_hello(hello_req, _unused_call)
    Helloworld::HelloReply.new(message: "Hello #{hello_req.name}")
  end
end

# main starts an RpcServer that receives requests to GreeterServer at the sample
# server port.
def main
  s = GRPC::RpcServer.new
  s.add_http2_port('0.0.0.0:50051', :this_port_is_insecure)
  s.handle(GreeterServer)
  # Runs the server with SIGHUP, SIGINT and SIGQUIT signal handlers to 
  #   gracefully shutdown.
  # User could also choose to run server via call to run_till_terminated
  s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT'])
end

mainでRPCサーバーを0.0.0.0:50051で起動しハンドラーにGreeterServerを設定している。 GreeterServersay_helloというメソッドを持ち、 Helloworld::HelloReply.new(message: "Hello #{hello_req.name}")(これはhelloworld_services_pb.rbに定義されていた。) を実行する。 Hello + リクエストのnameプロパティの値を返す

クライアント側

def main
  stub = Helloworld::Greeter::Stub.new('localhost:50051', :this_channel_is_insecure)
  user = ARGV.size > 0 ?  ARGV[0] : 'world'
  message = stub.say_hello(Helloworld::HelloRequest.new(name: user)).message
  p "Greeting: #{message}"
end

gRPCのスタブを生成し、say_helloメソッドに"world"を渡す。 サーバから帰ってきたメッセージにGreetingを付加して出力している。

所感

まずはこんな感じってのがわかった。 クライアントからサーバーのメソッドを呼び出すという概要は理解できた。 チュートリアルの最初の最初をやっただけなので、protoファイルの作り方やストリーミング など、もっと学習していこうと思います。

参考にさせていただいた資料

booth.pm

grpc.io

k8sで複数環境を触るときに便利なツール

kubernetesで操作する環境が増えてくると、想定外の環境にデプロイするような事故が増えてくるので、少しでも事故を減らすためのツールを紹介します。

kubectx

コンテキストを簡単に切り替えてくれるツール

インストール

$ brew install kubectx

コンテキスト切り替え

# 特定の環境に切り替え
$ kubectx gke_my-project-development_asia-northeast1-a_sample-cluster

、、、、コンテキストが長い…

別名をつけることができる。

$ kubectx MYDEV=gke_my-project-development_asia-northeast1-a_sample-cluster

これで、別名で切り替えることができる。

# 開発環境に切り替え
$ kubectx MYDEV
# 本番に切り替え(本番の別名をつけておく)
$ kubectx PROD

kube-ps1

実行中のコンテキストをプロンプトに表示させることができる。 https://github.com/jonmosco/kube-ps1

インストール

$ brew install kube-ps1

.bashrcの設定

# .bashrc
PS1='\u:\W\$'   # プロンプト表示を、ユーザ名とカレントディレクトリのみにしている(ここは各自の好みで)
source "/<インストールパス>/kube-ps1/share/kube-ps1.sh"
PS1='$(kube_ps1)'$PS1
kubeoff      # デフォルトでオフにしておく
# 表示
$ kubeon

# 非表示
$ kubeoff

こんな感じで表示される

$ kubeon
# ステージングに切り替え
$ kubectx STAGE
(⎈ |STAGE:default)username:workspace$

# 本番に切り替え
$ kubectx PROD
(⎈ |PROD:default)jusername:workspace$

$ kubeoff
username:workspace$

これで自分が操作中のコンテキストを確認しながら作業できるので、少しは安全になります。

転職して約半年が経ちました

5月も終わりになってきて、転職して約半年が経ちます。

 

当初の目標としては、業務以外でももっとたくさんコードを書いてるつもりだっんですが、思った以上に知らないことが多すぎて、そのキャッチアップに追われ、なんとも自身を無くしています。

 

特にインフラ周りの知識がほとんどなく、Docker、k8sGCP、terraformなどのツールやサービスの情報収集に終われ、自身の英語力のなさから来る公式リファレンスを読む遅さと相まって、思った以上に捗らず仕事に追われています。

 

またプライベートでも、妻がフルタイムで働き始めたことによる家事の増加、娘が小学校に入学したことで、出発の時間がこれまでより遅くなり、その分出社が遅れ、退社時間がずれるなど色々ドタバタしていて、仕事以外でなかなかスキルアップの時間が取れない日々が続きました。

 

その対処として、食器洗浄器と、衣類乾燥機を買うことで家事にかかる時間の削減を行いました。

特に衣類乾燥機は、洗濯の後の、「干す」、「取り込む」の二つのプロセスをなくすことができるので(完全には無理ですが)相当の時間短縮になります。

食器洗浄器は、洗う量に対して洗ってる時間が長いので、手で洗った方がいいかと思ったりしますが、洗い終わった後の疲労感がなくなるので、とりあえず毎回使っています。

そんなこんなで、一日1時間半は自分の時間が作れそうなので、まあ焦らず地道にスキルアップしていこうと思います。

 

RubyKaigi2019に参加してきました。

久々にブログを書きます。

タイトルの通り、RubyKaigi2019に参加してきました。 4/18〜20で福岡で開催で、特に一緒にいく同僚もいないので一人ぼっちで行ってきました。

去年Rubyを始めたので、RubyKaigiがどういうものか全然知りませんでした。 あまり詳しくないのですが、相当レベルが高く、自分が行ってもどうなんだろうと思っていました。 去年の末くらいにチケットが発売された後も、行こうか迷っていたのですが、去年末の品川で開催された「Ruby Business Users Conference 2018 Winter」でRuby開発者のMatz(まつもとゆきひろ)さんとお話する機会があり、Ruby会議は楽しいし、初心者でも大丈夫と言っていたので、参加することに決めました。
実際Ruby歴1年ちょっとでは、セッションの内容は高度で、さらに国外のエンジニアの英語スピーチは理解が追いつきませんでしたが、Rubyコミッターの方々の、もっとRubyをよくしていこうという熱い思いは感じ取ることができました。 ぼっちで参加しましたが、東京で顔見知りの方々にアフターパーティで混ぜてもらったり、なんだかんだで楽しく過ごすことができました。 来年は長野県の松本ということで、また絶対に行きたいと思います。