rastam on rails

東京在住のマレーシア人 Rubyist

Ruby Weekly #361: 日本語サマリー

職場の Slack の #ruby 窓で Ruby Weekly メルマガが毎週配信されます。その中から面白そうなものをピックアップして、日本語で簡単なサマリーを書くようにしています。そのサマリーをここでまとまさせていただきます。くだけた日本語で失礼いたします。

rubyweekly.com

Highlights

Fixing Bundler's Dependency Resolution Algorithm

dependabot(依存 gem チェッカー as a Service)のバグ改修で Bundler を 2 倍速くした話。

バグは Bundler の依存関係解決ロジック(Molinillo gem に抽出されてる)にあった。バックトラッキングアルゴリズムが複雑すぎて、リファクターすることに。そこで閃いて、グルーピングで 10 倍高速化。

次バグ改修したが、今度激重くなった。フィルタリングしてみたら今のスピードになった。

A Crash Course in Analyzing Memory Usage in Ruby

YAML から巨大 Hash を読み込む Rails アプリのメモリが気になり、Ruby メモリ計測方法調べてみた話。

  1. String#bytesize で文字列単位計測
  2. memory_profiler gem でブロック単位(Total retained 値)
  3. derailed_benchmarks gem の derailed exec perf:mem_over_time で多数リクエストを投げてメモリ計測

結論:3 の数値が横ばいになっていくから Hash 巨大化してもメモリへの影響はさほどない。

Looking Into CSRF Protection in Rails

RailsCSRF 対策の内部実装をコードリーディングした話。

Advanced Enumeration with Ruby

Enumeration 上級者編

  • Enumeration とは
  • Enumerator の最低条件
  • Lazy vs Not Lazy
  • 上級者活用例
    • グループ分け
    • フォールディング
    • Struct

Why It's Just Lazy to Bad-Mouth Rails

Rails 終わったなんてデタラメ」ブラジル RubyConf 元オーガナイザー Fabio Akita 氏。 Rails 経歴を振り返りながら、先々週の Coding Dojo ブートキャンプ騒ぎに反論。

2003 年。SNS 元年。

2004 年。Gmail 誕生。FB 急上昇。常識を覆す Rails がブラジルの無名カンファレンスで登場。

2006 年。TwitterGroupon、Engine Yard 登場。AWS S3・EC2 リリース。インディーズでもスケーリングが可能に。

2007 年。Heroku 誕生。Git リリース。iPhone 発売。

2008 年。GitHub 誕生。DevOps 元年。Zed Shaw が Ruby から引退した騒ぎ。Rails + Merb 結合。

2009 年。Apple ネイティヴ SDK リリース。NoSQL 元年。TwitterScala に移行。

2010 年。AppleFlash に対して宣戦布告。Rails 全盛期。

2011 年。RubyObjective-C が主流となった。

2012 年以降。Rails 屋さんから生まれた、なんでも作り直したがる不満げなエンジニアの時代。Go、Elixir、Scala などニッチ言語に流出。

結論Rails 終わったなんて大げさ。常識を覆さなくなったのは、Rails が常識になったから。

Effectively Managing Localization Files in Rails

Railsi18n 管理

  • キー命名統一しろ
  • ダブらせるな
  • 文字列前後に " 付けろ(YAML パース失敗 → Rails ブート失敗の原因。超デバッグし辛い)
  • ローカライズ外注先とのワークフロー決めろ
  • 該当文章が見つからなかった場合は機械翻訳をフォールバックに(コードあり)
  • YAML ファイル名とファイル内ロケールが一致しているかテスト書け

Tutorial

Modeling has_many Relationships with AWS DynamoDB

DynamoDB で has_many アソシエーションをモデリングした話。1 テーブルでのモデリングと 2 テーブルでのモデリングを比較した。

Using Gemstash for Private Gem Hosting

社内 gem を Gemfury(有料)でホスティングしてた Showoff 社が、gemstash でセルフホスティングにした手順。

  1. EC2 インスタンス作成・設定
  2. gemstash 用 Rails アプリ作成
  3. Capistrano 設定
  4. gemstash 設定(認証有無)
  5. デプロイ
  6. gemstash コマンドでプッシュ用キー作成
  7. 社内 gem を gemstash にプッシュ
  8. gemstash コマンドでフェッチ用キー作成、Gemfile に埋め込む

