週刊Railsウォッチ(20191105前編)Rails 6のデフォルト設定解説、DHHも消したいaccepts_nested_attributes_for、スライド『実践Railsアプリケーション設計』ほか

こんにちは、hachi8833です。今年の3連休は昨日のでおしまいだそうです。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

お知らせ: 週刊Railsウォッチ「第16回公開つっつき会」(無料)

第16回目公開つっつき会は、来週11月14日(木)19:30〜にBPS会議スペースにて開催されます。今回は月初ではありませんのでご注意ください。

週刊Railsウォッチの記事にいち早く触れられるチャンス!発言・質問も自由です。引き続き皆さまのお気軽なご参加をお待ちしております🙇。

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

今週は平和に公式情報からです。

Active Storage blobのパーマネントURLを公開可能に

blob向けのパーマネントURL。
configurations.ymlでサービスのキーにpublic: true | falseを設定することで、サービスのblobをpublicまたはprivateにできる。publicなサービスは常にパーマネントURLを返すようになる。
Blob#service_urlは非推奨になり、Blob#urlが推奨される。
changelogより

# activestorage/test/service/s3_public_service_test.rb
+# frozen_string_literal: true
+
+require "service/shared_service_tests"
+require "net/http"
+require "database/setup"

+if SERVICE_CONFIGURATIONS[:s3]
+  class ActiveStorage::Service::S3PublicServiceTest < ActiveSupport::TestCase
+    SERVICE = ActiveStorage::Service.configure(:s3_public, +SERVICE_CONFIGURATIONS)
+
+    include ActiveStorage::Service::SharedServiceTests
+
+    test "public URL generation" do
+      url = @service.url(@key, filename: +ActiveStorage::Filename.new("avatar.png"))
+
+      assert_match(/.*\.s3\.amazonaws\.com\/.*\/#{@key}/, url)
+
+      response = Net::HTTP.get_response(URI(url))
+      assert_equal "200", response.code
+    end
+  end
+else
+  puts "Skipping S3 Public Service tests because no S3 configuration was supplied"
+end

つっつきボイス:「issue #31419の、Active StorageのService APIからファイルへのアクセスも許可したいという流れで入ったPRだそうです」「ほほう😋」「パーマネントURLって何を指してるのかな?🤔」「#31419にいいね👍が48個もついてるのでみんな欲しがってるっぽい😆」

ActiveStorage::Serviceの現時点のAPIではurlメソッドでしかリンクを取得できず、返されるpublic URLはほとんどのサービスでは同じタイムフレーム内で期限切れになる。サービスからファイルオブジェクトにもアクセスできるfileメソッド的なものがあれば、ファイルの公開方法をより柔軟にできて便利になると思われる。
#31419より大意

「#36729を見ると(ストレージ)プロバイダのpublic URLでは一般にファイル名をカスタマイズできないので、現状はpublic bucketとprivate bucketのディレクトリ構造が違ってしまっている: この修正ではconfigで設定すればS3やAzureやGCSで/キー/ファイル名の固定URLで統一的にアップロードして後でそのURLでダウンロードできるようにする、という感じのようです🤔」「まだActive Storageちゃんと使ってないけど、あるとうれしい機能らしいということはわかった😆」

has_manyのinverseが設定可能になった

この間取り上げた#34533の続きだそうです(ウォッチ20191021)が、#37413は取り上げてませんでした。

# activerecord/lib/active_record/railtie.rb#L29
    config.active_record.use_schema_cache_dump = true
    config.active_record.maintain_test_schema = true
+   config.active_record.has_many_inversing = false

つっつきボイス:「先週はhas_many関連が豊作でしたけど😆、#34533で入ったhas_manyのinverseを利用できるようにするかどうかを設定で選べるようになったそうです(デフォルトはfalse)」「has_manyでinverseできるのが本来だけどbreaking changeになるから設定を増やしたのね☺️」

inflectorが:zeitwerkモードでオーバーライド可能になった

# activesupport/lib/active_support/dependencies/zeitwerk_integration.rb#L56
      module Inflector
+       # Concurrent::Map is not needed. This is a private class, and overrides
+       # must be defined while the application boots.
+       @overrides = {}

-       def self.camelize(basename, _abspath)
+         basename.camelize
+         @overrides[basename] || basename.camelize
+       end
+
+       def self.inflect(overrides)
+         @overrides.merge!(overrides)
+       end
      end

つっつきボイス:「Active SupportのinflectorはRailsで名前の単数形複数形みたいな活用形(inflection)を制御するヤツですね☺️」「@overridesというハッシュがあればオーバーライドできると」「特殊な活用形をここに入れられる感じですね😋」「basenameがあればよし、なければcamelizeすると」

「Railsガイドの更新↓を見るとautoloaderでこうやって活用形を定義できるとありますね」「html_parserHTMLParserに変換するヤツわかる〜😆」「HtmlParserだと違う感ありますね😆」「SslErrorも違う感😆」「Htmlは現実に使われちゃってるところもあったりするのでワンチャンありな気もしなくもないけど😆」「HTMLは大文字にしたいです〜😭」

以下のようにすることでActive Supportの活用形がグローバルに効く。アプリケーションによってはこれでもよいが、Active Supportでデフォルトのinflectorにオーバーライドのコレクションを渡してbasenameを個別にcamerizeすることもできる。
ガイド更新分より大意

# guides/source/autoloading_and_reloading_constants.md#L289
# config/initializers/zeitwerk.rb
-inflector = Object.new
-def inflector.camelize(basename, _abspath)
-  basename == "html_parser" ? "HTMLParser" : basename.camelize
-end
-
Rails.autoloaders.each do |autoloader|
-  autoloader.inflector = inflector
+  autoloader.inflector.inflect(
+   "html_parser" => "HTMLParser",
+   "ssl_error"   => "SSLError"
+ )
end

「inflectionってときどきinfection(感染)と間違えそうになります😆」

ルーティングのマッパーでHTTPのOPTIONSをサポート

# actionpack/lib/action_dispatch/routing/mapper.rb#L752
+       # Define a route that only recognizes HTTP OPTIONS.
+       # For supported arguments, see match[rdoc-ref:Base#match]
+       #
+       #   options 'carrots', to: 'food#carrots'
+       def options(*args, &block)
+         map_method(:options, args, &block)
+       end

つっつきボイス:「何とHTTPのOPTIONS verbがルーティングマッパーで初めてサポートされたそうです」「あれ〜今までなかった?」「OPTIONSってそういえばあったわ😆」

参考: OPTIONS - HTTP | MDN

curl -X OPTIONS http://example.org -i

HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, POST
Cache-Control: max-age=604800
Date: Thu, 13 Oct 2016 11:45:00 GMT
Expires: Thu, 20 Oct 2016 11:45:00 GMT
Server: EOS (lax004/2813)
x-ec-custom-error: 1
Content-Length: 0
developer.mozilla.orgより

「新機能にしてはテスト無しでズコっと入ってますね😆」「今更ですけどmapper.rbのコード↓めちゃ長い〜😇」「2300行😇」

「#37370のプルリク↓見ると、今まではmatchを使わないと書けなかったのか」「シンタックスシュガーというか😳」「OPTIONS何に使うんだろう😆」「要るのかどうかは知らないけど😆」「クローラーとかで使いそうですけど、業務だとあまり使わないかな?」「とも言い切れなさそう😆」

# 同PRより
# before
match 'bar', to: 'foo#bar', via: :options

# after
options 'bar', to: 'foo#bar'

「ルーティングといえば、以前つっつきでmorimorihogeさんが『Railsのルーティングは組み合わさったときにどう動くのかがわからなくてつらい』って言ってましたね(ウォッチ20180406)」「ほんにそれ: ひとつひとつのAPIにはドキュメントがあるけど組み合わせたときがマジむずいし、しょうがないからルーティングを切ってはアクセスしてみて動いた〜とか動かない〜とかやってますし😭」

Railsのルーティングを極める(前編)

番外: RailsはまだSameSite=Noneパッチがマージされていない


つっつきボイス:「前回のウォッチのレビュー中に教わった情報で、cookieにSameSite=Noneを設定していないサイトは来年2月からChromeでSameSite=Laxとみなすぞということだそうです」「Laxって『ゆるい』ってことか😆」「Railsではそれに対応する2017年の#28297がまだマージされてないそうです😳」

参考: Chrome で SameSite=None に関する Cookieについての警告が表示される | ラボラジアン
参考: CookieのSameSite属性 NoneとLaxの違い - Qiita

#28297の最新のコメントを見ると『現在のメンテナンスポリシーによるとこれが入るのは早くてRails 6.1で、バックポートはされないかも』とあります。

Rails

なお、今週のRuby Weeklyの末尾のICYMIがなかなかよさそうなエントリでした。


つっつきボイス:「ICYMIを調べたら『In case you missed it』の略で『もしご存知なければ』という感じですね☺️」

「その中でこの記事↓はリードオンリーのRailsコンソールを使う方法の解説です」「productionのデータをぶっ壊さずにコンソール使いたいときはあるかも☺️」「dry run的な」「saveやめろぉぉみたいなことがなくなるのはよさそう😆」「ローカルでもリードオンリーコンソールをやりたいことはあったりしますね: 頑張って作ったテストデータをうっかり壊したくないときとか😋」

参考: How to Setup a Readonly Rails Console - DEV Community 👩‍💻👨‍💻

Rails 6の新しいデフォルト設定の意味と、安全にコメント解除する方法(Ruby Weeklyより)

同記事より(長いのでRails.application.config.は略しました):

  • action_view.default_enforce_utf8 = false
  • action_dispatch.use_cookies_with_metadata = true
  • action_dispatch.return_only_media_type_on_content_type = false
  • active_job.return_false_on_aborted_enqueue = true
  • active_storage.queues.analysis = :active_storage_analysis
  • active_storage.queues.purge = :active_storage_purge
  • active_storage.replace_on_assign_to_many = true
  • action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
  • active_record.collection_cache_versioning = true
  • config.autoloader = :zeitwerk

つっつきボイス:「この記事はRails 6で追加された新しい設定のいくつかを詳しく解説して、移行時に設定のコメントを安全に外す方法も書かれています」「おほ😍」

「バージョンアップのたびにconfigの項目って増えますよね😆」「それはしゃーない😆」「RailsのconfigについてはRailsガイド↓にもありますけど最小限しか書かれていないことが多いので😅」

「たとえばaction_dispatch.use_cookies_with_metadata = trueを有効にするとpurposeフィールドをcookieに追加してから署名・暗号化する、その代わり一度有効にしたらRails 5.xにダウングレードできなくなる、という具合」「へぇ〜😳」「引き返せない設定😇」

action_dispatch.return_only_media_type_on_content_type = falseも長いですけど😆、有効にするとcontent_typeでmedia type以外の値(charset=utf-8など)も含まれるようになる」「最近のcontent_type周りの修正に関連してそう🤔(ウォッチ20190902)」

active_job.return_false_on_aborted_enqueue = trueはActive Jobですね」「Active Jobでthrow(:abort)できる↓って知らなかった〜😳」「そこの挙動を変えられるんですね」

# 同記事より
class MyJob < ApplicationJob
  before_enqueue { |job| throw(:abort) if job.arguments.first }
  def perform; end
end

job1 = MyJob.perform_later(false)
job2 = MyJob.perform_later(true) 

active_storage.queues.analysis = :active_storage_analysis」「Active Storageで何か分析してくれるのかな?😆」「あ、画像のheightwidthを取ってmini_magickで使ったりできるのか」

[Rails] MiniMagickでPDFのページ数を取得するときはフォントエラーに注意!

「きりがないのでこの辺で止めますけど、Railsガイドだけだとわからない情報があってよさそうですね😍」「configできれば触りたくないけど😢」「必要になったらこの記事を泣きながら読むことになるんでしょうね😆」「ありそう😆」「この記事翻訳したいです😋」

Rails 6でビューヘルパーのimage_altが削除

たしかに最新のapi.rubyonrails.orgから消えています。

# api.rubyonrails.orgより
image_alt('rails.png')
# => Rails

image_alt('hyphenated-file-name.png')
# => Hyphenated file name

image_alt('underscored_file_name.png')
# => Underscored file name

つっつきボイス:「image_altヘルパーがRails 6から消えたそうです」「そういえば最近は自分でaltを設定しないといけないことになってた気がするけどそれかな😎」「image_altがファイル名から適当に推測して生成するaltテキストがスクリーンリーダーなどでいろいろ具合がよくなかったそうです」「やはりaltぐらい自分で書けと」「書きたくないけど😆」

後で調べると、Rails 5.2でimage_altが非推奨になっていました↓。image_tagからの呼び出しも削除されたそうです。

DHHも消したがっているaccepts_nested_attributes_for


#26976より

上はDiscourseのclean-railsで知りました↓。


つっつきボイス:「以前から評判のよろしくないaccepts_nested_attributes_forですけど(ウォッチ20180820)、少なくとも2016年の時点ではDHHも殺したいと思っていることを上で知りました😆」「これは殺していいと思う🙋‍♀️」「使ったことあるけど心底つらかった〜😇」「Discourseでjoker1007さんも滅びるべきと書いてますね」「自分もjoker1007さんに全面同意🙋‍♀️」

「DHHもコメントで『新しいAPIとして推奨すべきでない』『むしろコントローラで手動でやる方法を示すべき』と書いてますね」「わかる〜😂」

「joker1007さんのコメントでも言及されているけど、こういうのはむしろJSON構造から攻略するのがいいんじゃないかって自分も思いますし☺️」「なるほど!」「動的にフォームの項目を増やすんなら結局JavaScriptのお世話になりますし、そうやってJavaScript使ったのに結局素のフォームだったら意味ないので、素直にAPI叩けばええやんって思いますし😆」

accepts_nested_attributes_forはデモ用なのか?

「ここは自分の推測でしかないんですけど、accepts_nested_attributes_forってもしかすると『Railsなら15分でアプリを書けまっせドヤァ😎』みたいなデモ用なんじゃないかって今思いました」「あ〜何だかわかる気がします😳」「ほとんど何も書かなくてもよしなにやれるあたりとか、そういう用途だと有用なんですよ」「ところがそれを真に受けて業務で使うと途端に破綻するという🤣」「🤣」「モデルもビューも結構癒着しますし😇、これって単純に追加して保存できるだけなんじゃね?って」

「こんな例えが合ってるかどうかわかりませんけど、楽器のキーボードについているデモ演奏ボタン↓にちょっと似ているかもですね😆」「それそれっ🤣」「そのデモ演奏ボタンを本番のライブで無理やり使おうとしているみたいな😆」「デモ演奏だとテンポも変えられませんし😆」

「まあそういう感じのデモ機能って15年ぐらい前に流行りましたよね☺️」「ボタン一発でブログサイトを作れますとか😆」「そういうデモでhas_many周りを一気にやれるのを見せるのはとってもインパクトあるんですけど、実際には使えないという🤣」「deleteってどうすんの?みたいなレベルで既に悩む」「で建て増しを繰り返すうちに結局詰んだり😇」

「idがついてないとcreateだし、idがついていればupdateだし、deleteフラグが立ってればdestroyするし、というのを一見同時にやれそうな気がするんですよ: でも誰もコントロールできない😆」「スレッドセーフとかも大丈夫かどうかよくわからないし😅」「歴史調べてないのであくまで推測&印象😆」「自分一人しか使わない管理画面で、かつ重要じゃないデータを手軽に出したいみたいなユースケースならaccepts_nested_attributes_forはまだワンチャンあるかなって思います☺️」

「なおaccepts_nested_attributes_forはRails 2.3からあるそうです↓」「割と古くからあった気はします」「さすがに最初期からではなかった😆」

Meetup for Rails engineersのスライド3つ


connpass.comより


つっつきボイス:「こちらのイベントを見逃してて、終わってから気づきました😅」「3つは追いきれないので、とりあえず『実践Railsアプリケーション設計』のスライドを見てみましょうか😋」

『実践Railsアプリケーション設計』

つっつきではかなり盛り上がりましたが、記事にすると多すぎるので間引いています🙇。

「『実装とテストは資料が多いけど、設計の書籍は抽象的な内容が多い』と」「これホントにそう!外部設計と内部設計の本ってたいてい抽象論になっちゃう😭」「なるべく具体的な設計の過程を知りたいですよね」「ところが設計を具体的に書くと、今度は分量が増える割には価値が薄くなっちゃうという😇」「読んだ人にとっては自分の業務に合わないとかが続出しちゃうんですよ😢」「言語が違うだけでも大きく変わりますし」「設計論を具体的に書くと一般性が損なわれちゃうんですね😅」

「しかも本当に具体的な設計ってビジネス上の機密に直結しちゃうから、そういうものほど本にできない😆」「そうそう😆」

「このスライドでは以下に絞って話を進めていますね」

「要件から重要な名詞と動詞を抽出して、概念を固めたうえで関係をまとめる↓」「つまりエンティティに適切な名前を与える」「そこが超重要👍」「設計って実はものすごく日本語力を要求されるんですよ」「誰が見ても誤解しない名前をつけるのが大事」

「最近自分が設計するときは、いきなり英語名を付けないように注意してますね☺️」

「これが実際の名前か↓」「ReassociatedRequestだと受動態か完了形か迷っちゃうので、個人的にはReassociationRequestとしたい気がしますけど😆」「実際の業務を見ないとどっちが適切か判断難しいですね😅」「やっぱり名前むずい😭」

「このスライドいいですね👍」「ここに書ききれないぐらいいい話がいっぱい出ました😂」「こういうスライドを元に強い人が解説するのがよい気がしました😋」

でかい画像を正しく扱う


つっつきボイス:「お馴染みEvil Martiansの記事で、Railsに限定せずにでかい画像を適切に扱う方法を解説しています」「こんな図も↓」「かなり長い記事…😅」


同記事より

TimeWithZoneクラス


つっつきボイス:「ベストマサフミさんの短い記事です」「:dbto_timeで変換しないとUTCになっちゃうのか😳」

その他Rails

つっつきボイス:「RAILS_ENV=stagingはたしかに悪い文化!」「productionとstagingで環境を分ける意味ってあんまりなくて、stagingはデータが本物ならproductionになれるようにするのが正確ですね☺️」「でないとif stagingみたいなのができてだんだんつらくなるし」「挙句の果てにstagingはよくてもproductionでコケたりしますし😇」「そしてproductionはデプロイしないとどうなるかわかりません、になって本末転倒になると😆」


つっつきボイス:「まだbetaだそうですが気になりますね😋」「Railsとフロントエンドってそんなに相性悪くないと自分は思うんですけどね☺️」「仲良くなれないという思い込みもあるのかも?」「たぶんね」「でもたぶん仲良くはなれない🤣」「🤣」


前編は以上です。

おたより発掘

こちらこそありがとうございます🙇。

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191029後編)Ruby 2.7.0-preview2、tapping_device gemとhumanize gem、平成Ruby会議ほか

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

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

Rails公式ニュース

Ruby Weekly

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

夏のTechRachoフェア2019

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