Ruby Weekly #361: 日本語サマリー
職場の Slack の #ruby 窓で Ruby Weekly メルマガが毎週配信されます。その中から面白そうなものをピックアップして、日本語で簡単なサマリーを書くようにしています。そのサマリーをここでまとまさせていただきます。くだけた日本語で失礼いたします。
Highlights
dependabot(依存 gem チェッカー as a Service)のバグ改修で Bundler を 2 倍速くした話。
バグは Bundler の依存関係解決ロジック(Molinillo gem に抽出されてる)にあった。バックトラッキングアルゴリズムが複雑すぎて、リファクターすることに。そこで閃いて、グルーピングで 10 倍高速化。
次バグ改修したが、今度激重くなった。フィルタリングしてみたら今のスピードになった。
YAML から巨大 Hash を読み込む Rails アプリのメモリが気になり、Ruby メモリ計測方法調べてみた話。
String#bytesize
で文字列単位計測- memory_profiler gem でブロック単位(
Total retained
値) - derailed_benchmarks gem の
derailed exec perf:mem_over_time
で多数リクエストを投げてメモリ計測
結論:3 の数値が横ばいになっていくから Hash 巨大化してもメモリへの影響はさほどない。
Rails の CSRF 対策の内部実装をコードリーディングした話。
Enumeration 上級者編
- Enumeration とは
Enumerator
の最低条件- Lazy vs Not Lazy
- 上級者活用例
- グループ分け
- フォールディング
- Struct
「Rails 終わったなんてデタラメ」ブラジル RubyConf 元オーガナイザー Fabio Akita 氏。 Rails 経歴を振り返りながら、先々週の Coding Dojo ブートキャンプ騒ぎに反論。
2003 年。SNS 元年。
2004 年。Gmail 誕生。FB 急上昇。常識を覆す Rails がブラジルの無名カンファレンスで登場。
2006 年。Twitter、Groupon、Engine Yard 登場。AWS S3・EC2 リリース。インディーズでもスケーリングが可能に。
2007 年。Heroku 誕生。Git リリース。iPhone 発売。
2008 年。GitHub 誕生。DevOps 元年。Zed Shaw が Ruby から引退した騒ぎ。Rails + Merb 結合。
2009 年。Apple ネイティヴ SDK リリース。NoSQL 元年。Twitter が Scala に移行。
2010 年。Apple が Flash に対して宣戦布告。Rails 全盛期。
2011 年。Ruby、Objective-C が主流となった。
2012 年以降。Rails 屋さんから生まれた、なんでも作り直したがる不満げなエンジニアの時代。Go、Elixir、Scala などニッチ言語に流出。
結論:Rails 終わったなんて大げさ。常識を覆さなくなったのは、Rails が常識になったから。
- キー命名統一しろ
- ダブらせるな
- 文字列前後に
"
付けろ(YAML パース失敗 → Rails ブート失敗の原因。超デバッグし辛い) - ローカライズ外注先とのワークフロー決めろ
- 該当文章が見つからなかった場合は機械翻訳をフォールバックに(コードあり)
- YAML ファイル名とファイル内ロケールが一致しているかテスト書け
Tutorial
DynamoDB で has_many
アソシエーションをモデリングした話。1 テーブルでのモデリングと 2 テーブルでのモデリングを比較した。
社内 gem を Gemfury(有料)でホスティングしてた Showoff 社が、gemstash でセルフホスティングにした手順。
- EC2 インスタンス作成・設定
- gemstash 用 Rails アプリ作成
- Capistrano 設定
- gemstash 設定(認証有無)
- デプロイ
gemstash
コマンドでプッシュ用キー作成- 社内 gem を gemstash にプッシュ
gemstash
コマンドでフェッチ用キー作成、Gemfile に埋め込む
テストが不安定になる原因
N+1 クエリ撲滅方法
includes
- bullet gem
おまけ
- Semaphore CI では bullet.log もジョブ毎に表示できる!という執筆者のステマ
Bullet.raise = true
で CI ビルドをレッドにしよう- 既存 N+1 が多すぎる場合は、ホワイトリストに追加して、新規クエリのみ CI 対象にしよう
RSpec の --profile
オプションで重いテスト特定して潰していこう。
Create a State Machine with AASM, Sequel, SQLite, Rake, and RSpec
AASM gem + Sequel で state machine 実装、RSpec でテスト。
Shoryuken gem + Amazon SQS の FIFO キューでバックグラウンドジョブ処理。
FIFO キューの特徴
- ジョブのグループ分け(処理順を例えばユーザ別に固めたい場合)
- ジョブ重複実行防止(Amazon SQS に繰り返して投げた場合)
手順
shoryuken sqs create queue.fifo
コマンドで FIFO キュー初期化Shoryuken::Worker
を実装(ActiveJob
も OK!)Shoryuken::Worker.perform_async
やActiveJob.perform_later
でジョブをキューに積むshoryuken -q queue -r ./hello_worker.rb
コマンドでジョブ処理開始
rate limit にでも使える。
Using dry-container to Implement Inversion Of Control for Hanami::Events
Hanami::Events gem 作成者が dry-container の制御の逆転で DB バックエンドのアダプターをカスタムなものでも指定可能にした話。
極秘メソッドの作り方。
.
を含んだメソッド名を define_method('super.secret')
で定義したら、send('super.secret')
でしか呼べないメソッドができる。
考えられる用途:
- 外から使わせたくないメソッド(つまり
private
メソッド) - 名前空間汚染防止
執筆者の注意事項:これやらないほうがいいよ!
Rails 認証セキュリティ改善案 4 つ。
Bundler + GitHub の private リポ上の gem
git://
プロトコール使うな(中間者攻撃脆弱性あり)github:
使うな(裏でgit://
使ってるから)- SSH プロトコールを使う場合は、CI は Machine User の Deploy Key で
- HTTPS プロトコールを使う場合は、Gemfile に Basic 認証のアイパスを埋め込むな。下記のどれかにしろ:
Code
役に立つ awk ワンライナー集 を Ruby で実装してみた。
GraphQL 用認可 gem。CanCanCan、Pundit とも併用可能。
Ruby Weekly #360: 日本語サマリー
職場の Slack の #ruby 窓で Ruby Weekly メルマガが毎週配信されます。その中から面白そうなものをピックアップして、日本語で簡単なサマリーを書くようにしています。そのサマリーをここでまとまさせていただきます。くだけた日本語で失礼いたします。
Highlights
schneems さんは昔 jekyll ブログをうっかり WEBrick でデプロイしちゃった。意外とパフォーマンスが良かった。平均 7ms。Perc95 33ms。スループットは 25 requests/min。
今年もっとも人気な記事は 375 requests/min まで上がったが、WEBrick は 1 dyno で 8751 requests/min まで対応可能。余裕。
NGINX に移行することになったが、パフォーマンスのためではなく、SSL を強制させたかったから。一見大変そうだが static buildpack のおかげで static.json ファイル 1 個置くだけで済んだ。
- 平均 7ms → 3ms
- Perc95 33ms → 10ms
- メモリ使用率もグンと下がった
結論:(jekyll みたいに)静的ファイルをサーブするだけなら WEBrick で十分。
おまけ:WEBrick は意外とマルチスレッド対応。1 リクエスト = 1 スレッド。
RubyVM::InstructionSequence
で YARV インストラクションを覗いてみる話
Eventide: A Microservices and Event Sourcing Toolkit for Ruby
イベントソーシング用フレームワーク
先週の Clojure の interpose
の話に興味を持った Avdi Grimm 先生が yield
式 Enumerable でリファクターしてみた話。テスト、ベンチマーク付き。
config 系 DSL の作り方。
- PORO 作る
- mixin に移植。再利用できるように
config.hoge = 123
がhoge 123
になるようにリファクター
注意: メタプロ多め
Apache Thrift シリアライゼーションプロトコールの Ruby バインディングを再実装した Airbnb。
Apache が出しているバインディングはパフォーマンスが JSON 以下。Airbnb の Sparsam は MessagePack と肩を並べる。
Tutorial
kmeans-clusterer gem の k 平均法で教師なし学習を実現させた話。例としてカリフォルニア州 100 大都市を位置でクラスタリングっして、流通センターを設ける場所と数を計算した。
テスト高速化。
build_stubbed
使えbefore(:all)
使え- 不要な
let!
捨てろ - 並列実行しろ
- ファクトリーの不要なアソシエーション捨てろ
- ログレベル変えろ
__dir__
が __FILE__
に勝る理由。コードが短くなるから。
Lint Your Ruby Code with Overcommit and Static Analysis Tools
Linter まとめ
- overcommit (コミットのメッセージやメタデータ)
- rubocop (コード規約)
- fasterer (パフォーマンス)
- bundler-audit (依存 gem の脆弱性)
- reek (コードスメル)
- rails_best_practices
- brakeman (セキュリティ脆弱性)
- foodcritic (Chef cookbooks)
- fukuzatsu (コード複雑さ)
- rubycritic (linter 結果の美しきウェブ UI 報告)
- pronto (linter 対象を指定コミット・ブランチに絞るツール)
Asynchronous Elasticsearch Reindexing with Rails, Searchkick, and Sidekiq
Searchkick + Sidekiq で ActiveRecord データを ElasticSearch にインポートした話。
Ruby 2.3 以前の Integer#dup
は TypeError でエラってた。プリミティブのようなものなので複製するのがおかしいから。
2.4 以降はエラー投げずに何もしない。
Story
Why We Broke Our Philosophical Vows to Bring You CircleCI 2.0
CircleCI を一から作り直した理由。
一から作り直しちゃダメだと Joel (on Software) も言った。CI/CD 業社なのに一から作り直すなんて全然 CD になってない。それでも 15 ヶ月かけた作り直した。
弊社インフラが効率のピークに迫った。次のピークまでには、根本アーキテクチャーへの変更が必要だったが、段階的には実装できないものだった。
スコープをなるべく小さく絞った。スパイク、タイムボックス、そして失敗を繰り返した。1.0 と 2.0 を並行でビルドできるようにツーリング改善した。closed alpha、closed beta、open beta で発覚した設計ミスを直した。そして数週間前にリリースした。
1.0 は順番通りに build、test、deploy フェーズで固く縛られてた。途中でテストが失敗した場合は、頭からやりなおすしかなかった。
2.0 はフェーズを疎結合したジョブに分割したことで、ワークフローを作れるようになった。テストが失敗したとしても、一から再コンパイルせずに、失敗したジョブから再実行すれば良し。しかもジョブ毎にそれぞれのブランチ、リソース、並行レベルを当てることができる。
Opinion
Hanami が絶対に Rails を追い越さない理由。
現状:Rails は Ruby 界隈を圧倒的に支配してる。しかし嫌いになった人も少なくない。
- 初めて Rails に触れた時は、フレームワークや gem のロジックがどこに隠されてるのか分からなくて不安。特に .NET など NIH 主義言語の出身者。
- コツ掴んで開発スピードに惚れる。
- モノリスの保守に苦しみ恨む。PORO が恋しくなり、ActiveRecord の複雑さを憎む。
そこで Hanami 登場。Sinatra より機能が充実。Grape ほど特化してない。何よりも Rails よりキレイ。
しかし Hanami は魔術がないわけではない。例えばコントローラの Action#call
が Rack 標準の HTTP ステータス + ヘッダー + body の配列なのに、実装する側の戻り値は違う。配列を返すロジックはフレームワークによって prepend されてる。
魔術を使ってるのは、Rails に対抗するのが Hanami の目的ではないから。作成者曰く実用主義がメイン。実用主義では Rails からの大移動が起こらない。Rails には作成者のビジョンがあるから人がついていく。Hanami にはビジョンがない。
Tools
Gemfile の gem を ABC 順にソートするコマンド
Code
docker-compose.yml 系オーケストレーション用 Ruby DSL
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 がボツにする。