Tips on Treating Flakiness in your Rails Test Suite

テストが不安定になる原因

  • FactoryGirl(見えないところでレコード作りまくってる)
  • タイムスタンプ(Timecop 使え)
  • 外部リクエスト(スタブ・VCR 使え)
  • Ajax(JS レベルでスタブするコードあり)

Faster Rails: Eliminating N+1 Queries

N+1 クエリ撲滅方法

おまけ

  • Semaphore CI では bullet.log もジョブ毎に表示できる!という執筆者のステマ
  • Bullet.raise = true で CI ビルドをレッドにしよう
  • 既存 N+1 が多すぎる場合は、ホワイトリストに追加して、新規クエリのみ CI 対象にしよう

Quick Tip: The --profile Hammer and RSpec

RSpec--profile オプションで重いテスト特定して潰していこう。

Create a State Machine with AASM, Sequel, SQLite, Rake, and RSpec

AASM gem + Sequel で state machine 実装、RSpec でテスト。

Using Ruby and Amazon SQS FIFO Queues

Shoryuken gem + Amazon SQSFIFO キューでバックグラウンドジョブ処理。

FIFO キューの特徴

  • ジョブのグループ分け(処理順を例えばユーザ別に固めたい場合)
  • ジョブ重複実行防止(Amazon SQS に繰り返して投げた場合)

手順

  1. shoryuken sqs create queue.fifo コマンドで FIFO キュー初期化
  2. Shoryuken::Worker を実装(ActiveJob も OK!)
  3. Shoryuken::Worker.perform_asyncActiveJob.perform_later でジョブをキューに積む
  4. 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 バックエンドのアダプターをカスタムなものでも指定可能にした話。

Defining 'Super Secret' Hard-to-Call Methods

極秘メソッドの作り方。

. を含んだメソッド名を define_method('super.secret') で定義したら、send('super.secret') でしか呼べないメソッドができる。

考えられる用途:

  • 外から使わせたくないメソッド(つまり private メソッド)
  • 名前空間汚染防止

執筆者の注意事項:これやらないほうがいいよ!

4 Ways to Secure Your Auth System in Rails

Rails 認証セキュリティ改善案 4 つ。

  1. rack-attack などでリクエスト数制限しろ
  2. ちゃんとした HTTP セキュリティヘッダー付けろ
  3. 認証ライブラリーのソース、changelog、ブログ記事読め
  4. トークンをダイジェストしろ

Bundler and Private Dependencies

Bundler + GitHub の private リポ上の gem

Code

A Collection of Ruby One-Liners

役に立つ awk ワンライナー集Ruby で実装してみた。

graphql-guard: Simple Authorization Gem for GraphQL

GraphQL 用認可 gem。CanCanCan、Pundit とも併用可能。

Ruby Weekly #360: 日本語サマリー

職場の Slack の #ruby 窓で Ruby Weekly メルマガが毎週配信されます。その中から面白そうなものをピックアップして、日本語で簡単なサマリーを書くようにしています。そのサマリーをここでまとまさせていただきます。くだけた日本語で失礼いたします。

rubyweekly.com

Highlights

Is WEBrick Webscale?

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 スレッド。

A Look At How Ruby Interprets Your Code

RubyVM::InstructionSequenceYARV インストラクションを覗いてみる話

Eventide: A Microservices and Event Sourcing Toolkit for Ruby

イベントソーシング用フレームワーク

Trestle: A Modern, Responsive Admin Framework for Rails

Rails 用管理画面フレームワーク

Riffing on 'interpose' Implementations in Ruby

先週の Clojure の interpose の話に興味を持った Avdi Grimm 先生が yield 式 Enumerable でリファクターしてみた話。テスト、ベンチマーク付き。

Creating a Ruby DSL: A Guide to Metaprogramming

config 系 DSL の作り方。

  1. PORO 作る
  2. mixin に移植。再利用できるように
  3. config.hoge = 123hoge 123 になるようにリファクター

注意: メタプロ多め

