- Ruby / Rails関連
週刊Railsウォッチ(20201221前編)aws-sdk-rails gemの機能をチェック、RubyWorld Conference 2020のDHHインタビューほか
こんにちは、hachi8833です。約400年ぶりという木星と土星の超大接近は12/21(月)なので今夜ですね。と思ったらもう西の空に沈んでしまったようです。
参考: 【特集】2020年12月 木星と土星の超大接近 - アストロアーツ
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇
⚓Rails: 先週の改修(Rails公式ニュースより)
今回は以下のコミットリストのChangelogを中心に見繕いました。
つっつきボイス:「Rails 6.1がリリースされた直後なので、コミットリストも6.1修正が多そうですね」「今手掛けているRailsアプリも早速6.1にアップグレードしました」「お、どうでした?」「論理削除でおなじみのparanoia gemが動かなくなりました😇: それ以外は大丈夫っす」「gemが追いついてないのはよくありますね」
rc取れたらいっぱいリグレッション報告きとるやん😇
— Ryuta Kamizono (@kamipo) December 11, 2020
「そうなんですよね、リリースされてから使い始める自分💦」「rcが取れるまでに多くの人がお試しするのが本当は望ましいですよね」
⚓ translate
にnil
キーを渡したときの挙動を修正
つっつきボイス:「コミットメッセージにnil
がいっぱい書かれててややこしい...」「translate
にnil
を渡すとエラーになったのを修正したということのようだけど、default
オプションを効くようにもしたらしいとありますね」
I18n.translate
にnil
キーを渡すと、default
も同時に指定されていない場合はnil
を返す。default
も指定しておくと、nil
キーは「見つからないキー」として扱われる。Rails 6.0の
translate
ヘルパーは、nil
キーを渡すと常にnil
を返すが、#40773以後はtranslate
ヘルパーにnil
キーを渡すと常にI18n::ArgumentError
をraiseするようになった。このコミットは、default
を指定せずにI18n.translate
にnil
キーを渡すときの振る舞いと同じになるようtranslate
ヘルパーを修正する。
同コミットより大意
「テストコードとChangelogの方が見やすいかな↓: default:
オプションを指定しておけばnil
キーを渡してもdefault:
で渡したキーにフォールバックしてくれるようになったのね」「あ、そういうことですか」「例外を出されて止まっちゃうと困るので、これは必要な修正ですね」
# actionview/test/template/translation_helper_test.rb#80
def test_returns_nil_for_nil_key_without_default
assert_nil translate(nil)
end
def test_returns_default_for_nil_key_with_default
assert_equal "Foo", translate(nil, default: "Foo")
assert_equal "Foo", translate(nil, default: :"translations.foo")
assert_predicate translate(nil, default: :"translations.html"), :html_safe?
end
translate
ヘルパーにnil
キーを渡したときに、常にnil
を返すのではなく、default
で指定したキーに解決するようになった。
Changelogより
⚓ nonnamed expression indexを追加した後マイグレーションで元に戻せるように修正
つっつきボイス:「revertibleって何をrevertするんでしょう?」「Changelogには、ロールバックすると以下でエラーになったとあるけど...」
add_index(:items, "to_tsvector('english', description)")
「元のissueを見る方がいいかな↓」
「普通のデータベースインデックスではない、以下の"to_tsvector('english', description)",
{ using: :gin, name: 'index_items_on_to_tsvector_english_description' }
のような関数インデックスをマイグレーションで追加すると、その関数インデックス名が自動生成されていた場合はマイグレーションをロールバックしたときにエラーになるということか」「あ、そういうことですか」「ロールバックするときに自動生成済みインデックス名を解決できなくて落ちてたのを、ロールバックできるように修正したということのようですね」
- PostgreSQL 7.2.3 ユーザガイド: 関数インデックス
# #40732より
class AddFullTextSearchIndexToItemDescription < ActiveRecord::Migration[6.0]
# Note: this migration is irreversible, to revert it,
# please use the __Reversable__ block below and comment out the Irreversible block
# then rake db:rollback will work
def change
# __Reversable__
# add_index(
# :items,
# "to_tsvector('english', description)",
# { using: :gin, name: 'index_items_on_to_tsvector_english_description' }
# )
# Irreversible
add_index(
:items,
"to_tsvector('english', description)",
{ using: :gin }
)
end
end
「つまりrevertibleはマイグレーションをロールバックできるという意味なんですね」「この修正、超大事じゃないですか!」「知らずにロールバックしたら即死ですもんね😇」「こういうマイグレーションってあまり書かないだけに踏んだときがつらそう...」
⚓ Unreleased: 失敗したリクエストをconfig.exceptions_app
に渡すときのリクエストメソッドをGET
に変更
つっつきボイス:「これはChangelogのUnreleasedに書かれていたので、6.1には入っていないようです」「こっちの関連issueを見てみるかな↓」
「RailsガイドによるとActionController::UnknownHttpMethod
は自動的にrescueされてmethod_not_allowed
(HTTP 405)になるはずだったのにHTTP 500エラー(内部エラー)になってしまったのね」「ガイドと挙動が違ってた問題ですか」「RailsのRackミドルウェアの構成によっては期待どおりに動かないことがあったらしい」
参考: 3.9 Action Dispatchを設定する -- Rails アプリケーションを設定する - Railsガイド
「RailsのRackミドルウェアがUnknownHttpMethod
のエラーを食べてしまうと正しく405エラーを返せないことがあったので、内部的にはrequest methodをGETということにして処理を継続するようにしたようですね↓」「それだけだと元のリクエストの種類がわからなくなるので、それをoriginal_request_method
に保存したということか」
# actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L43
def render_exception(request, exception)
backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
status = wrapper.status_code
request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
request.set_header "action_dispatch.original_path", request.path_info
+ request.set_header "action_dispatch.original_request_method", request.raw_request_method
request.path_info = "/#{status}"
+ request.request_method = "GET"
response = @exceptions_app.call(request.env)
response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
rescue Exception => failsafe_error
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
FAIL
「GETじゃなくて405を返すのはダメでしょうか?」「ミドルウェアスタックの途中で処理を止めたくないでしょうし、未定義のリクエストメソッドが来る可能性もあるので、処理継続のために便宜上GETにしたんじゃないかな」
「UnknownHttpMethod
自体普通はあまりないエラーだと思います」
#40246で修正された#38998と似たような感じで、
ActionDispatch::Request
でメソッドが呼び出されると必ず行われるHTTPメソッドバリデーションで、予想外の妙な結果が生じることがある。たとえば、config.exceptions_app = self.routes
の場合、ActionDispatch::ShowExceptions
というミドルウェアフェイルセーフで例外が発生する。この冗長な例外を防ぐため、
env
がconfig.exceptions_app
に渡る前にrequest_method
を上書きした。オリジナルのリクエストメソッドは維持されるのでaction_dispatch.original_request_method
で維持されるので、インスペクトもできる。
同PRより
⚓ 6.1: attribute_for_database
メソッドを追加
ここからは少し趣向を変えて、@kamipoさんの記事より見繕いました。
とりいそぎ / “ツイッターで見つけて直したActiveRecordの問題3つ - かみぽわーる” https://t.co/CpX9syhp2t
— Ryuta Kamizono (@kamipo) December 3, 2020
つっつきボイス:「上のマージ自体は今年10月に行われていて、ウォッチでは取り上げてませんでした」「ああ、例のenum
のデータベース上の値を取れるようにした改修ですね」
enum state: {active: 0, inactive: 1}
とかした時に、typecast前の0とか1を取りたい
blog.kamipo.netより
なお、attribute_for_database
などのattribute
は属性名に置き換わるので、属性名_for_database
や属性名_before_type_cast
という感じの名前になります。
「attribute_for_database
メソッドはたしかに欲しい!」「attribute_before_type_cast
だとenumがtypecastしているかどうかを意識しながらになるので、それだと欲しいものとちょっと違うという気持ちもわかります」
参考: attribute_before_type_cast
-- ActiveRecord::AttributeMethods::BeforeTypeCast
「以下で言うと、book.status
はデータベースに2
が入っていて、そのデータベースの値を取ろうとすると、Rubyはbook.status
で"published"
を返すので、book.status_before_type_cast
を使ってデータベースから読み込み済みの2
を取っていた」「ふむふむ」「それがこの改修で、book.status_for_database
と書けばenumの"published"
に対応する2
をデータベースから読み込んで取れるようになった」「あ、なるほどわかりました」
# 同PRより
book = Book.new(status: "published")
# returns "published", but what we really want is 2.
book.status_before_type_cast
「attribute_before_type_cast
よりattribute_for_database
というメソッド名の方がデータベースから取ってくる操作なのがわかりやすいかも」「何かの都合でデータベースにあるenumの値を取りたいことはよくありますね」
⚓ 6.1: where
で関連付け名をjoinedテーブルのエイリアス名として参照できるようになった
belongs_to :author, class_name: 'User'
したときにleft_joins(:author).where("author.id": nil)
とか書きたい
blog.kamipo.netより
つっつきボイス:「こちらも今年8月にマージ済みでしたがウォッチで取り上げていなかったので」「そうそう、神速さんのツイートにもあるようにbelongs_to
で別名を使うと、joins
は別名で、where
は元のテーブル名を使わないといけなかったんですよね↓」
これすると、joins(:author).where(users: { id: nil }) みたいに書く必要もあったり、各所でオプションが必要になったりで、個人的にはあまり好きじゃない。
— 神速 (@sinsoku_listy) June 4, 2020
これやっぱり joins(:author).where(author: { id: nil }) か joins(:author).where(authors: { id: nil }) で書きたいですか?気持ちはわかるので昔からなんとかしたい気持ちはあります。
— Ryuta Kamizono (@kamipo) June 4, 2020
「それがこの修正によって、以下のテストコードのFirm.includes(:clients).where("clients.new_name": "Summit")
のようにドット付きの"clients.new_name"
で参照できるようになったのか」「ネステッドハッシュによるアクセスとは違うけど、"clients.new_name"
というドットアクセスをキーに書けるようになったんですね」
# activerecord/test/cases/associations/eager_test.rb#202
def test_type_cast_in_where_references_association_name
parent = comments(:greetings)
child = parent.children.create!(label: "child", body: "hi", post_id: parent.post_id)
comment = Comment.includes(:children).where("children.label": "child").last
assert_equal parent, comment
assert_equal [child], comment.children
end
def test_attribute_alias_in_where_references_association_name
firm = Firm.includes(:clients).where("clients.new_name": "Summit").last
assert_equal companies(:first_firm), firm
assert_equal [companies(:first_client)], firm.clients
end
「ツイートのコードで言うとauthor.何とか
みたいに書けるようになったということですか?」「ですです」「お〜これは便利そう!」「joinsがスッキリ書けてありがたい🙏」「ネステッドハッシュで書けるかどうかは少なくともこのテストには見当たらないですね」
あるテーブルが複数回joinsされると、それらのテーブルは最初の名前でない別名になる。
これは自己参照的な関連付けで起こりやすく、現在はその場合にwhere
条件の別名テーブルでカスタム属性(type casting)や属性エイリアス名の解決が効かない。
この問題を修正するために、where
で関連付け名をエイリアス名で参照できるようにする。関連付け名がwhere
で参照されると、それらの名前にjoinedテーブルのエイリアス名が使われる。
同PRより大意
# 同PRより
class Comment < ActiveRecord::Base
enum label: [:default, :child]
has_many :children, class_name: "Comment", foreign_key: :parent_id
end
# ... FROM comments LEFT OUTER JOIN comments children ON ... WHERE children.label = 1
Comment.includes(:children).where("children.label": "child")
なお@kamipoさん記事3つ目の#39830は以下の記事をどうぞ。
この後さらに@kamipoさん記事が出ていました。こちらもつっつきで少し見てみましたが記事では割愛します🙇。
⚓Rails
⚓ RubyWorld Conference 2020のキーノート: DHHインタビュー by Matz
#RubyWorld Conference 2020 is happening now! https://t.co/nPO4PJtCSF
— Samuel Williams (@ioquatix) December 17, 2020
補足すると、先日「Railsの未来」の記事を書いたんだけど、DHHが「Railsとフロントエンド」の話もしてくれたのでRailsの未来に興味ある方はぜひ聞いてほしい。
— 大倉雅史(OKURA Masafumi) (@okuramasafumi) December 17, 2020
つっつきボイス:「(2020/12/17 20:30頃)さっきRubyWorld Conference 2020がちょうど終わった頃だそうで、最後のキーノートであるMatzによるDHHインタビューが各所で評判になっていました↑」「お〜、DHHがRailsとフロントエンドの話もしたんだ!」「そこに関心のある人が多いからフロントエンドの話題は欠かせないでしょうね」「自分はRailsは当分滅びないと思っていますけど、フロントエンドをRailsでやる理由はだいぶ少なくなったとも思ってます」「今度動画見てみようっと」「誰か速攻で文字起こしとかしないかな」
なお、つっつきの時点では英語版動画にしか気づいていませんでしたが、以下の同時通訳付き動画でもキーノートインタビューを見られます(頭出し済み: 7:11:59)↓。
満を持してのDHH登壇だったそうです↓。
流石に毎年は依頼してなかったですが、富士スピードウェイに来た翌週にカンファレンス開催すれば何とか来てもらえるんじゃないかという話をしてた時もあったくらい待望の登壇でした #rubyworld https://t.co/iibNoTJLR3
— Shugo Maeda (@shugomaeda) December 17, 2020
以下はつっつき後に見つけたツイートです。
これだ、メタプログラミングRubyの序文か。https://t.co/VVccZruxa2
— Chikahiro Tokoro / kibitan (@ctokoro_me) December 17, 2020
⚓ RailsとCableReadyとStimulus ReflexでリアクティブなTwitterクローンを構築する
以下のツイートで知りました。
せせりさんのツイート見て出てきた動画、Rails+CableReady+StimulusReflexでJavaScript1行も書かずにTwitterクローン(ツイート、いいねの動的画面反映)してるのすごい。
Build a Twitter clone in 10 minutes with Rails, CableReady, and Stimulus... https://t.co/MyKsIdrYAB @YouTubeより
— 醤油瓶🍘@Rails再入門 (@seuyu_bin) December 15, 2020
つっつきボイス:「Stimulusといえば、11月の銀座RailsでもStimulus関連の発表がありました↓」「お、そうなんですね」
#ginzarails でStimulus Reflexを使ったライブコーディングをしました!緊張したけど時間内に終わったので良かったです。みんなでSR盛り上げられたらいいなー😆
— 大倉雅史(OKURA Masafumi) (@okuramasafumi) November 27, 2020
「Stimulusってつっつきで取り上げてましたっけ...よく知らなかった😅」「割と登場してたと思います(サイト内検索)」「StimulusはRailsでお馴染みのBasecampが作って使っているJavaScriptライブラリで、Rails方面の記事でちょくちょく見かけますね↓」「なるほど〜」「StimulusはRailsのオフィシャルに近い印象あります」「今のRails wayに則ってフルスタックでフロントエンドまでやるならStimulusとRailsの相性はいいでしょうね」
なお、Stimulusの2.0もリリースされたそうです。
⚓ @kamipoさんの記事より
これは知らないと一生ハマりそうだ...... -> “ロックを取る順番って見えづらいので問題に気づきづらいですよね” / “SELECT ... FOR UPDATE同士でデッドロックさせる - かみぽわーる” https://t.co/WS1bHpGcLf
— yancya (@yancya) December 15, 2020
つっつきボイス:「@kamipoさんのこの記事は唸りました: こうやって解説してもらわないとわからない世界」「ですよね」
「SELECT ... FOR UPDATEは割とよく使われるSQL構文で、デッドロックしないという都市伝説がまことしやかにあったんですが、実際はそんなことはなくてデッドロックは起きるというお話」「プライマリキーとセカンダリキーで並び順が変わるデータにしておくと、プライマリキーとセカンダリキーがクエリプランのSELECT ... FOR UPDATEでロックの一方が上から順、もう一方が下から順に同時に進んでいって、それでデッドロックになるというのを記事で再現してますね」「あ〜、そういうことですか!」「同じテーブルの中でのロックを取る順序というものをこれまで気にしたことがなかったことに気付かされました」「そんなことがあるとは...」
「ロックを取る順序が一意になるようにクエリやクエリプランを揃えるのが一般的な回避法だそうですが、そこまでチェックしないといけないのか」「@kamipoさんも『眼の良さを活かして気合いで対処』なんですね」「これはデータベース強者でないとなかなか気づけない問題」「データベース強者、なりたいです...」「取りあえずオラマスを目指すところからですかね↓」
参考: ORACLE MASTER Portal - be an ORACLE MASTER - | オラクル認定資格制度 | Oracle University
以下はつっつき後に見つけたツイートです。
わからないので PostgreSQL でも試してみました。普通にデッドロック発生しましたね...... WHERE の条件で使うカラムを揃えてやれば発生しなくなりました...... https://t.co/74qxz9ODN7
— yancya (@yancya) December 16, 2020
⚓ aws-sdk-rails gemの新機能(Ruby Weeklyより)
つっつきボイス:「aws-sdk-rails gemって知らなかった」「aws-sdk-ruby↓ではないんですね😆」「どちらもAWS公式のgemですけど、そういえばaws-sdk-rubyの方は以前のSDKがバージョンアップされたときがちょっと大変でしたね」「私も思い出しました」
参考: AWS SDK for Rubyの S3署名バージョン2廃止に対応しました [2019/6/24期限迫る!] - LCL Engineers' Blog
「どれどれ」「お、DynamoDBセッションストアなんて機能がある↓」
# aws.amazon.comより
rails generate dynamo_db:session_store_migration
参考: Amazon DynamoDB(マネージド NoSQL データベース)| AWS
「次はActive Support Notification Instrumentationサポート」「お〜、言われてみればRailsのInstrumentationで投げた通知をAWS X-Rayあたりで拾いたいことって割とありそうなので、このSDKでできるなら便利そう」「その分AWSにロックインすることになりますけどね」
参考: AWS X-Ray(分散アプリケーションの分析とデバッグ)| AWS
参考: Active Support の Instrumentation 機能 - Railsガイド
「これを見ると↓、ActiveSupport::Notifications
のsubscribe
でaws-sdk-railsのメソッドを拾うこともできる」「これは有能そう」「こういうのは欲しい機能ですね」
# 同リポジトリより
ActiveSupport::Notifications.subscribe('put_object.S3.aws') do |name, start, finish, id, payload|
# process event
end
# Or use a regex to subscribe to all service notifications
ActiveSupport::Notifications.subscribe(/S3[.]aws/) do |name, start, finish, id, payload|
# process event
end
「aws-sdk-railsのリポジトリも見てみると、Railsのencrypted credentialもサポートしてるのか↓」「お〜便利そう!」「SDKにロガーも付いているとは知らなかった」
# 同リポジトリより
# config/credentials.yml.enc
# viewable with: `rails credentials:edit`
aws:
access_key_id: YOUR_KEY_ID
secret_access_key: YOUR_ACCESS_KEY
参考: Rails5.2から追加された credentials.yml.enc のキホン - Qiita
「RailsのAction Mailerのdelivery_method
にAWS SES(Simple Email Service)を指定することもできる↓」「便利そうなものがいろいろ入ってますね」「aws-sdk-rails、あまり注目してなかったけどちょっと見直しました」
# 同リポジトリより
# for e.g.: config/environments/production.rb
config.action_mailer.delivery_method = :ses
参考: Action Mailer の基礎 - Railsガイド
参考: Amazon SES(高可用性で低価格なEメール送信サービス)| AWS
「Active JobのバックエンドでAWS SQS(Simple Queue Service)を使うこともできる↓」「これもよさそう😋」
# 同リポジトリより
# config/application.rb
module YourApp
class Application < Rails::Application
config.active_job.queue_adapter = :amazon_sqs # note: can use either :amazon or :amazon_sqs
# To use the non-blocking async adapter:
# config.active_job.queue_adapter = :amazon_sqs_async
end
end
# Or to set the adapter for a single job:
class YourJob < ApplicationJob
self.queue_adapter = :amazon_sqs
#....
end
「キューにSQSを使うだけかと思ったらワーカーをローカルでも動かせるのね↓」
RAILS_ENV=development bundle exec aws_sqs_active_job --queue default
参考: Active Job の基礎 - Railsガイド
参考: Amazon SQS(サーバーレスアプリのためのメッセージキューサービス)| AWS
「aws-sdk-rails、思ったよりよさそう👍」「こういうの見ると使いたくなっちゃいます」「最近やってるRailsアプリの環境がAWS ECSやAWS Fargateになっていることが増えてきて、ワーカーやキューをどうしようかと考えることが多いんですけど、aws-sdk-railsが使えるところがありそう」
参考: AWS Fargate(サーバーやクラスターの管理が不要なコンテナの使用)| AWS
「AWSが公式に提供しているのでサポート面でもありがたい🙏」「あとはSDKのアップグレードがaws-sdk-rubyのときほど大変にならなければさらに嬉しい」
前編は以上です。
バックナンバー(2020年度第4四半期)
週刊Railsウォッチ(20201216後編)Ruby 3.0.0-rc2とRuboCop 1.6がリリース、Ruby 3の静的型解析記事、CentOS 8のEOLが短縮ほか
- 20201214前編 Rails 6.1の直近コミットを見る、RuboCop Rails 2.9リリース、ar_lazy_preload gemほか
- 20201209後編 Ractorベンチマーク記事、Railsで複合主キーを使う、AWS re:Invent 2020ほか
- 20201208前編 レガシーRailsアプリを引き継ぐときの6つの作業、サーバーレスプロジェクトをRailsに移行ほか
- 20201202後編 Rails 6.1 RC2リリース、Ruby STMの詳細な解説記事、RSpecのdiffを見やすくするsuper_diff gemほか
- 20201201前編 switch_pointがActive Record 6.0でサポート終了、Rails DBトランザクションの落とし穴ほか
- 20201124 strict loading violationの振る舞いを変更可能に、Railsモデルのアンチパターン、quine-relayとさまざまなクワインほか
- 20201117後編 Rubyのパターンマッチングが3.0で本採用に、AWS Lambdaサイズを縮小する、AppleのM1チップほか
- 20201116前編 6.1のActive Storageでimage_processing gemが必須に、Webアプリ設計の変遷とフロントエンド領域の再定義ほか
- 20201111後編 RubyConf 2020が11/17〜19オンライン開催、GitHub Container Registryベータ開始、スマートロックほか
- 20201110前編 Rails 6.1 RC1がリリース、Railsアプリに最適なEC2インスタンスタイプ、n_plus_one_control gemほか
- 20201028後編 RuboCop 1.0.0 stable版がリリース、Ruby DSLのGUIフレームワークGlimmer、Keycloakほか
- 20201026前編 Shopifyのerb-lint gem、Form Objectを使いやすくするyaaf gem、railsrcの機能追加ほか
- 20201021後編 webpack 5リリースでWebpacker対応開始、AWS Lambda Extensions発表、Pythonにマクロ構文追加提案ほか
- 20201020前編 Percona Toolkitは優秀、Active Admin非公式ガイド、Railsをリアクティブにするガイドほか
- 20201013後編 ruby-type-profilerがtypeprofにリネーム、AWS API Gatewayの実行ログは便利、M5Stackほか
- 20201012前編 Railsの隠し機能routing visualizer、action_args gem、N+1用goldiloader gemほか
- 20201006後編 Rubyの
defined?
キーワード、Ractorベースのジョブスケジューラ、Caddy Webサーバーほか - 20201005前編 Ruby 2.7.2がリリース、Shopifyのモジュラー化gem「packwerk」、stimulus_reflexほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。