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

週刊Railsウォッチ: ActiveRecord::QueryLogs追加、spring gemがデフォルトから削除、fast_gettextほか(20210906前編)

こんにちは、hachi8833です。今週はいよいよRubyKaigi Takeout 2021ですね。

週刊Railsウォッチについて

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

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

お知らせ: 来週の週刊Railsウォッチはお休みいたします🙇。

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

公式に追いつくべく今回は2回分取り上げます。

import-mapsは先週取り上げたので(ウォッチ20210831)、importmaps-railsリポジトリを貼ります。

rails/importmap-rails - GitHub


つっつきボイス:「DHHは今後このgemを推していくんでしょうね」「予想はしていたけどRails 7以上なのか〜」「今やってるプロジェクトでimport mapsが使えたらと思ったんですが残念」

🔗 Marginalia gemがActiveRecord::QueryLogsとして追加


つっつきボイス:「Marginalia?」「Basecampのgemだそうです」「そのgemがネイティブのActiveRecord::QueryLogsとして追加されたみたい」

basecamp/marginalia - GitHub

「MarginaliaはSQLクエリのログにどのアプリのどのコントローラのどのアクションからのクエリかというコメントをこういうふうに追加するのね↓: そういえばこんなgemがあった」「名前でぱっとわかりにくそう」「造語かと思ったら”傍注”という意味でした」「入っていたら使うかも👍」

# basecamp/marginalia READMEより
Account Load (0.3ms)  SELECT `accounts`.* FROM `accounts` 
WHERE `accounts`.`queenbee_id` = 1234567890 
LIMIT 1 
/*application:BCX,controller:project_imports,action:show*/

ActiveRecord::QueryLogsを追加

Active Recordで生成されるすべてのSQLクエリにコンフィグ可能なタグが自動追加可能になった。

# config/application.rb
module MyApp
  class Application < Rails::Application
    config.active_record.query_log_tags_enabled = true
  end
end

デフォルトでは、クエリタグに「アプリケーション」「コントローラ」「アクション」の詳細が追加される。

class BooksController < ApplicationController
  def index
    @books = Book.all
  end
end
GET /books
# SELECT * FROM books /*application:MyApp;controller:books;action:index*/

静的な値やProcを含むカスタムタグをアプリケーション設定で定義可能。

config.active_record.query_log_tags = [
  :application,
  :controller,
  :action,
  {
    custom_static: "foo",
    custom_dynamic: -> { Time.now }
  }
]

Keeran Raj Hawoldar, Eileen M. Uchitelle, Kasper Timm Hansen
同Changelogより

以下は作成中の移行ガイドだそうです。

参考: upgrade.md

🔗 マルチDBのrails db:setuprails db:resetで特定のデータベースを指定できるようになった

これは、データベース固有のセットアップとリセットのタスクを可能にする試み。自分たちはマルチプルデータベースを広範囲に使っていて、すべてのデータベースを一度にリセットすることは避けたいと考えている。この変更により、名前空間ごとにデータベース固有のセットアップやリセットのタスクが追加される。デフォルトのセットアップタスクやおよびリセットタスクは、説明文を除いて変更していない。

ひとつ問題があるとすれば、db:seedタスクはデータベースに依存しないため、データベースに依存するタスクはすべてをseedすることになる点。特定のデータベースだけをseed可能だろうか?見たところ、seedファイルはどのActive Recordモデルでも参照できるので、できなさそうに思える。

たとえば、primaryとeventsという2つのデータベースがある場合、以下のタスクが利用できる。

rails db:reset                   # Drops and recreates all databases from their schema for the current environment and loads the seeds
rails db:reset:events            # Drops and recreates the events database from its schema for the current environment and loads the seeds
rails db:reset:primary           # Drops and recreates the primary database from its schema for the current environment and loads the seeds
rails db:setup                   # Creates all databases, loads all schemas, and initializes with the seed data (use db:reset to also drop all databases first)
rails db:setup:events            # Creates the events database, loads the schema, and initializes with the seed data (use db:reset:events to also drop the database first)
rails db:setup:primary           # Creates the primary database, loads the schema, and initializes with the seed data (use db:reset:primary to also drop the database first)

同PRより


つっつきボイス:「マルチプルDBで特定のデータベースだけを設定したりリセットしたりできるrakeタスクが追加されたんですね」「今までできなかったとは」「これは欲しい機能👍」

🔗 spring gemをデフォルトインストールから削除


つっつきボイス:「development環境やtest環境でRailsをプリロードして起動を速くするspring gemがRailsのデフォルトから消えるそうです」「いいねがかなり多いですね」

rails/spring - GitHub

コンピュータが高速になったので、小〜中規模アプリでspringを使う大きなメリットがほぼなくなった。よって、たまに問題を起こすspringをデフォルトで入れてつらい思いをする必要はもうない。
同PRより

