- Ruby / Rails関連
週刊Railsウォッチ: productionのforce_ssl=trueがデフォルトで有効に、rakeタスクをthorで書くほか(20230704前編)
こんにちは、hachi8833です。
お知らせ: 来週の週刊Railsウォッチはお休みをいただき、通常記事を公開します 🙇
🔗Rails: 先週の改修(Rails公式ニュースより)
🔗 production環境でforce_ssl=true
をデフォルトで有効にする
production環境では
force_ssl=true
をデフォルトで有効にするようになった。これはアプリへのアクセスをすべて強制的にSSL(TLS)経由にし、Strict-Transport-Security
(HSTS)やsecure cookieを使うようにする。
Justin Searls、Aaron Patterson、Guillermo Iguaran、Vinícius Bispo
同Changelogより
production環境にアプリをデプロイ後、認証済みトラフィックがセキュアでないHTTPで転送されていることに気づくまで数週間放置してしまったことがある。production環境では
config.force_ssl
がデフォルトでtrue
になるという誤った前提で新しいアプリを運用してしまった。この変更で皆の関心を集めて議論のきっかけとしたい。このオプションが導入されて以来、Let's Encryptの証明書によってWebの状況はずいぶん変わり、今やHTTPSはほとんどのホスティングサービスで必須要件となっている。新しいアプリでは
Strict-Transport-Security
をデフォルトで有効にしてもいい時期が来たと思う。
同PRより
参考: Strict-Transport-Security - HTTP | MDN
つっつきボイス:「今は無料で証明書を発行してくれるLet's Encryptもあるし、production環境をデフォルトでforce_ssl=true
するのはこれはこれでいいんじゃないかと思います」
参考: Let's Encrypt - フリーな SSL/TLS 証明書
「ちなみに、productionやstaging環境以外のローカル開発環境でも、使おうとしているAPIによってはHTTPSを有効にしないとアクセスできないものがあったりしますね」「OAuthコールバックとかは最近HTTPSじゃないと弾かれますね」
🔗 (PostgreSQL向け)マイグレーションでenumのリネーム、値の追加、値のリネームが可能になった
rename_enum
およびrename_enum_value
はリバース可能である。add_enum_value
はPostgreSQLの制約(enum値を削除できない)によりリバースできない。代替手段として、enum全体を削除してから再作成すること。rename_enum :article_status, to: :article_state
add_enum_value :article_state, "archived" # will be at the end of existing values add_enum_value :article_state, "in review", before: "published" add_enum_value :article_state, "approved", after: "in review"
rename_enum_value :article_state, from: "archived", to: "deleted"
Ray Faddis
同Changelogより
つっつきボイス:「お〜、ぽすぐれでenumのリネームやenum値の追加/リネームができるようになった🎉」「2022年4月からのプルリクなんですね」「そういえば#44898は以下の記事でも前からマージが望まれていました↓」
「PostgreSQLにはALTER TYPE
というものがあるのか↓」
# activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L553
+ # Rename an existing enum type to something else.
+ def rename_enum(name, options = {})
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
+
+ exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
+ end
参考: PostgreSQL 15.0ドキュメント: ALTER TYPE
「add_enum_value
がリバースできないのは仕様上仕方がない」「ここで言うリバースは、マイグレーションのup
やdown
に書かなくてもchange
に書けばロールバック可能になるということですね」「そうそう」
参考: §3.9 changeメソッドを使う -- Active Record マイグレーション - Railsガイド
🔗 複合主キー関連
🔗 複合主キーの逆方向関連付けの設定を修正
動機/背景
複合主キーの関連付けでレコードに外部キーが存在するかどうかをチェックするには、外部キーのすべての部分が存在しているかどうかのチェックが必要になる。さもないと逆方向の関連付けが設定されない。詳細
#foreign_key_for?
を決定する外部キーのすべての部分をチェックするようになる。追加情報
より詳細なテストをinverse_associations_test.rb
に追加して、子の複合主キー関連付けが適切に読み込まれるかどうかを検証している。また、より一般的なテストをbelongs_to_associations_test.rb
に追加して、逆方向の関連付けが設定された場合にbelongs_to
関連付けが正しく保存されるようにしている。
同PRより
つっつきボイス:「今回の複合主キー関連改修は2つとも修正でした」「複合主キーの場合は外部キーにすべてのキーが揃っていなければ正常に動かなくなりますね↓: 複合主キーの改修が進むにつれて、こういうふうに修正の必要な部分が見えてきた感じ」
# activerecord/lib/active_record/associations/association.rb#L338
def foreign_key_for?(record)
- record._has_attribute?(reflection.foreign_key)
+ foreign_key = Array(reflection.foreign_key)
+ foreign_key.all? { |key| record._has_attribute?(key) }
end
🔗 複合主キーモデルを指すhas_many through:
関連付けのdestroy_all
を修正
このプルリクは、「複合主キーがあるモデルを指すが、関連付け自身は参照のビルドにidカラムだけを使う」
has_many through:
関連付けにおけるdestroy_all
の振る舞いを修正する。
primary_key: :id
という関連付けの定義は「このid
カラムは外部主キーとして使うこと」という意味であり、そのためRailsはpublic_send
でこのカラムの値にアクセスすることは避けるべき(そうすると特殊な意味を持つid
メソッドが呼び出されてしまうため)。このメソッドは識別子すなわち「主キーカラムの背後にある値」を指すが、複合主キーの文脈ではid
カラムと同じものではないので、代わりに_read_attribute
で直接id
カラムの値を取得する。
同PRより
つっつきボイス:「これも複合主キーを正しく参照していなかった箇所がまだ残っていたので修正したということですね」「public_send
で呼び出されると違うid
メソッドにアクセスしてしまうので、privateの_read_attribute
メソッドで取り出すように変更したんですね」
# activerecord/lib/active_record/associations/through_association.rb#L57
def construct_join_attributes(*records)
ensure_mutable
association_primary_key = source_reflection.association_primary_key(reflection.klass)
if Array(association_primary_key) == reflection.klass.composite_query_constraints_list && !options[:source_type]
join_attributes = { source_reflection.name => records }
else
- join_attributes = {
- source_reflection.foreign_key => records.map(&association_primary_key.to_sym)
- }
+ assoc_pk_values = records.map { |record| record._read_attribute(association_primary_key) }
+ join_attributes = { source_reflection.foreign_key => assoc_pk_values }
end
if options[:source_type]
join_attributes[source_reflection.foreign_type] = [ options[:source_type] ]
end
if records.count == 1
join_attributes.transform_values!(&:first)
else
join_attributes
end
end
参考: Object#public_send
(Ruby 3.2 リファレンスマニュアル)
🔗 secrets関連
🔗 Rails.application.secrets
呼び出しの非推奨化
Railsの
secrets
は#47801で非推奨化されてcredentials
に置き換わった。Rails.application.secrets
の呼び出しは非推奨警告を表示すべき。
そのために、最も深い場所にあるRails.application.secrets
への呼び出しを削除する#48470が必要。
同PRより
つっつきボイス:「これは次の#48470と連携しているそうです」「既に#47801でbin/rails secrets:setup
を実行しても無効になってたのね↓」
bin/rails secrets:setup
はRails 5.2以降非推奨化されていて、実行するとコマンドは実行されずに非推奨化警告を表示する。そろそろ全部削除しても安全だろう。
secrets:setup
が非推奨化されてから随分経ったので、secrets:show
コマンドとsecrets:edit
コマンドも非推奨化してもよさそう。
Remove deprecatedsecrets:setup
and deprecatesecrets:edit/show
by p8 · Pull Request #47801 · rails/railsより
「そういえばRailsがcredentialに切り替わったのが5.2だったからだいぶ経ちますね↓」
参考: §2.4 credential管理 -- Ruby on Rails 5.2 リリースノート - Railsガイド
config/credentials.yml.enc
ファイルが追加され、productionアプリケーションの秘密情報(secret)をここに保存できるようになりました。これによって、外部サービスのあらゆる認証credentialを、config/master.key
ファイルまたはRAILS_MASTER_KEY
環境変数にあるキーで暗号化した形で直接リポジトリに保存できます。Rails.application.secrets
やRails 5.1で導入された暗号化済み秘密情報は、最終的にこれによって置き換えられます。 さらに、Rails 5.2ではcredentialを支えるAPIが用意され、その他の暗号化済み設定/キー/ファイルも簡単に扱えます。 詳しくは、Rails セキュリティガイドを参照してください。
§2.4 credential管理 -- Ruby on Rails 5.2 リリースノート - Railsガイドより
🔗 ローカル環境のsecret_key_base
をRails.config
に保存する
Railsの
secrets
が非推奨化されてcredentials
に置き換わった。
しかしローカル環境ではsecret_key_base
の保存に引き続きRails.application.secrets
が使われている。
そこで、代わりにRails.config.secret_key_base
にsecret_key_base
を保存する。
この変更によってRails.application.secrets
の非推奨化が可能になる。#48472を参照。
同PRより
つっつきボイス:「この#48470が上の#48472より先にマージされたそうです」「プルリクに書かれているようにsecret_key_base
は今のローカルではRails.application.secrets
に保存されるんですが、これを非推奨化するためにローカル用の保存場所を変更しておく必要があったということですね」
「ちなみにsecret_key_base
が設定されていないとRailsを起動できません: Dockerコンテナで使うときは基本的にsecret_key_base
を環境変数で指定します」
参考: §3.2.33 config.secret_key_base
-- Rails アプリケーションを設定する - Railsガイド
参考: § 10.1 独自のcredential -- Rails セキュリティガイド - Railsガイド
🔗 キャッシュの:message_pack
を:coder
オプションで指定する方法に変更する
#48104で、キャッシュフォーマットのバージョンでサポートされる値として
:message_pack
が追加された(フォーマットのバージョンを指定するということは本質的にデフォルトのコーダーを指定することになる)。
しかしAPIとしては、シリアライズの他の側面に影響を与える目的でフォーマットのバージョンを指定する可能性がありうる。
このコミットは、フォーマットバージョンでサポートされる値としての:message_pack
を削除し、代わりにcoder: :message_pack:
による指定を追加する。# 改修前 config.active_support.cache_format_version = :message_pack # 改修後 config.cache_store = :redis_cache_store, { coder: :message_pack }
同PRより
つっつきボイス:「MessagePack
が追加されて以来改修が重ねられていますけど(ウォッチ20230502、ウォッチ20230530など)、コンフィグでの指定方法が変わったんですね: たしかにキャッシュフォーマットを指定するときはコーダーも同時に指定する方がわかりやすい」「Changelogも変更されていますね」
🔗Rails
🔗 Railsタスクをrakeではなくthorで書くコツ(Ruby Weeklyより)
つっつきボイス:「rakeの代わりにthor gem↓を使ってタスクを書こうという記事はときどき見かけますね」「仕方がないとはいえ、rakeタスクは使いにくいんですよ: 記事にもあるように、rakeは引数の渡し方がいろいろ面倒で、気をつけないと引数がシェルで誤って解釈されてしまったりする」「rakeタスクはテストも面倒と書かれていますね」
# 同記事より: thorでタスクを書く例
class UserTasks < Thor:
desc "create EMAIL", "Create a User record in the database identified by EMAIL"
def create(email)
# TODO
end
end
🔗 Railsコンソールの便利技集(Ruby Weeklyより)
つっつきボイス:「ざっと見た限りでは、Railsコンソールの深掘りというよりはtipsをまとめた感じかな」「ヘルパーメソッドの呼び出し↓は、コンソールをどこで実行するかにもよりますね: ビューのコンテキストなら当然ヘルパーメソッドを呼び出せる」
# 同記事より
irb(main):001:0> helper.number_to_currency(123)
=> "$123.00"
irb(main):002:0> helper.number_to_currency('123', precision: 0)
=> "$123"
irb(main):004:0> helper.number_to_phone('5555555555')
=> "555-555-5555"
irb(main):005:0> helper.number_to_phone('5555555555', area_code: true)
=> "(555) 555-5555"
irb(main):006:0> helper.number_to_phone('5555555555', country_code: 1)
=> "+1-555-555-5555"
irb(main):007:0> helper.number_to_phone('5555555555a', raise: true)
/Users/cody/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionview-7.0.5/lib/action_view/helpers/number_helper.rb:453:in `parse_float': ActionView::Helpers::NumberHelper::InvalidNumberError (ActionView::Helpers::NumberHelper::InvalidNumberError)
「逆にi18nは以下のようにI18n
で直接呼び出せる↓」
# 同記事より
irb(main):001:0> I18n.t('users.agreements.show')
=> {:title>"%{agreement} Changed", :last_updated=>"Last updated %{date}", :description=>"Before you can proceed, you must read and accept the new %{agreement}.", :accept=>"I Accept", :decline=>"I Decline"}
irb(main):001:0> I18n.t('are_you_sure')
=> "Are you sure?"
「_
変数で直前の結果を取り出すのも定番↓」「IRBではないけど、Railsコンソール(bin/rails c
)で一番使うのはデータベースをリードオンリーにする--sandbox
オプションかな、特にproductionで」「自分も使います」
User.first
=> User stuff
user = _
=> <User ...>
「app
でルーティングを参照できるのね↓」「自分はあまり使ってなかったけど、source_location
も使えますね」
# 同記事より
irb(main):001:0> app.new_billing_address_path
=> "/billing_address/new"
irb(main):002:0> app.root_path
=> "/"
# 同記事より
irb(main):001:0> User.instance_method(:confirm).source_location
=> ["/Users/cody/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/devise-4.8.1/lib/devise/models/confirmable.rb", 79]
🔗 message_bus: RubyアプリやRackアプリで使えるメッセージングバス(Ruby Weeklyより)
つっつきボイス:「サンプルコード↓を見ると、サーバーtoサーバーのメッセージングバスを単体で構築できるライブラリのようですね: publish
とsubscribe
があるところからしてpub/sub的に使えるみたい」
# 同リポジトリより
message_id = MessageBus.publish "/channel", "message"
# in another process / spot
MessageBus.subscribe "/channel" do |msg|
# block called in a background thread when message is received
end
# subscribe to channel and receive the entire backlog
MessageBus.subscribe "/channel", 0 do |msg|
# block called in a background thread when message is received
end
# subscribe to channel and receive the backlog starting at message 6
MessageBus.subscribe "/channel", 5 do |msg|
# block called in a background thread when message is received
end
参考: 出版-購読型モデル - Wikipedia -- pub/sub
「RailsやSinatraなどのRackアプリケーションでも使えるのでRailsアプリにも相乗り可能なのね」「JavaScriptライブラリも入っているのでJavaScriptからでもメッセージングバスを使えるのか: Railsアプリを作るほどでもないようなときにメッセージングバスを構築するのに便利そう👍」
# 同リポジトリより: Railsのミドルウェアスタックに組み込む場合
# config/initializers/message_bus.rb
Rails.application.config do |config|
# do anything you wish with config.middleware here
end
参考: Message Bus - Enterprise Integration Patterns
「このmessage_busは既存の機能だとどれに近いんでしょうか?」「既存のものだと、相互通信できるAction Cableみたいな感じかな: 要するに相互に共有できるバス(bus)を経由して通信する方法」「いわゆるバス接続のトポロジーなんですね」
参考: Action Cable の概要 - Railsガイド
参: バス (コンピュータ) - Wikipedia
🔗 eyeloupe: Railsのエラー画面をAIアシスタントで強化(Ruby Weeklyより)
つっつきボイス:「eyeloupeっていわゆるルーペ(拡大鏡)のことか」「READMEの動画↓を見た感じではAirbrakeとかSentryあたりをちょっと思い出すけど、例外もハンドリングしたりするらしいので、どちらかというとエラー画面を強化するライブラリのようですね」「そこにAIアシスタント機能も搭載しているんですね」「mount Eyeloupe::Engine => "/eyeloupe"
でエンジンをマウントすると/eyeloupe
でアクセスできるのね: ちょっと面白そうなので★追加してみた👍」
参考: Rails | Sentry Documentation
eyeloupeはLaravel Telescopeにインスパイアされて作ったそうです↓。
参考: Laravel Telescope - Laravel - The PHP Framework For Web Artisans
前編は以上です。
バックナンバー(2023年度第2四半期)
週刊Railsウォッチ: ruby_memcheckでネイティブgemのメモリリークを自動検出ほか(20230629後編)
- 20230628前編 複合主キーをスキーマから導出可能に、 DBアダプタの例外をconnection_poolに保存ほか
- 20230622後編 書籍『The Rails and Hotwire Codex』、JavaScript Primer改訂2版ほか
- 20230621前編 Action ViewのサニタイザがHTML Living Standard(旧HTML5)準拠にほか
- 20230614後編 RailsでApplication Layer Encryption、rubocop-factory_bot登場ほか
- 20230613前編 Arel::Nodes::Cteが追加、html_escape_onceの修正ほか
- 20230608後編 Jets v4リリース、頑張らない型導入、Rust言語からCrabがforkほか
- 20230607前編 MessagePackがcookieシリアライザとメッセージシリアライザにも導入ほか
- 20230531後編Rubyで環境変数を扱う、Web標準に「Baseline」ステータス追加ほか
- 20230525後編 Ruby 3.3.0-preview1リリース、in_order_ofのバグ修正ほか
- 20230524前編 withで作成したリレーションをjoinsで指定可能に、キャッシュストアの例外処理を統一ほか
- 20230502 スライド『Rails 7.1をn倍速くした話』、Rails 7.1でMessagePackをサポートほか
- 20230427後編 第1回Rails Worldが10月に開催、『研鑽Rubyプログラミング』でRuby本体も高速化ほか
- 20230425前編 Rails 7.1の複合主キー対応が引き続き進む、exceptメソッドにwithoutエイリアスが追加ほか
- 20230413後編 ShopifyのRubyパーサーyarp、RJITを書いた理由ほか
- 20230412前編 複合主キーの実装が進む、Rails公式のバグ再現用テンプレートほか
- 20230406後編 Rubyオブジェクトモデルクイズの最難問ほか
- 20230405前編 Arel::Nodes::NodeにAPIドキュメントが追加、rubocop-mdほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)