- Ruby / Rails関連
週刊Railsウォッチ: フォームヘルパーの改修、Railsの監査ログgem比較、DHHとimport-mapほか(20211129前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
以下の公式情報から見繕いました。
- 公式更新情報: Automated shard swapping middleware, standardised error reporting interface and more! | Riding Rails
🔗 フォームヘルパーでurl: false
やaction: false
オプションが指定可能になった
- PR: [Support
<form>
elements without `action]` by seanpdoyle · Pull Request #42051 · rails/rails
以下を用いて
<form>
要素のレンダリングをaction
属性なしで行えるようになった。
form_with url: false
またはform_with ..., html: { action: false }
form_for ..., url: false
またはform_for ..., html: { action: false }
form_tag false
またはform_tag ..., action: false
button_to "...", false
またはbutton_to(false) { ... }
Sean Doyle
同Changelogより
つっつきボイス:「久しぶりにform_*
系ヘルパーに改修が入ったようです」「この改修ではRailsのフォームヘルパーでurl: false
やaction: false
を明示的に指定することでアクションなしを指定できるようになったんですね: 現在のRailsのデフォルトではaction
属性が必ず付与されるようになっていますが、HTMLの仕様ではaction
属性なしでフォームを生成すると現在のURLが生成されて自分自身へのアクションを参照するようになっています」「フォームを自分自身に投げるならaction
属性を書かなくてもいいんですね」
参考: HTML Standard -- 4.10.21.3 Form submission algorithm html.spec.whatwg.org
- If
action
is the empty string, let action be the URL of the form document.
html.spec.whatwg.orgより
「今までは以下のように<form method="get">
をアクションなしで作りたくてもform_with
やform_for
などではデフォルトでurl_for({})
が使われるので作る方法がなかったということみたい」「たしかに、今まではform_with
やform_for
を使うと必ずaction="/posts
みたいなアクションが<form>
タグに入ってたので、アクションなしのフォームもありとは知りませんでした」「この機能を自分で使う状況はあまり思いつかないけど、HTMLの仕様で許されていることができないとRailsの自由度が下がってしまうことになるので、できる方がいいでしょうね👍」
<form method="get">
<button name="sort" value="desc">Most to least</button>
<button name="sort" value="asc">Least to most</button>
</form>
🔗 button_to
でauthenticity_token:
オプションをサポート
- PR: button_to: Support `authenticity_token:` option by seanpdoyle · Pull Request #43417 · rails/rails
form_with
やform_for
の呼び出しでauthenticity_token:
オプションを渡せるようになった。
button_to "Create", Post.new, authenticity_token: false
# => <form class="button_to" method="post" action="/posts"><button type="submit">Create</button></form>
button_to "Create", Post.new, authenticity_token: true
# => <form class="button_to" method="post" action="/posts"><button type="submit">Create</button><input type="hidden" name="form_token" value="abc123..." autocomplete="off" /></form>
button_to "Create", Post.new, authenticity_token: "secret"
# => <form class="button_to" method="post" action="/posts"><button type="submit">Create</button><input type="hidden" name="form_token" value="secret" autocomplete="off" /></form>
同PRより
つっつきボイス:「authenticity_token:
オプションでtrue
やfalse
を渡したり、任意の文字列を渡したりできるようになった」「true
や文字列の場合は<input type="hidden">
の中にトークンが埋められるんですね」「このオプションがbutton_to
ヘルパーで使えるようになったので、フォームの中に含まれていない単体のボタンでもトークンが使えるようになる: これを使いたくなる気持ちはちょっとわかるかも」
🔗 field_name
ビューヘルパーが追加
field_name
ビューヘルパーを導入する。これはFormBuilder#field_name
に相当する。
form_for @post do |f|
f.field_tag :tag, name: f.field_name(:tag, multiple: true)
# => <input type="text" name="post[tag][]">
end
Sean Doyle
同PRより
つっつきボイス:「今回はフォーム周りの改修が多いですね」「お〜、フォームのフィールドでネステッドな連想配列形式のフィールド名を指定する公式の方法がついにできた🎉(シンボル渡しだとtag[hoge][]
のようにできなかった)」「しかもmultiple: true
にも対応しているのが偉い!」
# 同PRより
text_field_tag :post, :title, name: field_name(:post, :title, :subtitle)
# => <input type="text" name="post[title][subtitle]">
text_field_tag :post, :tag, name: field_name(:post, :tag, multiple: true)
# => <input type="text" name="post[tag][]">
form_for @post do |f|
f.field_tag :tag, name: f.field_name(:tag, multiple: true)
# => <input type="text" name="post[tag][]">
end
「今までは以下のような書き方↓しかなかったのがイケてないな〜と思いながら使っていましたけど、改修後はようやくname: field_name(:post, :title, :subtitle)
のようなRubyらしい方法でフィールド名を指定できるようになった」「今回のフォーム周りのプルリクを投げてくれたSean Doyleさんは自分たちがビューで欲しいものをわかってくれている感じで嬉しいです」「覚えてたら使おうっと」
# api.rubyonrails.orgより
text_field_tag 'name'
# => <input id="name" name="name" type="text" />
text_field_tag 'query', 'Enter your search query here'
# => <input id="query" name="query" type="text" value="Enter your search query here" />
text_field_tag 'search', nil, placeholder: 'Enter search term...'
# => <input id="search" name="search" placeholder="Enter search term..." type="text" />
text_field_tag 'request', nil, class: 'special_input'
# => <input class="special_input" id="request" name="request" type="text" />
text_field_tag 'address', '', size: 75
# => <input id="address" name="address" size="75" type="text" value="" />
text_field_tag 'zip', nil, maxlength: 5
# => <input id="zip" maxlength="5" name="zip" type="text" />
text_field_tag 'payment_amount', '$0.00', disabled: true
# => <input disabled="disabled" id="payment_amount" name="payment_amount" type="text" value="$0.00" />
text_field_tag 'ip', '0.0.0.0', maxlength: 15, size: 20, class: "ip-input"
# => <input class="ip-input" id="ip" maxlength="15" name="ip" size="20" type="text" value="0.0.0.0" />
🔗 Rails標準のエラーレポートインターフェイスを追加
- PR: Rails standardized error reporting interface by casperisfine · Pull Request #43625 · rails/rails
修正: #43472
このレポーターは
Executor
にあるが、このRailsモジュールはもっと便利なRails.error
というショートカットを提供している。使いやすさのため、2個のブロックをベースとする専用メソッドを公開している。
handle
は、エラーを飲み込んでサブスクライバに転送する。
Rails.error.handle do
1 + '1' # raises TypeError
end
1 + 1 # これは実行される
record
は、エラーをサブスクライバに転送するが、コールスタックを巻き戻して継続させる。
Rails.error.record do
1 + '1' # raises TypeError
end
1 + 1 # ここは実行されない
ブロックベースのAPIに合わない場合は、低レベルの
report
メソッドを使える。
Rails.error.report(error, handled: true / false)
インターフェース
このプルリクではRails.error
のみを導入したが、後で「ローカル」エラーレポーターを導入してctive SupportやActive Recordなどのgemでもエラーをレポートできるようにしたい。現在のRails.logger
と少し似た感じで動くだろう。例:
ActiveSupport.error
はデフォルトではエラーをログ出力するだけのエラーレポーターだが、RailtieでこれをRails.error
に置き換える。
つっつきボイス:「Rails.error
というショートハンドでエラーレポートできるようにしたということのようですね」「ActiveSupport:: ErrorReporter
が追加されてる」
「考えてみればログ出力とエラーレポートは別に指定できる方がいいですね: 今までは自分でraise
してrescue_from
するとか、Slack通知などを各自が実装しますけど、こういうふうにRails公式のインターフェイスでエラーレポートを使えると便利でしょうね」「Rails.error
だとraise
とかを書かなくてよくなりそうですね」「そうそう」
「このActiveSupport::ErrorReporter
と機能が近いのはActiveSupport::Instrumentation
ですが、前者はエラーレポートに特化したインターフェースを持ち、後者は汎用的なpub/subで、subscriberがいなければスルーされるという点で使い分けが想定されてる感じかな」
参考: Active Support の Instrumentation 機能 - Railsガイド
「このインターフェイスを使って、airbrakeやsentryやrollbarといったエラーレポートツールも今後共通化できそうかなと思いました: それぞれの*-reporterみたいなgemやSlack-reporterライブラリなどをリリースしたりして」「そうなるといいですね」
🔗 jbuilderのコレクションレンダリングが高速化
つっつきボイス:「おぉ、コレクションのレンダリングが高速化されるのは嬉しい🎉」「cached: true
をオンにするとキャッシュが効くのね」「アプリケーションの性質次第では速くなりそう」
「でもこれよく見たらjbuilder gemの改修ですね」「jbuilder使ってない...」「私も...」
🔗 jb gemは優秀
「自分は最近jbuilderの代わりにこのjbというgemを使ってます↓」「amatsudaさんのgemだ」
# amatsuda/jbより
# app/views/messages/show.json.jb
json = {
content: format_content(@message.content),
created_at: @message.created_at,
updated_at: @message.updated_at,
author: {
name: @message.creator.name.familiar,
email_address: @message.creator.email_address_with_name,
url: url_for(@message.creator, format: :json)
}
}
if current_user.admin?
json[:visitors] = calculate_visitors(@message)
end
json[:comments] = @message.comments.map do |comment|
{
content: comment.content,
created_at: comment.created_at
}
end
json[:attachments] = @message.attachments.map do |attachment|
{
filename: attachment.filename,
url: url_for(attachment)
}
end
json
「jbだとRubyっぽく書けるのが嬉しいんですよ😂」「わかります、jbuilderの書き方はDSL的ですよね」「それそれ、jbuilderで思ったとおりのJSONやXMLを出力しようと思ったらDSLの書き方を覚えないといけないんですよ」「Response.json
とかxmlを組み立てていて細部の挙動が思うようにならないと、ERBで書く方がましという気持ちになったりしますよね」
参考: Response.json\()
- Web API | MDN
「jbだとRubyで書いたとおりにJSONが出力されるのでホント楽」「ちょっと大きいけど、active_model_serializers gemも比較的そういう感じで書けるところが好き」「キャッシュみたいなものはDSLに任せたいけど、JSONみたいなものはRubyらしいシンプルな方法でビルドしたい気持ちです」
🔗Rails
🔗 rails_multisite: Discourseから切り出されたマルチテナントgem(Ruby Weeklyより)
つっつきボイス:「マルチテナント系のgemはいろいろありますけど、これはDBも含めて完全に切り分けるマルチサイトがやれるライブラリのようですね」
# 同リポジトリより
mlp:
adapter: postgresql
database: discourse_mlp
username: discourse_mlp
password: applejack
host: dbhost
pool: 5
timeout: 5000
host_names:
- discourse.equestria.com
- discourse.equestria.internal
drwho:
adapter: postgresql
database: discourse_who
username: discourse_who
password: "Up the time stream without a TARDIS"
host: dbhost
pool: 5
timeout: 5000
host_names:
- discuss.tardis.gallifrey
「そこまで分けるなら別アプリにするかなとも思いますが、Discourseでそういう需要があるのは何となく想像できる」「いずれ別アプリに分けたいというリクエストが来るかもしれませんね」「レンタルサーバーでよくある共有サーバープランから専用サーバープランに移行するような感じでアプリを切り離せるんじゃないかな」
🔗 Railsの監査ログgemを比較する(RubyFlowより)
つっつきボイス:「監査ログ機能のgemを比較する記事だそうです」「知っているのや知らないのや、いろんなのがありますね」「記事の末尾で使い分けが書かれていました」「自分はpaper_trailを使ってます」「監査ログ機能は常に何らかの形で求められる機能なので、こういうふうに定期的にまとめてくれるのはいいですね👍」
🔗 ログの置き場所
「ただ、監査ログについてはなるべくCloudWatch↓のような外部サービスにおまかせしたい気持ちがあります」「たしかに」
参考: Amazon CloudWatch(リソースとアプリケーションの監視と管理)| AWS
「後でログを調べたりするだけならSQLクエリでさっと取り出せるのは一見便利ですが、アプリのデータベースに監査ログを置くとものすごく量が増える可能性があるのと、作業者がセンシティブなデータを不用意にSELECT *
してしまう可能性があるんですよ」「そうそう、データベースのインスタンスは別にしておきたいです」「直近のログだけならまだしも、少なくとも永続化するログを同じデータベースに置くのは避けたい」「古くなったログをexpireするのも忘れないようにしないと」
「ちなみにPostgreSQLやBigTableだと自動的にテーブルをパーティショニングするオプションがありますけどね」「MySQL派ですけど、ぽすぐれにそんな機能もあるんですか」「ググってみるとMySQLにもありますね」
参考: PostgreSQLドキュメント 5.11. テーブルのパーティショニング
参考: BigTable - Wikipedia
参考: MySQL :: MySQL 5.6 リファレンスマニュアル :: 19.6 パーティショニングの制約と制限
「なおlogidzeは翻訳記事↓でも取り上げたgemですが、ウクライナ語なので読み方をいつも忘れてしまいます😅」
Rails: Logidze gemでActive Record背後のPostgreSQL DB更新をトラッキング(翻訳)
🔗 Dry-monadsで「Railway指向プログラミング」設計(RubyFlowより)
つっつきボイス:「以前取り上げたRailway指向プログラミング(ROP)の図↓がこの記事に出てきたので拾ってみました(ウォッチ20200302)」
「コードを見た感じでは特に変わったことをしているわけではなさそうかな↓」
# 同記事より
def deliver_car(year, model, color, city)
yield check_year(year)
yield check_model(model)
yield check_city(city)
yield check_color(color)
Success("A #{color} #{year} Toyota #{model} will be delivered to #{city}")
end
def check_year(year)
year < 2000 ? Failure("We have no cars manufactured in year #{year}") : Success('Cars of this year are available')
end
def check_model(model)
@available_models.include?(model) ? Success('Model available') : Failure('The model requested is unavailable')
end
def check_color(color)
@available_colors.include?(color) ? Success('This color is available') : Failure("Color #{color} is unavailable")
end
def check_city(city)
@nearby_cities.include?(city) ? Success("Car deliverable to #{city}") : Failure('Apologies, we cannot deliver to this city')
end
「Railway指向とは?」「まさに上の図のようにステップごとにポイント切り替え的に処理を進めるという考え方ですね: ここでは失敗部分の処理を共通化するのに使っているように見える」「おぉ?」「よくあるオブジェクト指向的な設計だと、失敗ごとに別々の例外を投げることでエラーオブジェクトも別々になったりしますけど、この記事では失敗時はすべてFailure
に流れる、つまり図で言うと赤い線路に流れることで共通化するということなんでしょうね」「あ、そういうことですか」
「この場合失敗の理由に応じたエラーオブジェクトは取れなくなりますが、処理はシンプルになりますね: 分岐がたくさんあって、失敗の理由に興味がない場合は、こういう設計にすることもあるでしょうね」「なるほど」「あくまで設計思想のひとつです」
「この記事はdry-monadsを使ってモナドっぽく書いているらしい」「dry-rbシリーズは地味にラインナップが増えていますね」「dry-rbシリーズはなかなかいいライブラリなので、この調子で広まって今後公式にも反映されたりしたらいいですよね」
🔗 エンジニア3年目の人に向けて
つっつきボイス:「koicさんの記事にもあるように、Railsエンジニア3年目ぐらいの人を対象にした題材で銀座Rails#39に登壇いただきました: とてもいい話❤️」「お〜、スライドを見た感じでもよき話なのが伝わってきます」「最初の頃はタスクを拾うだけで一杯になりますけど、3年目ぐらいになってくるともっといい設計にすることに目が向くようになりますよね」
「koicさんの話を聞いていて、こういう話は時代が移っても比較的変わりにくいなと思いましたね」「たしかに10年前もこういうことしていたなという感じはありますね」「技術の移り変わりは激しいけど、変わりにくい部分はやはりあると改めて実感します」
銀座Rails#39 にご参加頂いたご登壇者・スポンサー様・視聴者の皆さまありがとうございました。またぜひ来月12/17(金)の銀座Railsでお会いしましょう👍 https://t.co/KjbDLfAvPH
— 銀座Rails (@GinzaRails) November 19, 2021
🔗 DHHの『JavaScriptのバンドルとトランスパイルが不要なモダンWebアプリ』
"JavaScriptのバンドルとトランスパイルが不要なモダンWebアプリ" https://t.co/reRjs3cpnP
— DHH (@dhh) November 15, 2021
つっつきボイス:「いい記事👍」「ruby-jp Slackで『DHHが日本語で記事書いたのかと思った』と話題になってました」「ないない😆」
「DHHがimport-mapの普及に力を入れているのを見ていると、import-mapがすべてのブラウザで使えるようになって、Rails以外のところでもフロントエンジニアがimport-mapを使いまくる世界を目指しているのかなと思いましたね」「あ、そうかも」
「caniuse.comで言うと、一日も早くすべてのブラウザでimport-mapが緑色になって欲しいと願っているんじゃないかなと思っています↓」「なるほど、今はChromeとEdgeあたりは緑か」「今のEdgeのレンダリングエンジンはChromeと同じなので緑なのは当然ですが」「FirefoxとSafari、頑張ってくれ〜」
参考: Import maps | Can I use... Support tables for HTML5, CSS3, etc
「DHHの記事によるとes-modules-shims↓を使えば今のFirefoxやSafariでもimport-mapのほとんどの機能を使えるらしい」
「FirefoxとSafariでもimport-mapが使えるようになったらproductionで安心して使えますよね」「たぶんそれがDHHの目指す世界だと思っています」
前編は以上です。
バックナンバー(2021年度第4四半期)
週刊Railsウォッチ: Ruby Struct入門、書籍『進化的アーキテクチャ』、AWS Web問題集ほか(20211116後編)
- 20211115前編 Rails 7がRuby 3.1のClass#descendantsに対応、GitHub Issue風ファイルアップローダほか
- 20211110後編 JSON.parseの機能、Opal 1.3、async gem、Linuxコマンドチートシートほか
- 20211102後編 2021年度Rubyアソシエーション開発助成、Rails REST APIレベルで楽観的ロックほか
- 20211101前編 Rails 7アセットパイプライン解説記事、ロジックをapp/operatorsで整理ほか
- 20211026後編 YJITがRuby 3.1向けにマージ、ripperのドキュメント化、crontabの罠ほか
- 20211025前編 insert_allやupsert_allのタイムスタンプ自動更新、rails/contextsにロジックを置くほか
- 20211019後編 ruby/debugをChromeでリモートデバッグ、Rubyアプリの最適化ほか
- 20211018前編 Railsリポジトリで進行中のPropshaft、inverse_ofを自動推論ほか
- 20211012後編 Ruby 3.1にYJITマージのプロポーザル、Rubyのmagic historyメソッド、JSのPartytownほか
- 20211011前編 ServerTimingミドルウェア追加、paramsで数値キーを許可、Railsで多要素認証ほか
- 20211006後編 ruby/debug 1.2.0リリース、Railsにはthorが入っている、tendejitほか
- 20211004前編 Rails 7でbyebugがruby/debugに変更、GitHub Codespacesをサポートほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)