「自分もspringはいつも真っ先にオフにしてる」「私も」「テストが理由なく落ちるときにspringを無効にすると解消するということがちょくちょくありましたね」「マイグレーションがなぜか失敗したときもよくありました」「ネイティブ環境だとspringもそれなりに有用なのかもしれないけど、最近はDocker環境で使う人が増えていますし、それもあって外したのかもしれませんね」

🔗 classicモード廃止に伴う孤立メソッド削除


つっつきボイス:「ActiveSupport::Dependencyからorphanな(孤立した)メソッドが続々削除されたそうです」「privateなインターフェイスだし消しても大丈夫そう」

参考: 定数の自動読み込みと再読み込み (Classic) - Railsガイド

「もうひとつのプルリクはActiveSupport::Dependencyにあるsafe_constantizeが削除された」「constantizeは使っていたけどsafe_constantizeは知らなかったな〜」

参考: constantizeString

(これはclassicモードの削除に伴うActiveSupport::Dependenciesのお掃除の一環)

ActiveSupport::Dependenciesconstantizeメソッドとsafe_constantizeメソッドはprivateでオートローディングとは関係しておらず、単にinflectorに転送される。これらは歴史的な理由で残されていたが、今や既に不要。
publicなインターフェイスはStringクラスにある。

model_name.constantize

したがって以下の代わりに上のように書ける。

ActiveSupport::Dependencies.constantize(model_name)

既にフレームワークの大半がこのようになっており、残りはわずか。
#43058より

🔗 weekday_options_for_selectビューヘルパーが追加


つっつきボイス:「ブラウザで平日の曜日選択のプルダウンを表示するweekday_options_for_selectってありそうでなかったのか」「ビューのフォームヘルパーなんですね」「これはあっていいメソッド👍」

自分はこれまで多くのRailsアプリで平日の曜日を選択するヘルパーを手作りしなければならなかった。Railsには優秀なヘルパーがほとんど揃っているので、このヘルパーがないことにいささか驚いていた。ちょうどヘルパーをまた実装しなければならなくなり、他の開発者もこのヘルパーがないことに驚いていたので、Railsにプルリクを投げてもいい頃合いだと思った。

このプルリクはFormOptionHelperFormBuilderに2つのメソッドを追加し、Tags::WeekdaySelectクラスを追加する。

weekday_options_for_select
# => "<option value=\"Sunday\">Sunday</option>\n<option value=\"Monday\">Monday</option>\n
# <option value=\"Tuesday\">Tuesday</option>\n<option value=\"Wednesday\">Wednesday</option>\n
# <option value=\"Thursday\">Thursday</option>\n<option value=\"Friday\">Friday</option>\n
# <option value=\"Saturday\">Saturday</option>"

weekday_options_for_selectではselected値を受け取るほかに:index_as_valueオプションと:day_formatオプションも受け取れる。

weekday_options_for_select(nil, day_format: :abbr_day_names)
# => "<option value=\"Sun\">Sun</option>\n<option value=\"Mon\">Mon</option>\n
# <option value=\"Tue\">Tue</option>\n<option value=\"Wed\">Wed</option>\n
# <option value=\"Thu\">Thu</option>\n<option value=\"Fri\">Fri</option>\n
# <option value=\"Sat\">Sat</option>"

weekday_options_for_select(nil, index_as_value: true)
# => "<option value=\"0\">Sunday</option>\n<option value=\"1\">Monday</option>\n
# <option value=\"2\">Tuesday</option>\n<option value=\"3\">Wednesday</option>\n
# <option value=\"4\">Thursday</option>\n<option value=\"5\">Friday</option>\n
# <option value=\"6\">Saturday</option>"

weekday_options_for_selectは以下のような場合で使うヘルパーメソッド。

weekday_select(:model, :weekday)
# => "<select name=\"model[weekday]\" id=\"model_weekday\"><option value=\"Sunday\">Sunday</option>\n
# <option value=\"Monday\">Monday</option>\n<option value=\"Tuesday\">Tuesday</option>\n
# <option value=\"Wednesday\">Wednesday</option>\n<option value=\"Thursday\">Thursday</option>\n
# <option value=\"Friday\">Friday</option>\n<option value=\"Saturday\">Saturday</option></select>"

weekday_selectメソッドはFormBuilderでも使われるので、以下のように書くと

<!-- 同PRより -->
<%= form_for @digest do |f| %>
  <%= f.weekday_select :weekday %>
  <%= f.submit %>
<% end %>

以下のようなHTMLが生成される。

