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

週刊Railsウォッチ: Evil Martiansが使っているgem、JavaScriptガイドが更新ほか(20230131前編)

こんにちは、hachi8833です。RubyKaigi 2023のCFPは今夜1/31いっぱいが締め切りです。

週刊Railsウォッチについて

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

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

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

つっつきボイス:「やっと新年の公式更新情報にたどり着きました💦」「ところで最近はRails公式の更新情報び公開ペースが安定していてありがたい」「The Rails Foundationの発足とも関係あるかもしれませんね(ウォッチ20221122)」

🔗 UJSテストランナーがKarmaに置き換えられた

動機/背景
#45546のUJSビルドでBladeがRollupに置き換わった。
このプルリクはその続きとして、テストのビルドでBladeをRollupに置き換え、Action CableのテストランナーでBladeをKarmaに置き換える。
これによって、RailsをRack 2.xに縛り付けていた依存関係の1つであるBladeの最後の利用が削除される。
同PRより

javan/blade - GitHub
rollup/rollup - GitHub
karma-runner/karma - GitHub


つっつきボイス:「従来のBladeがKarmaに置き換えられたのか」「そういえばKarmaっていうランナーありましたね」「BladeがCoffeeScriptに依存していたとは↓」

# Gemfile.lock#L143
    ...
    bindex (0.8.1)
-   blade (0.7.3)
-     activesupport (>= 3.0.0)
-      blade-qunit_adapter (>= 2.0.1)
-     coffee-script
-     coffee-script-source
-     curses (>= 1.4.0)
-     eventmachine
-     faye
-     sprockets (>= 3.0)
-     thin (>= 1.6.0)
-     thor (>= 0.19.1)
-     useragent (>= 0.16.7)
-   blade-qunit_adapter (2.0.1)
    bootsnap (1.9.3)
    ...
# actionview/RUNNING_UJS_TESTS.rdoc
== Running UJS tests

-Ensure that you can build the project by running:
-  rake ujs:server
+Run the tests in headless mode by running:
+
+  rake test:ujs

-Then run the web tests by visiting the following URL in your browser:
+To run the tests in a browser, start the Rails UJS server by running:

-   http://localhost:4567
+  rake ujs:server

🔗 productionでPumaのワーカー数をプロセッサ数と同じになるようにする

production環境でホストのプロセッサのパフォーマンスをすべて使うようにする。
同PRより


つっつきボイス:「DHHのプルリクです」「お〜なるほど、production環境のワーカー数をデフォルトで4とかに固定するより、その環境で使えるリソースの最大数に設定するのはいい👍」「複数のRailsサーバーやジョブワーカーが同居しているようなサーバーだと過剰にワーカーが起動してしまうので、明示的にWEB_CONCURRENCY環境変数を指定するというのもできて良いですね」

# railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt#L14
# Specifies that the worker count should equal the number of processors in production.
+if ENV["RAILS_ENV"] == "production"
+  worker_count = ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count }
+  workers worker_count if worker_count > 1
+end
+

🔗 コールバックのonlyexceptオプションで指定したシンボルがない場合にエラーにするようになった

コールバックのonlyexceptオプションには、存在しないアクションのシンボルを含めることができてしまう。このブランチでは、そうした例外的なアクション名が実際に利用可能なアクションと対応していない場合にエラーを発生させる。
この変更は、誤字脱字のせいで重要なコールバックが呼び出されるべきときに呼び出されなくなってしまうという事態を防ぐことが目的。

before_action :authorize_user, only: [:showww] #typo

def show
end

あるいは、コールバックで参照されていることに気づかないままアクション名を変更することを防ぐ。

before_action :authorize_user, only: [:admin_console]

# 行数が多くて見落としてしまう...

def admin_panel # メソッドを最近`admin_console`から変更した
end

その他の情報
このチェックをAbstractController::Callbacks::ActionFilterに置いた理由は、コールバック条件のリストと、インスタンス化されたコントローラが同じ場所にあるから。このチェックはコントローラの初期化時点で行う方がより"クリーン"と思えたが、初期化もコールバックのフィルタもリクエストごとに行われるので、少なくともここでは既に発生しているコールバックチェインのスキャンに相乗りすることが可能。

