Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ: Sidekiqが10歳に、BuildKiteのテストを高速化、フィーチャーフラグほか(20220131前編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙏

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Rails: 先週の改修(Rails公式ニュースより)

公式の更新情報をRailsウォッチが先回りしてしまったので、コミットリストの中からChangelogが変更されたものを中心に見繕いました。ドキュメントの修正が多いですね。

🔗 Active Storageのリダイレクトモードでレスポンスをストリーミングしないようにした

従来はリダイレクトモードとプロキシモードのどちらもレスポンスがストリーミングされていたため、新しいスレッドが作成されてコネクションプールでコネクションがリークする可能性があった。しかし実際にはリダイレクトモードでデータを送信しないのでストリーミングは不要。
Luke Lau
同Changelogより

# activestorage/app/controllers/active_storage/base_controller.rb#L4
class ActiveStorage::BaseController < ActionController::Base
- include ActiveStorage::SetCurrent, ActiveStorage::Streaming
+ include ActiveStorage::SetCurrent

  protect_from_forgery with: :exception
# activestorage/app/controllers/active_storage/blobs/proxy_controller.rb#L9
class ActiveStorage::Blobs::ProxyController < ActiveStorage::BaseController
  include ActiveStorage::SetBlob
+ include ActiveStorage::Streaming
# activestorage/app/controllers/active_storage/representations/proxy_controller.rb#L9
class ActiveStorage::Representations::ProxyController < ActiveStorage::Representations::BaseController
+ include ActiveStorage::Streaming
+

つっつきボイス:「リダイレクトモードではデータを送信しないのでスレッドを作る必要はないと」「たしかに」「ActiveStorage::Streamingは通常使わなくてもよいのでBaseControllerから外してProxyControllerに移動したんですね」

参考: §5.1 リダイレクトモード -- Active Storage の概要 - Railsガイド

参考: Rails API ActiveStorage::Streaming

🔗 YAML.loadYAML.unsafe_loadに置き換えた

# activesupport/lib/active_support/encrypted_configuration.rb#L51
      def deserialize(config)
-       YAML.load(config).presence || {}
+       doc = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(config) : YAML.load(config)
+       doc.presence || {}
      end

つっつきボイス:「yamlパーサーであるpsych gemのバージョンアップに対応するためにYAML.loadYAML.unsafe_loadに置き換えられた」「そういえば少し前にpsych 4.0.0でbreaking changeが入っていましたね」

ruby/psych - GitHub

参考: Ruby の Psych.safe_load(YAML.safe_load)の引数が Psych v4.0.0 から非互換になる - Secret Garden(Instrumental)

🔗 Object#instance_variable_namesを高速化


つっつきボイス:「これはActive Supportの最適化ですね」「instance_variable_namesって何に使うんでしょうか?」「文字どおりインスタンス変数のリストを返すメソッドですね」「あ〜なるほど」

# activesupport/lib/active_support/core_ext/object/instance_variables.rb#L27
- def instance_variable_names
-   instance_variables.map(&:to_s)
+ if Symbol.method_defined?(:name) # RUBY_VERSION >= "3.0"
+   # Returns an array of instance variable names as strings including "@".
+   #
+   #   class C
+   #     def initialize(x, y)
+   #       @x, @y = x, y
+   #     end
+   #   end
+   #
+   #   C.new(0, 1).instance_variable_names # => ["@y", "@x"]
+   def instance_variable_names
+     instance_variables.map(&:name)
+   end
+ else
+   def instance_variable_names
+     variables = instance_variables
+     variables.map! { |s| s.to_s.freeze }
+     variables
+   end
+ end
end

「Ruby 3.0からSymbol#nameが使えるようになったので、Stringのアロケーションをそれで避けたのか」「Ruby 3.1だと1.5倍ぐらい速くなっている!」

🔗 ActiveSupport::LoggerThreadSafeLevel#addを削除


つっつきボイス:「不要になったので消したっぽい」「Ruby 2.7以上なら不要なメソッドで、現在のRailsがRuby 2.7以上が必須になったので消したんですね、なるほど」

参考: §1.2 Rubyバージョン -- Rails アップグレードガイド - Railsガイド

🔗 ガイド: カウンタキャッシュに追記


つっつきボイス:「これはドキュメントの更新ですね」「reset_countersというメソッドがあるとは知らなかった」「あるモデルの主キーを更新すると、そのモデルの主キーを参照して計算しているカウンタキャッシュの値が実際の参照モデル数と一致しなくなってしまうので、reset_countersで再計算させる」

# guides/source/association_basics.md#L1040
-Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`.
+Counter cache columns are added to the owner model's list of read-only
+attributes through `attr_readonly`.

+If for some reason you change the value of an owner model's primary key, and do
+not also update the foreign keys of the counted models, then the counter cache
+may have stale data. In other words, any orphaned models will still count
+towards the counter. To fix a stale counter cache, use [`reset_counters`][].

+[`reset_counters`]: https://api.rubyonrails.org/classes/ActiveRecord/CounterCache/ClassMethods.html#method-i-reset_counters

🔗Rails

🔗 Railsのminitestを1分で終わらせる(Ruby Weeklyより)


つっつきボイス:「BuildKiteでテストが速くなったのかと思ったら、factory_botのファクトリーを修正して高速化したのね」

thoughtbot/factory_bot - GitHub

「そういえば、BuildKiteはRailsフレームワークのテストにも使われていますね↓」「そうそう、Railsで複数バージョンのテストを回すのに使っている: CIの設定をいろいろ変えてテストするときにこういうのを使いますね」「CircleCIの仲間みたいな感じですか」

参考: BuildKite -- Ruby on Rails/Rails
参考: 最先端の CI/CD ツール - CircleCI

「1分でテストが終わるってすごい」「個別のテストが高速ならやれるでしょうね」

🔗 Page ObjectでRailsのシステムテストをメンテ可能にする(Ruby Weeklyより)


つっつきボイス:「Page Objectってどこかで聞いたような」「こういうクラスを作ってテスト用のヘルパーにするという趣旨みたい↓」「TestPageとかRegistrationPageとかがそうなんですね」

# 同記事より
# test/pages/test_page.rb
class TestPage
  include Rails.application.routes.url_helpers

  attr_accessor :test, :page

  def initialize(system_test, page)
    @test = system_test
    @page = page
  end

  def visit
    @test.visit page_path
  end

  def page_path
    raise "Override this method with the page path"
  end
end

# test/pages/registration_page.rb
class RegistrationPage < TestPage
  def register(user)
    @test.fill_in "Email", with: user.email
    @test.fill_in "Password", with: "12345671"
    @test.fill_in "Password confirmation", with: "12345671"

    @test.click_on "Sign up"
  end

  def page_path
    new_user_registration_path
  end
end

# test/pages/dashboard_page.rb
class DashboardPage < TestPage
  def assert_logged_in
    @test.assert_selector "h1", text: "Dashboard"
  end

  def page_path
    dashboard_path
  end
end

「ログインセッションを取るためのPage Objectぐらいはあってもいいけど、個人的にはあまり増やしたくない気持ちがありますね」「というと?」「テストコードにロジックを持つクラスを作ると、テストコードをテストしないといけなくなるので」「あ〜たしかに!」「そうなんですよ」「テストコードはベタがいいというヤツですね」

「もちろん、ログインセッションのように毎回同じことをやることがわかっている部分をこういうふうにPage Objectで自動化する↓のはありだと思いますし、実際にやります」

# 同記事より
# test/pages/registration_page.rb
class RegistrationPage < TestPage
  def register(user)
    @page.fill_in I18n.t("attributes.user.email"), with: user.email
    @page.fill_in I18n.t("attributes.user.password"), with: user.password
    @page.fill_in I18n.t("attributes.user.password_confirmation"), with: user.password

    @page.click_on I18n.t("buttons.register")
  end

  def page_path
    new_user_registration_path
  end
end

🔗 Rails Best Practicesサイト


つっつきボイス:「お、年季の入ったサイト」「昔からあるRails Best Practicesというサイトで、rails_best_practices gemのチェック結果ではここを見るように言われるんですが、古くなっているのはあるかなと思って取り上げてみました」

flyerhzm/rails_best_practices - GitHub

「どれどれ、トップのエントリの内容↓がいきなり古い↓」「ありゃ」「今はTime.zone.nowではなくTime.currentを使うべき」「そうそう、Time.currentですよ〜」「一番新しいエントリが2014年か」「2010年のエントリが一番多いみたいですね」

# rails-bestpractices.comより
Time.zone.now
Time.zone.today

参考: When should DateTime.now.utc vs. Time.current.utc be used in Rails? - Stack Overflow

「rails_best_practices gemは今も更新されているみたいだけど」「名前のとおりRailsのベストプラクティスをチェックするという位置づけのようですが、今だとrubocop-railsと役割がちょっとかぶっていそう」「たしかに似てそうですね」「rubocop-rails↓は広く使われていて更新も盛んなので、自分だったらrubocop-railsを使うかな」「自分もrubocop-railsでいいかな」

rubocop/rubocop-rails - GitHub

「rails_best_practicesに限らず、この種のgemは深く考えずに適用するとハマる可能性があるので注意が必要ですね: 昔のベストプラクティスが今もそうとは限らないという話も含めて」「そうですね」


後で見ると、サイトは7年間更新されていませんでした↓

flyerhzm/rails-bestpractices.com - GitHub

🔗 online_migrations: PostgreSQLマイグレーションをチェックするgem(Ruby Weeklyより)

fatkodima/online_migrations - GitHub


つっつきボイス:「これは?」「PostgreSQLのマイグレーションで危険な操作があったら警告するそうです」

「このonline_migrationsと似たようなものを以前も見た気がする」「そういえばREADMEの末尾に書いてあるstrong_migrations↓は以前取り上げたことがあります(ウォッチ20170915)」

ankane/strong_migrations - GitHub

「結局マイグレーションで同じようなミスをする人は多いということでしょうね」「まあそうですよね」「strong_migrationsと同様、使いたい人が使えばよいと思います」

🔗 マイグレーションのドキュメントとして使う

「どちらのgemでもいいんですが、READMEに書かれている問題解説と修正方法を読んで参考にするといいんじゃないかな」「なるほど、マイグレーションをチェックするときのドキュメントとして使うんですね」「必要なことはだいたいREADMEに書かれているようです👍」


「これらのgemは警告のみのようですが、マイグレーションによっては自動修正しようがないものもあるんですよ: たとえばマイグレーション中にいったんデプロイをはさむとか」「あ、たしかに」「READMEにも書かれていますけど、たとえばカラムを変更するときなんかは、add_columnしてからいったんデプロイして、データ移行が終わってからremove_columnする、といった操作が必要になりますし、カラムを削除するときのignored_columnsなんかもそう」

🔗 フィーチャーフラグを1時間で実装した話(Ruby Weeklyより)

# 同記事より
  class Features
    def self.configuration
      Rails.configuration.features
    end

    def self.enabled?(feature)
      configuration.fetch(feature.to_sym, false)
    end
  end

つっつきボイス:「フィーチャーフラグの実装は実際に簡単なので、やりたい人はどうぞ👍」

参考: フィーチャートグル - Wikipedia


「ところで、自分はまだ使ったことはありませんが、フィーチャーフラグをWebインターフェイスで操作できるサービスがありますよね、たしかflipperあたりかな」「flipperは以前取り上げたことがありますね(ウォッチ20210330)↓」「エンジニアの手をわずらわせずにフィーチャーフラグを切り替えられたらいいなと思うときがあるので、そういうサービスを試してみたい気持ちがあります」「わかります」

jnunemaker/flipper - GitHub

🔗 その他Rails


つっつきボイス:「Sidekiqももう10年経つんですね🎉」「お〜そんなに」「まだ課金したことないけど」「お、有料版もあるんですか」「サイトにも記載があるように↓、Pro版以上でないと使えない機能もありますし、マルチプロセスはEnterprise版が必要」「Enterprise版は月2万か...」「Enterprise版の機能が必要になるほどの大規模サービスなら十分払えると思いますよ」


前編は以上です。

バックナンバー(2022年度第1四半期)

週刊Railsウォッチ: Rubyコンパイラの歴史動画、RubyのWebAssembly対応進む、ぼっち演算子の注意点ほか(20220126後編)

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

Ruby Weekly


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。