Ruby Weekly #359: 日本語サマリー
職場の Slack の #ruby 窓で Ruby Weekly メルマガが毎週配信されます。その中から面白そうなものをピックアップして、日本語で簡単なサマリーを書くようにしています。そのサマリーをここでまとまさせていただきます。くだけた日本語で失礼いたします。
Highlights
react_on_rails + ActionCable でリアルタイム地図上現在地配信アプリを作った話。Redux を MobX にすり替えた。Redux よりオブジェクト指向感あり。
owner の位置情報を 2 秒ごとに channel に publish。viewer が位置情報を配列に push、react-map-gl で地図上描画。
k8s で Rails の zero-downtime deploy を実現した話。
DNSimple の APIv2 を Hanami で作り直した話 by Hanami 作成者兼 DNSimple 社員。
メインアプリは Rails だが、API はビュー要らないから、別のフレームワークで作ることにした。
最初は Sinatra でプロトタイプを作ってたが、Hanami でも同じものを 1 時間で作ってみた。パフォーマンスはそんなに変わらなかったから Sinatra 捨てて Hanami で引き続き開発。
使ってる Hanami コンポーネントは 2 つだけ:router と action。Rails の 1 action = 1 controller メソッドのに比べて、Hanami は 1 action = 1 Hanami::Action
クラスで、before_action 系フィルターはモジュール mixin。各 action が Command オブジェクト(Service オブジェクト)を呼ぶ。
結論:APIv2 は 2 年で開発完了。Hanami のおかげで保守しやすくて、パフォーマンスが良い。
News
新規 Rails アプリはデフォで bootsnap が Gemfile に入ってて、ブート時間 50% 短縮。
Major Coding Bootcamp Ditches Rails, Claims Lower Employer Demand
米国のメジャーなブートキャンプ Coding Dojo が Ruby・Rails を教えるのをやめた。雇用先からの需要低下で。代わりに Java・Spring を教えることになった。HackerNews でもざわざわしてる。
Tutorial
Clojure の interpose
を Ruby で実装した話。
[1, 2, 3].interpose('x') # [1, 'x', 2, 'x', 3]
Enumerable
をモンキーパッチすることで実装した。
時間がかかる Rake タスクは出力しましょう、と筆者が思うようになったきっかけ。
最近こういう Rake タスク作ることになった:
- S3 上の全ファイル一覧取得
- 最終更新日時でソート
- ソート順に各ファイルに対してバックグラウンドジョブをキューに積む
10 分はかかるが、パフォーマンス的には問題はなかった。実行する人にとっては辛い。突然死したかどうかすら知りようがない。
解決策:
- 時間がかかるサブタスクは incremental feedback (プログレスバーとか)
- 完了時にその旨が伝わる最終出力
nil?
empty?
blank?
present?
presence
は Ruby or Rails のどっち?どんなオブジェクトに生やしてる?何が違う?などの解説。
モンキーパッチするなら Refinements でスコープ制限をかけましょう。
モンキーパッチはグローバル。例えば String をモンキーパッチしたとする。外部ライブラリーと衝突するかも。既存メソッドを上書きしちゃうかも。
using
マクロで特定クラス・モジュールのみにて refine
ブロックのモンキーパッチを有効にする。
Gotcha
- IRB では直接
using
呼べない - 例の
#add1_to
はusing
前に 定義時点 で#one
を呼んでいるから、Refinement がかからない
Ruby アプリ用ドちっちゃい Docker イメージを作った話。
公式イメージだとデカい(1.6GB!)
こう作った:
DDD の Repository で解説した依存性逆転。
class UserRepository def find(id:) User.find(id) end end
User が ActiveRecord モデルだったとしたら、これが ActiveRecord に密結合しちゃう。ActiveRecord 以外の何か(例えば in-memory オブジェクトとか mongodb とか)にすり替えられない。
注入することで柔軟になる。
class UserRepository def initialize(data_source:) @data_source = data_source end def find(id:) User.new(@data_source.find(id)) end end
JWT を実装した話。
通常 Rails アプリの認証はセッションのクッキーを使ってるが、API アプリでは、クッキーが使えない。そこで JWT 登場。
- jwt gem 入れた
- users.password_digest カラム追加
User.has_secure_password
設定- JWT エンコード・デコードは
- 冗長なので、
JwtService
サービスオブジェクトでラップ secret_key_base
再利用
- 冗長なので、
- 認証が通った場合は JWT トークン発行。payload は user_id と
exp
(有効期限) - JWT チェックは
- ヘッダーからトークン抽出
- user_id 存在チェック
- 期限切れた場合は
JwtService
がヌルを返す
- 5 と 6 はそれぞれコマンドオブジェクトで実装し
- Concern で before_filter 定義して、7 のコマンドを呼ぶ
- 各コントローラで 8 の Concern を include
結論:devise に頼らずに一から作ったことで内部実装覚えた
Foo#bar
メソッドがあるとしたら、Work#execute
以外から呼べないようにせよ。という Twitter で流れてきた問題を解決しみた話。
最初は caller
配列をチェックしてみた。動くっちゃ動くが、ファイル名と行番号をハードコードするハメになる。
クラス名とメソッド名を指定するのが理想。Work.instance_method(:execute).source_location
でメソッド開始行番号とファイル名が取れる。メソッド最終行番号は、Work
の全メソッドの開始行番号をソートすることで地道に計算。
定数なのに可変!定数のモジュールを include、extend するとどうなる?などの Ruby 定数豆知識。
Rails + Postgres で UUID を使う方法。
- migration で Postgres 拡張有効化
- UUID カラム追加記法は 2 通り:
id: false
(インデックス、UUID 生成は自分で実装しなきゃ)id: :uuid
(全部オマカセ)
UUID 以外設定しようとしたら
Person.new(id: '123') => #<Person id: nil>
リアル UUID の正規表現に一致しないから ActiveRecord がボツにする。