Airbnb Open Sources a New, Fast Thrift Binding

Apache Thrift シリアライゼーションプロトコールRuby バインディングを再実装した Airbnb

Apache が出しているバインディングはパフォーマンスが JSON 以下。AirbnbSparsam は MessagePack と肩を並べる。

Tutorial

Unsupervised Learning Using K-means Clustering in Ruby

kmeans-clusterer gem の k 平均法で教師なし学習を実現させた話。例としてカリフォルニア州 100 大都市を位置でクラスタリングっして、流通センターを設ける場所と数を計算した。

A Few Tips to Improve the Speed of Your Test Suite

テスト高速化。

  • build_stubbed 使え
  • before(:all) 使え
  • 不要な let! 捨てろ
  • 並列実行しろ
  • ファクトリーの不要なアソシエーション捨てろ
  • ログレベル変えろ

Cleaning Up Path Definitions with dir

__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 対象を指定コミット・ブランチに絞るツール)

Building Crazy Queries with ActiveRecord and Arel

クレイジーSQL を Arel で実現させた話。

Asynchronous Elasticsearch Reindexing with Rails, Searchkick, and Sidekiq

Searchkick + Sidekiq で ActiveRecord データを ElasticSearch にインポートした話。

Ruby 2.4 Avoids Exception for Integer#dup

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

Why Hanami Will Never Unseat Rails

Hanami が絶対に Rails を追い越さない理由。

現状RailsRuby 界隈を圧倒的に支配してる。しかし嫌いになった人も少なくない。

Rails アプリ開発者の 3 段階:

  1. 初めて Rails に触れた時は、フレームワークや gem のロジックがどこに隠されてるのか分からなくて不安。特に .NET など NIH 主義言語の出身者。
  2. コツ掴んで開発スピードに惚れる
  3. モノリスの保守に苦しみ恨む。PORO が恋しくなり、ActiveRecord の複雑さを憎む。

そこで Hanami 登場。Sinatra より機能が充実。Grape ほど特化してない。何よりも Rails よりキレイ。

しかし Hanami は魔術がないわけではない。例えばコントローラの Action#call が Rack 標準の HTTP ステータス + ヘッダー + body の配列なのに、実装する側の戻り値は違う。配列を返すロジックはフレームワークによって prepend されてる。

魔術を使ってるのは、Rails に対抗するのが Hanami の目的ではないから。作成者曰く実用主義がメイン。実用主義では Rails からの大移動が起こらない。Rails には作成者のビジョンがあるから人がついていく。Hanami にはビジョンがない。

Tools

Ordinare: Sort Gems in Your Gemfile Alphabetically

Gemfile の gem を ABC 順にソートするコマンド

Code

Orchparty: A DSL for Configuring Orchestration

docker-compose.yml 系オーケストレーションRuby DSL

Ruby Weekly #359: 日本語サマリー

職場の Slack の #ruby 窓で Ruby Weekly メルマガが毎週配信されます。その中から面白そうなものをピックアップして、日本語で簡単なサマリーを書くようにしています。そのサマリーをここでまとまさせていただきます。くだけた日本語で失礼いたします。

rubyweekly.com

Highlights

Realtime with React and Rails

react_on_rails + ActionCable でリアルタイム地図上現在地配信アプリを作った話。Redux を MobX にすり替えた。Redux よりオブジェクト指向感あり。

owner の位置情報を 2 秒ごとに channel に publish。viewer が位置情報を配列に push、react-map-gl で地図上描画。

Deploying Rails Apps on Kubernetes with No Downtime

k8sRails の zero-downtime deploy を実現した話。

How DNSimple's Latest API Architecture is Built with Hanami

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

The 'bootsnap' Gem is Now Part of the Rails Default Gemfile

新規 Rails アプリはデフォで bootsnap が Gemfile に入ってて、ブート時間 50% 短縮。

Major Coding Bootcamp Ditches Rails, Claims Lower Employer Demand

米国のメジャーなブートキャンプ Coding DojoRubyRails を教えるのをやめた。雇用先からの需要低下で。代わりに Java・Spring を教えることになった。HackerNews でもざわざわしてる。

Tutorial

Implementing Clojure's 'interpose' in Ruby