このブランチではconfig.action_controller.raise_on_missing_callback_conditionalsコンフィグオプションを追加してあり、これを音にしたときだけエラーがraiseされる。個人的には常に検出すべきバグだとは思うが(少なくともdevelopmentモードでは)、アプリをアップグレードしやすくするためにオフにできるオプションがあるとよいと思う。
同PRより


つっつきボイス:「プルリクの英語タイトル、プログラミング言語の予約語が名詞として使われていることを知らなかったら走り書きに見えそう」「コールバックのonly:とかで名前を書き間違えたりしたらちゃんとエラーを出すようになったんですね: これはいい改修👍」「呼んだつもりのコールバックが呼ばれてなかったら怖い」「タイポってどうしても発生しますよね」「タイポを修正するプルリク出すときにちょっとしょんぼりしちゃうけど」

🔗 rubygemの最小バージョンを3.3.13以上にした

動機/背景
このプルリクはrequired_rubygems_versionを3.3.13以上に上げる。理由は#45979rubygems/rubygems#5486に依存しているため(CHANGELOG.md#3313--2022-05-04に記載されている)。

詳細

  • RubyGems 3.3.12 Gem.ruby_versionにはパッチレベルが含まれている
$ gem -v
3.3.12
$ irb
irb(main):001:0> Gem.ruby_version
=> Gem::Version.new("3.1.3.p185")
  • RubyGems 3.3.13 Gem.ruby_versionにはパッチレベルが含まれていない
$ gem -v
3.3.13
$ irb
rb(main):001:0> Gem.ruby_version
=> Gem::Version.new("3.1.3")
irb(main):002:0>