<!-- 同PRより -->
<select name="digest[weekday]" id="digest_weekday">
  <option value="Sunday">Sunday</option>
  <option value="Monday">Monday</option>
  <option value="Tuesday">Tuesday</option>
  <option value="Wednesday">Wednesday</option>
  <option value="Thursday">Thursday</option>
  <option value="Friday">Friday</option>
  <option value="Saturday">Saturday</option>
</select>

同PRより

🔗 特定データベースのyaml設定にdatabase_tasks: falseオプションが追加


つっつきボイス:「通常はたとえばrails db:migrateを実行するとすべてのデータベースにコネクションを張るけど、database_tasks: falseを指定したデータベースにはコネクションを張らなくなるようですね」「ふむふむ」「以下のmy_animals_databaseでアクセスを一切行いたくない場合に使うということだと思います: コマンドラインでもいいような気はしますが、yamlで設定できるとより便利そう👍」

データベース設定オプションdatabase_tasksを追加
「スキーマ管理」「マイグレーション」「seed」などの管理タスクがない外部データベースに接続したい場合、データベースごとにdatabase_tasks: falseオプションを設定できるようになった。

# config/database.yml
production:
  primary:
    database: my_database
    adapter: mysql2
  animals:
    database: my_animals_database
    adapter: mysql2
    database_tasks: false

Weston Ganger
同Changelogより

🔗 削除されたオプション


つっつきボイス:「どちらもDHHによるプルリクです」「#42996で--skip-gemfileオプションが削除されて、#42998で--skip-pumaオプションが削除されたんですね」「歴史的なオプションみたい」

いにしえの小競り合いの記念碑をいつまでも残しておく必要はない。
#42996より
必要な設定を削除するためのオプションには意味がない。
#42996より

🔗Rails

🔗 大量のActionMailジョブをSidekiqで一括処理する(Ruby Weeklyより)


つっつきボイス:「deliver_laterで個別のジョブを大量に投げて詰まるのはよくあるヤツ」「1万通のメールで40分待ちはつらそう…」「この記事ではジョブの登録の詰まりが問題になっているみたいですね」

「Sidekiqのpush_bulkを使うと複数ジョブをまとめて投げられるのか」「メールジョブごとにパラメータを渡したい場合はActionMailer::Parameterized::DeliveryJobを使う必要があるのね」「メールによって送り先や文面をパラメータで変えるのはよくありますね」「ジョブを用意する段階でパラメータを渡しておいてからpush_bulkでまとめてプッシュする方が負荷が小さくなる、たしかに」「アトミックな処理を行う大量のジョブを1個ずつ登録するのはやりたくないですね」

参考: Method: Sidekiq::Client#push_bulk — Documentation for mperham/sidekiq (master)

# 同記事より
def enqueue_many_parametrized_mails(mail_class, template, args_array)
  job = ActionMailer::Parameterized::DeliveryJob

  # convert template and args array into an array of arrays containing args
  # for ActionMailer::DeliveryJob objects
  mailer_job_args = args_array.map { |args|
    [job.new(
      mail_class.name,
      template.to_s,
      "deliver_now",
      {some_arg: "foo"},
      *args
    ).serialize]
  }

  Sidekiq::Client.push_bulk(
    "class" => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper,
    "wrapped" => job,
    "queue" => MAILER_QUEUE,
    "args" => mailer_job_args
  )
end

参考: Rails6 のちょい足しな新機能を試す41(MailDeliveryJob 編) - Qiita

🔗 fast_gettext: 高速i18n gem(Ruby Weeklyより)

grosser/fast_gettext - GitHub

# 同リポジトリより
FastGettext.with_locale 'gsw_CH' do
  FastGettext._('Car was successfully created.')
end
# => "Z auto isch erfolgriich gspeicharat worda."

つっつきボイス:「i18n(国際化)のgemのようです」「gettextライブラリをrubyで再実装したみたい」

参考: gettext - Wikipedia

「fast_gettextはgettextより12倍高速でガベージが530分の1でスレッドセーフですって」「Active SupportのI18n::Simpleも比較されてる」「大量の国際化テキストを処理しないといけない場合に必要になってくるんでしょうね: 自分はあまりその必要に迫られたことはありませんが、海外のサイトだと国際化の言語数が多い分切実なのかも」

Hash FastGettext GetText ActiveSupport I18n::Simple
Speed* 0.08s 0.14s 1.75s 3.75s
Objects* 11K 15K 8017K 7107K
Included backends db, yml, mo, po, logger, chain mo yml (db/key-value/po/chain in other I18n backends)

同リポジトリより


前編は以上です。

バックナンバー(2021年度第3四半期)

週刊Railsウォッチ:TruffleRubyでdig_fetchを実装、ruby/debug gem、AWSハンズオン教材ほか(20210901後編)

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

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

Rails公式ニュース

Ruby Weekly


CONTACT

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