ClojureinterposeRuby で実装した話。

[1, 2, 3].interpose('x')
# [1, 'x', 2, 'x', 3]

Enumerable をモンキーパッチすることで実装した。

Quick Tip: Add Output to Your Long Running Rake Tasks

時間がかかる Rake タスクは出力しましょう、と筆者が思うようになったきっかけ。

最近こういう Rake タスク作ることになった:

  1. S3 上の全ファイル一覧取得
  2. 最終更新日時でソート
  3. ソート順に各ファイルに対してバックグラウンドジョブをキューに積む

10 分はかかるが、パフォーマンス的には問題はなかった。実行する人にとっては辛い。突然死したかどうかすら知りようがない。

解決策:

  1. 時間がかかるサブタスクは incremental feedback (プログレスバーとか)
  2. 完了時にその旨が伝わる最終出力

The Differences Between nil?, empty?, blank?, and present?

nil? empty? blank? present? presenceRuby or Rails のどっち?どんなオブジェクトに生やしてる?何が違う?などの解説。

Scope the Monkey: A Refinements Refresher

モンキーパッチするなら Refinements でスコープ制限をかけましょう。

モンキーパッチはグローバル。例えば String をモンキーパッチしたとする。外部ライブラリーと衝突するかも。既存メソッドを上書きしちゃうかも。

using マクロで特定クラス・モジュールのみにて refine ブロックのモンキーパッチを有効にする。

Gotcha

  • IRB では直接 using 呼べない
  • 例の #add1_tousing 前に 定義時点#one を呼んでいるから、Refinement がかからない

Build a Minimal Docker Container for Ruby Apps

Ruby アプリ用ドちっちゃい Docker イメージを作った話。

公式イメージだとデカい(1.6GB!)

こう作った:

  1. 公式 Alpine Linux イメージ(5MB)
  2. apk で ruby、bundler、ruby-io-console 追加
  3. apk キャッシュ削除
  4. ruby-dev ビルドパッケージ追加

The Dependency Inversion Principle

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

JSON Web Token Authentication in Rails APIs

JWT を実装した話。

通常 Rails アプリの認証はセッションのクッキーを使ってるが、API アプリでは、クッキーが使えない。そこで JWT 登場。

  1. jwt gem 入れた
  2. users.password_digest カラム追加
  3. User.has_secure_password 設定
  4. JWT エンコード・デコードは
    • 冗長なので、JwtService サービスオブジェクトでラップ
    • secret_key_base 再利用
  5. 認証が通った場合は JWT トークン発行。payload は user_id と exp(有効期限)
  6. JWT チェックは
    • ヘッダーからトークン抽出
    • user_id 存在チェック
    • 期限切れた場合は JwtService がヌルを返す
  7. 5 と 6 はそれぞれコマンドオブジェクトで実装し
  8. Concern で before_filter 定義して、7 のコマンドを呼ぶ
  9. 各コントローラで 8 の Concern を include

結論:devise に頼らずに一から作ったことで内部実装覚えた

Enforcing the Specific Location of a Method Call

Foo#bar メソッドがあるとしたら、Work#execute 以外から呼べないようにせよ。という Twitter で流れてきた問題を解決しみた話。

最初は caller 配列をチェックしてみた。動くっちゃ動くが、ファイル名と行番号をハードコードするハメになる。

クラス名とメソッド名を指定するのが理想。Work.instance_method(:execute).source_location でメソッド開始行番号とファイル名が取れる。メソッド最終行番号は、Work の全メソッドの開始行番号をソートすることで地道に計算。

Everything You Need to Know About Ruby Constants

定数なのに可変!定数のモジュールを include、extend するとどうなる?などの Ruby 定数豆知識。

Working with UUIDs in Rails

Rails + Postgres で UUID を使う方法。

  1. migration で Postgres 拡張有効化
  2. UUID カラム追加記法は 2 通り:
    • id: false (インデックス、UUID 生成は自分で実装しなきゃ)
    • id: :uuid (全部オマカセ)

UUID 以外設定しようとしたら

Person.new(id: '123')
=> #<Person id: nil>

リアル UUID の正規表現に一致しないから ActiveRecord がボツにする。