追加情報
Ruby 1.9.3-p0(#4576)のときに、カレントバージョン1.8.11が更新された。
同PRより


つっつきボイス:「Railsでrubygemの最小バージョンが上げられた」「rubygemはgemコマンドを提供しているgemですね」「Railsのrubygemの最小バージョンはRuby 1.9.3-p0のときからずっと1.8.11のままだったのか↓」「そんなに長い間だったとは」

# rails.gemspec#L12
  s.required_ruby_version     = ">= 2.7.0"
- s.required_rubygems_version = ">= 1.8.11"
+ s.required_rubygems_version = ">= 3.3.13"

rubygems/rubygems - GitHub


Ruby | endoflife.dateより

🔗 assert_raisesアサーションに例外メッセージとマッチさせるオプションが追加された

以下のように書く代わりに

error = assert_raises(ArgumentError) do
  perform_service(param: 'exception')
end
assert_match(/incorrect param/i, error.message)

以下のように書ける。

assert_raises(ArgumentError, match: /incorrect param/i) do
  perform_service(param: 'exception')
end

fatkodima
同Changelogより


つっつきボイス:「assert_raisesmatch: /incorrect param/iのようにエラーメッセージもチェックできるようになったんですね: assert_raisesはエラーオブジェクトの種別をチェックするものだけど、エラーオブジェクトを使い分けずに共通のエラークラスでエラーを表示している人にはこうやって短く書けるのは嬉しいのかも」

参考: Rails Edge API assert_raises -- ActiveSupport::Testing::Assertions

「以前はエラーの種別ごとにエラーのクラスを増やすのが何となく億劫だったこともあるんですが、本来であればエラーの種別ごとにエラークラスを定義するべきなので最近はそうするようにしてます」「エラークラスをあまり細かい粒度で増やしたくない気持ち、ちょっとわかります」「名前空間を使うことにはなりますし」「いったんそうすると、今後もエラークラスを際限なく作らなければいけないんだろうかという気持ちになったりする」「わかる」

「たぶんエラーオブジェクトの名前をたくさん考えるのがつらいからなのかも」「たしかに」「名前を考えるのは面倒だけど、かといってStandardErrorだけで処理するのはさすがによくないので、ValidationErrorみたいなクラスを作って、複数の似たようなエラーについては同じエラークラスだけどメッセージの内容が異なるというエラーオブジェクトをraiseさせたりすることがありますね」

class CreateUserAccountService
  def perform(account_creation_params)
  end
end

「たとえば上のようなクラスがあるときに、以下みたいなのを考えているとつらいので、」

class CreateUserAccountService
  def perform(account_creation_params)
    # コード
    raise OAuthTokenError.new
    # コード
  end
  class OAuthTokenError < StandardError; end
  class OAuthTokenExpiredError < StandardError; end
  class OAuthDoesNotHaveRequiredScopesError < StandardError; end
  # ...
end

「以下みたいに複数のエラーをある程度抽象化してまとめることがあったりしますね」「なるほど」

class CreateUserAccountService
  def perform(account_creation_params)
    # コード
    rescue => e
      raise OAuthIntegrationError.new(e.message)
    end
    # コード
  end
  class OAuthIntegrationError < StandardError; end
  # ...
end

「そういう感じでやっている人には、今回のassert_raisesの改修はありがたいかもしれませんね」「なるほど」

参考: class StandardError (Ruby 3.2 リファレンスマニュアル)

🔗 rails db:prepareinternal_metadataに誤った環境が保存されていたのを修正

修正対象: #46845

スペースの変更を除いた差分

#46845のコメントでは以下のような変更を提案した。

--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1427,7 +1427,7 @@ def migrate_without_lock
       def record_environment
         return if down?

-        @internal_metadata[:environment] = connection.migration_context.current_environment
+        @internal_metadata[:environment] = connection.pool.db_config.env_name
       end

       def ran?(migration)

しかし、database.ymlのdb_config.env_nameが環境変数(トップレベルキー)なのでうまくいかなかった。rakeタスクは基本的にRAILS_ENVの保存を試みるが、これとは別物なのだろうか?以下を始めとしていくつものテストが失敗した。

# rails/activerecord/test/cases/migration_test.rb#691 to 713 in c70a8f7
# Lines 691 to 713 in c70a8f7

 def test_internal_metadata_stores_environment 
   current_env     = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call 
   migrations_path = MIGRATIONS_ROOT + "/valid" 
   migrator = ActiveRecord::MigrationContext.new(migrations_path, @schema_migration, @internal_metadata) 
  
   migrator.up 
   assert_equal current_env, @internal_metadata[:environment] 
  
   original_rails_env  = ENV["RAILS_ENV"] 
   original_rack_env   = ENV["RACK_ENV"] 
   ENV["RAILS_ENV"]    = ENV["RACK_ENV"] = "foofoo" 
   new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call 
  
   assert_not_equal current_env, new_env 
  
   sleep 1 # mysql by default does not store fractional seconds in the database 
   migrator.up 
   assert_equal new_env, @internal_metadata[:environment] 
 ensure 
   ENV["RAILS_ENV"] = original_rails_env 
   ENV["RACK_ENV"]  = original_rack_env 
   migrator.up 
 end 

そこで、#46845のコメントの元の提案に立ち返って、Railsアプリケーションの存在に依存しない形で少し拡張した。
エレガントではないが、動くようになった。
cc @eileencodes
同PRより


つっつきボイス:「rails db:prepareで環境変数を取得するときに、マイグレーションのコンテキストを参照してバグったらしい」「test環境なのにdevelopment環境として保存されていたとは」「マルチプルデータベース環境だとデータベースごとにマイグレーションのコンテキストが変わることになると思うので、そのあたりに関連していそうな気がする」

🔗 設定されていないHostAuthorizationミドルウェアを使わないようになった

動機/背景
従来は、config.hostsにエントリがあるかどうかにかかわらずHostAuthorizationミドルウェアが追加されていた。HostAuthorizationは計算コストの高い処理を行う前にconfig.hostsをチェックするが結局読み込まれてしまうので、本来なら回避可能なオーバーヘッドが全リクエストで発生するということになりやすい。

詳細
コンフィグがある場合にのみHostAuthorizationミドルウェアを利用する。

追加情報
ミドルウェア操作のdeprecation warningは必要だろうか?これについて考える前に、とりあえずこのプルリクをオープンしてどのぐらい関心が寄せられるかを様子見しようと思った。
同PRより


つっつきボイス:「HostAuthorizationはRackミドルウェアなんですね」「HTTP Hostヘッダーの値が指定のホスト名と一致するかどうかをチェックするミドルウェアでしたね: 今までは設定がなくてもミドルウェアが読み込まれていたけど、config.hostsを指定しない場合は使わないようにするのはいい👍」

参考: Host - HTTP | MDN
参考: § 3.4.1 ActionDispatch::HostAuthorization -- Rails アプリケーションを設定する - Railsガイド

Rails.application.config.hosts = [
  IPAddr.new("0.0.0.0/0"),        # すべてのIPv4アドレス
  IPAddr.new("::/0"),             # すべてのIPv6アドレス
  "localhost",                    # localhost予約済みドメイン
  ENV["RAILS_DEVELOPMENT_HOSTS"]  # 開発用の追加ホストリスト(カンマ区切り)
]

Rails アプリケーションを設定する - Railsガイドより

🔗 Rails.env.local?を最適化

動機/背景
09e0372を参照。

詳細
このプルリクは、#local?の振る舞いを、定義済みの環境述語メソッドと同じにする(こちらは事前計算済みのインスタンス変数を即座に返す)。

追加情報

ベンチマーク:

dev = ActiveSupport::EnvironmentInquirer.new("development")
test = ActiveSupport::EnvironmentInquirer.new("test")
prod = ActiveSupport::EnvironmentInquirer.new("production")

Benchmark.ips do |x|
  x.report("dev local?") { dev.local? }
  x.report("test local?") { test.local? }
  x.report("prod local?") { prod.local? }
  x.compare!
end

改修前:

Warming up --------------------------------------
          dev local?   676.645k i/100ms
         test local?   627.687k i/100ms
         prod local?   671.078k i/100ms
Calculating -------------------------------------
          dev local?      6.785M (± 1.8%) i/s -     34.509M in   5.087679s
         test local?      6.284M (± 2.0%) i/s -     32.012M in   5.096338s
         prod local?      6.759M (± 1.8%) i/s -     34.225M in   5.065240s

Comparison:
          dev local?:  6785134.0 i/s
         prod local?:  6759023.6 i/s - same-ish: difference falls within error
         test local?:  6283910.1 i/s - 1.08x  (± 0.00) slower

改修後:

Warming up --------------------------------------
          dev local?     1.076M i/100ms
         test local?     1.049M i/100ms
         prod local?     1.028M i/100ms
Calculating -------------------------------------
          dev local?     10.586M (± 2.3%) i/s -     53.799M in   5.084729s
         test local?     10.350M (± 2.5%) i/s -     52.457M in   5.071399s
         prod local?     10.396M (± 2.2%) i/s -     52.422M in   5.045193s

Comparison:
          dev local?: 10586400.1 i/s
         prod local?: 10395758.6 i/s - same-ish: difference falls within error
         test local?: 10350328.8 i/s - same-ish: difference falls within error

同PRより


つっつきボイス:「前回DHHが追加したRails.env.local?ウォッチ20230125)が改修されていました」「これでRails.env.local?が高速になる」「EnvironmentInquirerというクラスがあるとは知らなかった」「2019年からあるみたいですね」「ここでLOCAL_ENVIRONMENTSを設定しているのか、なるほど」

# activesupport/lib/active_support/environment_inquirer.rb#L6
module ActiveSupport
  class EnvironmentInquirer < StringInquirer # :nodoc:
    # Optimization for the three default environments, so this inquirer doesn't need to rely on
    # the slower delegation through method_missing that StringInquirer would normally entail.
    DEFAULT_ENVIRONMENTS = %w[ development test production ]
    # Environments that'll respond true for #local?
    LOCAL_ENVIRONMENTS = %w[ development test ]
    def initialize(env)
      raise(ArgumentError, "'local' is a reserved environment name") if env == "local"
      super(env)
      DEFAULT_ENVIRONMENTS.each do |default|
        instance_variable_set :"@#{default}", env == default
      end

+     @local = in? LOCAL_ENVIRONMENTS
    end

    DEFAULT_ENVIRONMENTS.each do |env|
      class_eval "def #{env}?; @#{env}; end"
    end

    # Returns true if we're in the development or test environment.
    def local?
-     in? LOCAL_ENVIRONMENTS
+     @local
    end
  end

なお、EnvironmentInquirerはAPIドキュメントサイトでは検索しても出てきません↓。

🔗Rails

🔗 RailsガイドのJavaScriptガイドがRails 7に合わせて更新


つっつきボイス:「英語版のJavaScriptガイドがRails 7用に全面的に更新されていたので、Railsガイドの方も合わせて更新しました」「お、これでimport mapやTurboの公式情報が読めるようになりますね🎉」「Ajaxという言葉がJavaScriptガイドから消えました」

Rails 7: importmap-rails gem README(翻訳)

翻訳後、訳文がCIでリンクエラーになったことで英語版のフォームヘルパーガイドに古い記述があることがわかったので、本家にプルリクを投げてマージしてもらいました↓

参考: [ci-skip] Remove obsolete paragraph from form_helpers.md Guide for Rails 7 by hachi8833 · Pull Request #47069 · rails/rails
参考: Action View フォームヘルパー - Railsガイド

🔗 Evil Martiansが使っているgem


つっつきボイス:「最近"私の好きなgem"的な英語記事にめぼしいものがあまりなかったんですが、Evil Martiansの新しい記事がちょっとよさそうだったのでピックアップしてみました」「gem名を見ればだいたいどんなことをするか見当はつくかな」「Evil Martiansのメンバーが作ったgemや、Evil Martiansがスポンサーになっているgemが目立ちますね」

「schkedの読み方がわからない」「rufus-schedulerのラッパーということはschedule daemonを短縮してもじった感じなのかも」「実行すれば単体でスケジューラが動くようですね: 単体で動かせる軽いスケジューラはありがたそう」

「cronを使うとcrondがない環境で動かせないんですよ、特にコンテナの時代はありがち」「たしかに」「rakeタスクでスケジューラを動かすタイプのツールもあったりするんですけど、rakeタスクだとrakeタスク自体の起動・読み込みでそれなりに時間がかかってしまったりするので、とても単純なことをしたいならRailsやrakeに依存しないスケジューラが欲しいと思うときがあります」

bibendi/schked - GitHub

jmettraux/rufus-scheduler - GitHub

# jmettraux/rufus-schedulerより
require 'rufus-scheduler'

scheduler = Rufus::Scheduler.new

# ...

scheduler.in '10d' do
  # do something in 10 days
end

scheduler.at '2030/12/12 23:30:00' do
  # do something at a given point in time
end

scheduler.every '3h' do
  # do something every 3 hours
end
scheduler.every '3h10m' do
  # do something every 3 hours and 10 minutes
end

scheduler.cron '5 0 * * *' do
  # do something every day, five minutes after midnight
  # (see "man 5 crontab" in your terminal)
end

# ...

参考: crontab - Wikipedia

「rufus-schedulerは★も多いし、schkedはRailsのジョブをさっと動かせるみたい: 使ってみようかな👍」


faktoryって何だろうと思ったらSidekiqと同じ会社が出しているんですね」「ほんとだ、Sidekiqとfaktoryが横に並んでる」


「ところで、記事のリンクでRails 7.1にCommon Table Expression(CTE)が入ることになっているのを思い出した」「そういえばそうでしたね」「CTEはぜひ使いたい機能」

参考: MySQL :: MySQL 8.0 Reference Manual :: 13.2.20 WITH (Common Table Expressions)


「postgresとかpgがつく名前のgemはわかりやすい」「お、scenicも使ってる」「以前データベースVIEWの記事で触れていたgemですね」

# 同記事より
gem 'activerecord-postgres_enum'
gem 'pg_search'
gem 'postgresql_cursor'

gem 'fx'
gem 'scenic'
# or
gem 'pg_trunk'

RDBMSのVIEWを使ってRailsのデータアクセスをいい感じにする【銀座Rails#10】


「action_policyは知らなかった」「pundit gemより手軽に使える認可(authorization)ライブラリらしい」「これもEvil Martians製ですね」「見た感じpunditと使い方は似てそうだけど、少なくとも命名はaction_policyの方がずっとわかりやすい」「たしかに」

参考: Action Policy: authorization framework for Ruby/Rails applications

varvet/pundit - GitHub

参考: よくわかる認証と認可 | DevelopersIO


「ViewComponentは最近Evil Martiansが大プッシュしていますね↓」

実践ViewComponent(1): 現代的なRailsフロントエンド構築の心得(翻訳)

実践ViewComponent(2): コンポーネントを徹底的に強化する(翻訳)

「アセット管理のvite_railsは、あのViteのことですよね」

# 同記事より
gem 'vite_rails'

panko_serializerは、昔からあるActiveModelSerializersと似ていてとても速いらしい」

# 同記事より
gem 'alba'
gem 'oj'
# or
gem 'panko_serializer'

「ログ関連でlogrageはよく使う」「yabedaって初めて見た」

# 同記事より
group :production do
  gem 'yabeda-sidekiq', require: false
  gem 'yabeda-puma-plugin', require: false

  gem 'lograge'
end

「dry-シリーズのgemもありますね」

gem 'anycable-rails'

gem 'feature_toggles'

gem 'redlock'

gem 'anyway_config'

gem 'retriable'

gem 'dry-initializer'
gem 'dry-monads'
gem 'dry-effects'

参考: dry-rb - Home

「思ったよりgemがたくさん紹介されてますね」「最近のgemの動向を知っておくorおさらいしておくのに良さそう👍」

🔗 Ruby APIでFormObjectパターンを使う(RubyFlowより)

# 同記事より
class UsersController < ApplicationController
  def create
    form = UserForm.new(user_params)
    if form.valid?
      user = User.create(form.attributes)
      render json: user, status: :created
    else
      render json: form.errors, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:user).permit(:name, :email, :password)
  end
end

つっつきボイス:「FormObjectは最近あまり見かけないかも」「記事は普通っぽいですが、最後にオチがあります↓」「あら、ChatGPTも使って書いた記事🤣」「ChatGPTはそれっぽい技術記事を生成できちゃいますよね」「基本は自分の経験ベースのようなのでコピペではなさそうかな」

This post was created based on my personal experience and research which I did using chat-gpt tool. So some content might not be 100% unique and is taken from other sources (which just proves the wild spread of the FormObject pattern, btw)
同記事より

参考: ChatGPT - Wikipedia

🔗 Action Mailerのよい書き方(Ruby Weeklyより)


つっつきボイス:「まずはメールのdisplay_nameを設定しようという話↓」「メールの表示名のことですね」

# 同記事より
ActionMailer::Base.email_address_with_name(user.email, user.display_name)
=> "Matt Swanson <matt@boringrails.com>"

「メールのビューのパスをprepend_view_pathで指定できるのか↓」

# 同記事より
class ApplicationMailer < ActionMailer::Base
  prepend_view_path "app/views/mailers"
end

「メール送信ごとにメーラーを作るか、1つのメーラーで複数の種類のメール送信を扱うかについては、記事にもあるように後者でいいと思います: ドメイン的に近い機能は一緒にする方がわかりやすいですね」「たしかに」「なおAction Mailerではmail(to: ...)の戻り値(Action Mailerのオブジェクト)をメソッドから返すのが大事: そうすることで、非同期ジョブで同じ送信を繰り返したときにインスタンスが異なるものになるので、うっかり同じインスタンスを触って壊れたりしなくなります」「なるほど」

# 同記事より
class CommentReplyMailer < ApplicationMailer
  layout "minimal"

  def comment_reply_email(user, comment)
    # mail(to: ...)
  end
end

class UserMentionedMailer < ApplicationMailer
  layout "minimal"

  def mentioned_email(mentionee, comment)
    # mail(to: ...)
  end
end
# 同記事より
class NotificationMailer < ApplicationMailer
  layout "minimal"

  def comment_reply(user, comment)
    # mail(to: ...)
  end

  def mentioned(mentionee, comment)
    # mail(to: ...)
  end
end

「Action Mailerは普通に使われる枯れた機能なんですが、なかなか新しい記事が出ないので令和の時代に書いてくれるのはありがたい👍」

参考: Action Mailer の基礎 - Railsガイド


前編は以上です。

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

週刊Railsウォッチ: 2022年のRails振り返り記事、RailsにDocker関連ファイルが追加ほか(20230125前編)

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h


CONTACT

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