- 開発
週刊Railsウォッチ(20190805-1/2前編)Rails 6のActive Recordは速くなった、Windows WSL2+VSCodeでのRails開発、Martin Fowler記事ほか
こんにちは、hachi8833です。7payが9月に静かに息を引き取ることが先週決まったそうです。
いや、「7Payって、結局何が悪かったんだ?」って真っ当な疑問でしょ。僕も知りたいですよ。外部が(僕も含めて)色々言っているけど、いずれも推測、もっと言えば憶測にすぎません。 https://t.co/LEKEGzAQEc
— 徳丸 浩 (@ockeghem) August 4, 2019
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください
今回は「公開つっつき会」第13回を元にしています。ご参加いただいた皆さま、ありがとうございます!
⚓Rails: 先週の改修(Rails公式ニュースより)
6.0.0リリースが迫りつつありますね。今回は主に以下から見繕いました。
- 直近のコミットリスト: Comparing master@{2019-07-20}...@{2019-08-01} · rails/rails
- 6.0.0マイルストーン: 6.0.0 Milestone
6-0-stableブランチはもう立っていますね。
まさに今、Rails 6.0.0.rc2を触っていますw まだテストがfailしていて、もう少しで終わる!(はず)
— 神速 (@sinsoku_listy) July 30, 2019
つっつきボイス:「Rails 6、8月になったしもうそろそろかな😋」「マイルストーンはいったんゼロになったんですが、今日見たら6つになってました(その後5つになりました)」「なるほど、rc2でissue増えましたか」「@kamipoさんが『rc2のうちに使ってみてね』と呼びかけてたのが功を奏した感じ🙏」「以下は基本的にmasterへのマージですが、ものによってはバックポートされるかなと」「致命的なissueならバックポートされるかもですね☺️」
⚓レスポンスにVary: Accept
ヘッダーを追加
/users/1
のようなリクエストでは、返すべきものを決定するのにAccept
ヘッダーを用いる。そのレスポンスヘッダーにVary
を追加しないと、ブラウザが誤って別の種類のコンテンツをキャッシュする可能性があり、コンテンツの代わりにJavaScriptコードが露出するかもしれない。このプルリクは、そのようなクエストにVary: Accept
を追加することで修正する。詳しくは#36213を参照。
Stan Lo
changelogより大意
# actionpack/lib/abstract_controller/rendering.rb#L17
module Rendering
extend ActiveSupport::Concern
include ActionView::ViewPaths
# Normalizes arguments, options and then delegates render_to_body and
# sticks the result in <tt>self.response_body</tt>.
def render(*args, &block)
options = _normalize_render(*args, &block)
rendered_body = render_to_body(options)
if options[:html]
_set_html_content_type
else
_set_rendered_content_type rendered_format
end
+ _set_vary_header
self.response_body = rendered_body
end
# actionpack/lib/action_controller/metal/rendering.rb#L80
+ def _set_vary_header
+ self.headers["Vary"] = "Accept" if request.should_apply_vary_header?
+ end
# actionpack/lib/action_dispatch/http/mime_negotiation.rb#L150
+ def should_apply_vary_header?
+ !params_readable? && use_accept_header && valid_accept_header
+ end
...
private
...
+ def params_readable? # :doc:
+ parameters[:format]
+ rescue *RESCUABLE_MIME_FORMAT_ERRORS
+ false
+ end
Rails 4.2.1+Ruby 2.1.7p400で
Accept
ヘッダーを追加すると、レスポンスにVary: Accept
ヘッダーが含まれるはずが、すべてのレスポンスがuser agent(ブラウザ)側から等しいとみなされてキャッシュの問題が発生する。以下も参照。
94369 - Backing doesn't handle Vary header correctly - chromium - Monorail
issueより大意
Goby言語の@st0012ことStan Loさんによるコミットです。
つっつきボイス:「Vary
ヘッダーってそういえばあったかも🤔」「こんなのがあったとは↓」
参考: RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
日本語: RFC 7231 — HTTP/1.1: Semantics and Content (日本語訳)
「解説記事探す方が早そう↓」
Vary HTTP レスポンスヘッダは、オリジンサーバから新しく要求するのではなく、キャッシュされたレスポンスを使用できるかどうかを決定するために将来のリクエストヘッダをどのように一致させるかを決定します。これは、コンテンツネゴシエーションアルゴリズムでリソースの表現を選択するときにどのヘッダを使用したかを示すためにサーバによって使用されます。
developer.mozilla.orgより
「もしかすると、AWS Cloudfrontとかでキャッシュキーに指定すると参照されるのかも?」「この記事がまさにそれっぽい↓」
参考: コンテンツキャッシュとVaryヘッダとnginx - Qiita
参考: Amazon CloudFront(グローバルなコンテンツ配信ネットワーク)| AWS
「スマホ向けとか?」「スマホもそうでしょうし、Accept-Language
ヘッダーで言語別にキャッシュしたりとかもあるでしょうね☺️: URLは同じだけど言語が違う場合にもキャッシュを効かせたいときとか」「Vary
で指定したヘッダーの組み合わせをキャッシュのキーに使うというもののようだ」
参考: Accept-Language - HTTP | MDN
「そしてプルリク#36213はというと、『/users/1
のようなリクエストでは、返すべきものを決定するのにAccept
ヘッダーを用いる』、あ〜たしかに!」「おぉ?」「Railsの通常のリクエストフォーマットだと、Accept
ヘッダーで指定した内容に基づいて、JSONを返したりHTMLを返したりします: そしてVary
を指定しないと違うものをキャッシュしちゃうかもしれないので、このプルリクでVary: Accept
を追加したと」「これはごもっともな修正!」
「issueを見ると、Rails 4で見つかったとありますね」「全員ではないにしても、ハマってた人はおそらく以前からいたでしょうね😅」「あ〜」「主にどこで問題になるのかですけど、たぶんブラウザというよりはコンテンツプロキシの方じゃないかな〜?🤔」
後にst0012さんに尋ねたところ「主にブラウザが対象のつもりだった」とのことでした。
参考: カスタムオリジンの場合のリクエストとレスポンスの動作 - Amazon CloudFront
オリジンが応答で
Vary:*
を返し、対応するキャッシュ動作の [Minimum TTL
] の値が [0
] の場合、CloudFront はオブジェクトをキャッシュしますが、そのオブジェクトの後続のすべてのリクエストをオリジンに転送して、オブジェクトの最新バージョンが含まれていることを確認します。CloudFront には、If-None-Match や If-Modified-Since などの条件付きヘッダーは含まれません。その結果、オリジンはすべてのリクエストに応じて CloudFront にオブジェクトを返します。
オリジンが応答でVary:*
を返し、レスポンスで返される、対応するキャッシュ動作の [Minimum TTL
] の値が別の値になっている場合、CloudFront は「CloudFront が削除または更新する HTTP レスポンスヘッダー」に記述されている方法でVary
ヘッダーを処理します。
docs.aws.amazon.comより
「CloudFrontはVary
ヘッダー対応しているのかな?と思ったら『対応してない』という2015年のブクマコメント↓が目に止まったけど、実際どうなんだろう?🤔」
※コメントを直接埋め込めなかったので、「〜のコメント」をクリックしてください。
コンテンツキャッシュとVaryヘッダとnginx - Qiita
ちなみにCloudFrontはVaryヘッダについて感知しない。Accept-EncodingとForward Headers設定単位でキャッシュする。
その後社内での記事見直しで、『CloudFrontはあくまでCDNに対して「どのヘッダをキャッシュキーにするか」を設定する必要があるので「Originサーバーが任意にキャッシュキーを設定できる」Varyヘッダの動きはしてくれないであろう』と推測されましたが、このあたりに詳しい方の情報お待ちしています🙇。
「もしかすると、ウサギさんアイコンでおなじみのVarnishあたりならVary
に対応してるかな?」「以前ウォッチで取り上げましたね🐰」「Varnishは爆速コンテンツキャッシュサーバーですけど、最近はあまり自前では建てなくなったかな〜」「ともあれVary
ヘッダーはその種のコンテンツキャッシュサーバーとかnginxとかに関連するヤツですね」
参考: Vary — Varnish version 3.0.7 documentation
「コミットした@st0012さんも『自分的にクリーンヒットだったと思う』と言ってました」「このissueは知らずに踏んだら正体不明になりがち😆」「原因突き止めるのめちゃ大変そう...😅 」
⚓MatchData
を作らないMime::Type\#match?
を追加
# actionpack/lib/action_dispatch/http/mime_type.rb#L286
+ def match?(mime_type)
+ return false unless mime_type
+ regexp = Regexp.new(Regexp.quote(mime_type.to_s))
+ @synonyms.any? { |synonym| synonym.to_s.match?(regexp) } || @string.match?(regexp)
+ end
つっつきボイス:「amatsudaさんのコミットなんですが、次の2つは最初のに関連しているのかなと思って」「=~
をやめてmatch?
メソッドに変えたのね」
# actionpack/lib/action_dispatch/http/mime_type.rb#L204
def parse_data_with_trailing_star(type)
- Mime::SET.select { |m| m =~ type }
+ Mime::SET.select { |m| m.match?(type) }
end
「これは高速化かしら?」「コミットには特に説明ありませんね😅」「たしか=~
はグローバル変数を書き換えるけどmatch?
は書き換えない分速いとかがあったような?」「そういえば前にウォッチでも取り上げたような」
こちらでした↓。2.4で入ったんですね。
参考: Ruby 2.4 implements Regexp#match? without polluting global variables | BigBinary Blog
「=~
は自分が見つけた記事では2.6でdeprecatedになってる?」「おお?」「もうそこまで進んでた?」「自分、割と使ってますけど」「便利ですよね😋」「=
と~
の順序、どっちが先かわからなくなりがちですけど😆」「順序変わると意味変わりますし☺️」「それにしても記号はググりにくいの〜😅」
参考: サンプルコードでわかる!Ruby 2.6の主な新機能と変更点 - Qiita
「おや、でも記事にnil
の=~
は明示的に実装されたとありますし、どうやら2.6でdeprecatedになったのはObject#=~
だけらしい」「あ、そういうことか!」「Object#=~
はなまじnil
を返すんじゃなくて、NoMethod errorを出して欲しいと」「なので=~
自体はなくならない😋」「@mameさんのチケット↓を見ても、Object#=~
の話はあってもRegexは直接関係なさそうだし」
参考: Feature #15231: Remove `Object#=~` - Ruby master - Ruby Issue Tracking System
⚓frozen文字列がtransliterate
されたときのエラーを防止
- PR: Prevent error on transliterate with frozen strings by cpruitt · Pull Request #36825 · rails/rails
ActiveSupport::Inflector.transliterate
はエンコードが変更されると文字列を改変する。#36702から本コミットまでの間は、frozen文字列を渡すとFrozenError
が発生することがあった。transliterate
する前にはfrozen文字列をdup
するよう変更した。
同PRより大意
# activesupport/lib/active_support/inflector/transliterate.rb#L63
def transliterate(string, replacement = "?", locale: nil)
+ string = string.dup if string.frozen?
raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
つっつきボイス:「修正は文字列がfreeze
されてた場合に対処したと」
「ところで今のRubyはもうfrozen文字列ってデフォルトになったんだっけ?」「えっとまだだったかな?3.0で入るんだったかな?😅」「こないだRuboCopに怒られたような気が😆」
参考: [Ruby] Ruby 3.0 の特大の非互換について - まめめも -- 2015年の記事です
その後BPS社内Slackで、「最終的にRuby 3ではfrozen-string-literalsをデフォルトにしないことになった」↓と指摘をもらいました。
参考: Ruby3.0とRails6.0では主にどのような変更や新機能が加わりますか?登場の際、開発、保守、運用、コミュニティ、その他言語とのポジション(特にPython2と3)はどんな変化が考えられますか? - Quora
参考: Feature #11473: Immutable String literal in Ruby 3 - Ruby master - Ruby Issue Tracking System
I consider this for years. I REALLY like the idea but I am sure introducing this could cause HUGE compatibility issue, even bigger than Ruby 1.9. So I officially abandon making frozen-string-literals default (for Ruby3).
This does not mean we are going to remove the frozen-string-literal feature that can be specified by magic comments.
Matz.
#11473より
⚓transliterate
よもやま
「しかしtransliterate
ってそもそも何やねん🤣」「えっと、Rubyをルビーと書くとか、改善をKaizenと書くみたいに外国語の音を写し取ることを一般にtransliterateって言いますね」「へぇ〜」「そんなメソッドがあったとは😆」
transliterate v. (他国語の文字などに)書き直す; 音訳[字訳]する.
参考: ActiveSupport::Inflector -- なお、APIdockでは文字化けしてます😇。
transliterate('Ærøskøbing')
# => "AEroskobing"
「ロシア語を英語表記にするみたいなときに使いそうですね」「APIには『非ASCII文字をASCII文字で近似したりする』とありますね」「なるほど、アクセント記号付きのアルファベットをアクセント記号なしにするとか」
「transliterate
、その気になればジャパニーズ実装もできそう😋」「yamlファイルでできるみたいですね」「関西弁オプションとか🤣」「🤣」「日本語のローマ字、大きく分けただけでもヘボン式とか文科省がやってるヤツとかいろいろバラついてて難しそう😅」「transliterate
、日本人にはピンとこないけどヨーロッパの人はとても欲しいでしょうね」「使うシチュエーションがあまり思いつきませんしっ😆」
参考: ヘボン式ローマ字 - Wikipedia
参考: 日本式および訓令式 - Wikipedia
⚓Azureにもファイル名とdispositionをアップロード
# activestorage/lib/active_storage/service/azure_storage_service.rb#L20
- def upload(key, io, checksum: nil, content_type: nil, **)
+ def upload(key, io, checksum: nil, filename: nil, content_type: nil, disposition: nil, **)
instrument :upload, key: key, checksum: checksum do
handle_errors do
- blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum, content_type: content_type)
+ content_disposition = content_disposition_with(filename: filename, type: disposition) if disposition && filename
+
+ blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum, content_type: content_type, content_disposition: content_disposition)
end
end
end
つっつきボイス:「dispositionはContent-Disposition
ヘッダで合ってます?」「合ってると思います: IEではこれを付けないと...😇というヤツ」「それかっ!」「PR自体はAzureのStorageにアップロードするコードにmeta情報を付けられるようにしたということみたい」
参考: BK通信 - ブラウザのバッドノウハウ コンテンツ編
「IEはContent-Disposition
でファイル名を渡すんですが、FirefoxやChromeは普通にfilename
で渡せばUTF-8文字列でもちゃんと食べてくれる😋: Content-Disposition
が本来何をするものなのかよくわかってませんが😆」「IE対応という印象しかないというか、IEのファイルダウンロードはこれを設定しないとだいたいおかしくなる😢」
参考: Content-Disposition - HTTP | MDN
通常の HTTP レスポンスにおける Content-Disposition レスポンスヘッダーは、コンテンツがブラウザでインラインで表示されることを求められているか、つまり、Webページとして表示するか、Webページの一部として表示するか、ダウンロードしてローカルに保存する添付ファイルとするかを示します。
developer.mozilla.orgより
Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"
「今更MDNを見てみると↑、コンテンツをインラインで表示するか添付ファイルとしてダウンロードするかを指定するものだったのね😆」「PDFでinline
を指定するとブラウザの中でPDFが開いたり、attachment
を指定するとファイルとして保存したり、ということをやるためと」「そしてfilename=
というオプションが使えると: IEはこっちを優先しちゃってたりするのかもな〜」
「これも関連してそうですね↓」「こっちはActive Storageのストレージごとのオプションを実装したと」「こんな実装なのか↓: 引数最後にある**
が、以後の引数を全部捨てにかかってる感😆」「互換性維持とかのために、知らないオプションがやってきてもよしなに握りつぶす感😆」「もしかすると**
でやれば仮引数代入が発生しない分ちょっぴり速くなったりするかな〜、なんて😆」「どちらかというと、指定にない引数を付け呼び出してもArgumentErrorが出ないで処理してくれる方が重要だったりするかも🤔」
# activestorage/lib/active_storage/service/s3_service.rb#L23
- def upload(key, io, checksum: nil, content_type: nil, **)
+ def upload(key, io, checksum: nil, filename: nil, content_type: nil, disposition: nil, **)
instrument :upload, key: key, checksum: checksum do
+ content_disposition = content_disposition_with(filename: filename, type: disposition) if disposition && filename
+
if io.size < multipart_upload_threshold
- upload_with_single_part key, io, checksum: checksum, content_type: content_type
+ upload_with_single_part key, io, checksum: checksum, content_type: content_type, content_disposition: content_disposition
else
- upload_with_multipart key, io, content_type: content_type
+ upload_with_multipart key, io, content_type: content_type, content_disposition: content_disposition
end
end
end
「そういう書き方ってしてなかったので参考になる〜😋」「他人が書いたのを見ないと知らないままになりそう」
⚓Issue: Sidekiqがclassicオートローダーでコケる
- issue: Sidekiq hangs using Rails 6 app with "classic" autoloader · Issue #36810 · rails/rails
- 再現用アプリ: langsharpe/test_sidekiq_activetext
つっつきボイス:「6.0.0のマイルストーンから1つだけissueを拾ってみました」「Zeitwerkからクラシックオートローダーに切り替えると、よりによってAction Textでデッドロックする!」「よく見つけたな〜これ😳」
「ぱっと見、Action Textだからビューに関連してるかと思ったけど、Sidekiqジョブを投入する側のオートローダーとジョブを処理するワーカー側のオートローダーが異なるとおかしくなるらしい」「スレを見ると、productionでは起きずdevelopmentで起きるとか、Sidekiqプロセスのコンカレンシーを1
にすると回避できるとありますね」
Sidekiqログ: https://gist.githubusercontent.com/langsharpe//Sidekiq.log
「これがSidekiqのログ↑」「冒頭に絵が入ってくるんですね😆」
$ bundle exec sidekiq
m,
`$b
.ss, $$: .,d$
`$$P,d$P' .,md$P"'
,$$$$$bmmd$$$P^'
.d$$$$$$$$$$P'
$$^' `"^$$$' ____ _ _ _ _
$: ,$$: / ___|(_) __| | ___| | _(_) __ _
`b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` |
$$: ___) | | (_| | __/ <| | (_| |
$$ |____/|_|\__,_|\___|_|\_\_|\__, |
.d$$ |_|
2019-07-26T06:55:53.814Z 17273 TID-oxdgbihcp INFO: Running in ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18]
2019-07-26T06:55:53.814Z 17273 TID-oxdgbihcp INFO: See LICENSE and the LGPL-3.0 for licensing details.
2019-07-26T06:55:53.815Z 17273 TID-oxdgbihcp INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
2019-07-26T06:55:53.815Z 17273 TID-oxdgbihcp INFO: Booting Sidekiq 5.2.7 with redis options {:id=>"Sidekiq-server-PID-17273", :url=>nil}
2019-07-26T06:55:53.872Z 17273 TID-oxdgbihcp INFO: Starting processing, hit Ctrl-C to stop
...
「再現手順をチラ見した感じでは割と特殊なことやってるっぽいけど😆」「Zeitwerkの状態でジョブを実行してコンプリートしてから、クラッシックオートローダーに切り替えると、起きると」「逆はやってないけどやったらどうなるんだろう😆」「よくぞ見つけた👍」「6にアップグレードするときはSidekiqあたりに注意が必要かも」
⚓amatsudaさんがアロケーションなどを削減
ほとんどが細かなリファクタリングです。
- commit: Reduce Array allocations · rails/rails@7e299ce
- commit: Reduce Array allocations · rails/rails@34a7e68
- commit: Reduce Array assignment by not giving a name for unused `*args` · rails/rails@5ae814d
- commit: Reduce method calls · rails/rails@f0fdeaa
- commit: Reduce method invocations and object allocations in head() · rails/rails@511d1ab
- commit: Reduce block execution · rails/rails@c461721
- commit: Speedup and reduce Array creation when constantizing a non-namespaced… · rails/rails@7952087
- commit: No need to dup the payload for an instrumentation · rails/rails@23009e3
- commit: Reduce String allocation when finding controller class · rails/rails@303f388
- commit: Reduce object allocations in Middleware::Static · rails/rails@b8d29f3
- commit: Cache tags_text to avoid computing tags each time when logging · rails/rails@05060dd
- commit: Reduce Hash object creation when normalizing request env · rails/rails@a1d7d4c
- commit: Avoid creating new Array when looking up already registered detail · rails/rails@46d207f
- commit: Accessing ivar with Symbols might be just a very little bit better th… · rails/rails@6dbfc67
# activesupport/lib/active_support/inflector/methods.rb#L378
- parts.reverse.inject(last) do |acc, part|
+ parts.reverse!.inject(last) do |acc, part|
part.empty? ? acc : "#{part}(::#{acc})?"
end
つっつきボイス:「@amatsudaさんがアロケーションを削減したりした細かなコミットがたくさんあって、それぞれやってることが少しずつ違ってて参考になりそうだったので」「ひたすら黙々と」「こういう感じで書くことでメモリコピーが減るので、やって大丈夫であれば有効でしょうね」「たくさん並べましたが、今つっつきで全部開けると時間かかりそうなので適当なところまででいいです😅」
「こういうふうに*
で置き換えたりも↓」「引数を単独の*
にするのって意味的にはどういうものでしたっけ?」「メソッドシグネチャとしては受けられるけど中では使わない、ということでしょうね」「さっきの**
もそうですし」「渡してもinvalid argumentとかにはならないけど、使われない」
# actionpack/lib/abstract_controller/callbacks.rb#L40
- def process_action(*args)
+ def process_action(*)
run_callbacks(:process_action) do
super
end
end
⚓Rails
昨日酔っ払ってて貼り忘れたけどアクティブレコードだけなら結構速くなってるやろ? #roppongirbhttps://t.co/qqpTLWVP2Ohttps://t.co/rilxLff8cy
— Ryuta Kamizono (@kamipo) August 1, 2019
つっつきボイス:「上のツイートは本日(公開つっつき会の日)のついさっき見つけました」「自分もさっき社に戻る途中で見た😋」「@kamipoさん半端ないわやっぱ!」「Rails 6だいぶ速くなったみたいですし」「オブジェクトアロケーション系の操作って遅くなりやすいから、そういうところなんかも速くなるのはうれしいですね😂」「このベンチ結果を眺めながらrc2で遊ぶとはかどりそう❤️」
⚓ポリモーフィック関連付けでのeager loading
2017年の記事です。
つっつきボイス:「コードが9割を占めている感じの記事でした」
流れ:
- ポリモーフィック関連付けのeager loadingでN+1を避けるのは面倒
- (普通のサンプル)
- 動かすとN+1が発生
preload
で即解決- (ポリモーフィック関連付けのサンプル)
- ちょい面倒
ActiveRecord::Association::Preloader
を書いて解決ActiveRecord::Association::Preloader
とpreload
と組み合わせて解決
「この辺、最近のRailsだともうちょっとよくなってたような覚えがうっすらと」「N+1が起きなくなるんでしょうか?」「いや、起きるんだけど効率がもう少しよくなってた気がする: あれはどこでやってたかな〜?🤔」
「普通にやってN+1が起きたらpreload
でIN展開できる」
SELECT "films".* FROM "films"
SELECT "directors".* FROM "directors" WHERE "directors"."id" IN (...)
「ポリモーフィック関連付けではPreloader
を手作りしてる」「え、こういう書き方してるのか〜😳」「でもちゃんとINに展開されてますね」
class Like::Preloader
def self.preload(likes)
preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(likes.select { |like| like.resource_type.eql?(Book.name) }, { resource: :author })
preloader.preload(likes.select { |like| like.resource_type.eql?(Film.name) }, { resource: :director })
end
end
SELECT "likes".* FROM "likes"
SELECT "books".* FROM "books" WHERE "books"."id" IN (...)
SELECT "authors".* FROM "authors" WHERE "authors"."id" IN (...)
SELECT "films".* FROM "films" WHERE "films"."id" IN (...)
SELECT "directors".* FROM "directors" WHERE "directors"."id" IN (...)
「たぶんですけど、ポリモーフィック関連付けだけの問題じゃないんでしょうね: Bookだとauthor.name
を取ってFilmだとdirector.name
を取るとかしないといけなさそうですし、ポリモーフィック関連付けをポリモーフィックなまま1つのpreload
でやれるようにするということのようだ🤔」「ポリモーフィックでのN+1、自分も以前踏んだことあったような😅」
profiles = Profile.preload(:likes)
Profile::Preloader.preload(profiles)
profiles.each do |like|
puts profile.name
profile.likes.each do |like|
puts like.resource.name
puts case like.resource
when Book like.resource.author.name
when Film like.resource.director.name
end
end
end
「まあここまでやるか?という気もちょっとしますけど😆」「諦めてクエリを複数に分けるとかする手も」
⚓高品質ソフトウェアのためにコストをかける価値はあるか
Martin Fowlerさんの記事です。
つっつきボイス:「今回はMartin Fowlerさんが自分で書いてますね☺️」「お、この図どっかで見たことあるし↓: 図だけで思い出すと、コードをきちんと書いてリファクタリングもしておくと、全体のコード量はあまり進まなくても将来新機能をラクに追加できるようになるので、時間をかけてでもちゃんと書く方が長い目で見て結果的に時間を節約できる、みたいな説明だったかな」「たしかに〜」「深みがある」
「cruit...?」「英辞郎によると、cruitは新兵とか新人という意味で、recruitを略したものらしいけど、ここでは作るのに時間のかかる新機能という意味で使われてるっぽい」
参考: cruitの意味・使い方|英辞郎 on the WEB:アルク
「この図↓とかまさにそれで、この変曲点を超えるかどうかという話」「品質の低いコードを急いで書いても、時間をかけて品質の高いコードを書く方が将来機能が増えたときの品質も上がるし開発速度も上がるよと、その変わり目となる変曲点ですね😋」
「もうひとつ、手掛けているサービスがこの分岐点を超えるほど生きながらえるのかという問題とのバランスですよね🤣」「🤣」「🤣」「まあでも実際そうで、製品がリリースされるまでに開発費用が底をついたらおしまいですし💸、どんなに目も当てられないようなコードであってもまずはリリースできなければ価値ゼロですし」「このさじ加減がなかなか難しい😅」「冒頭の、9月に終わるサービスみたいな😆」「特にベンチャーがVCから資金を受けて開発しているような場合はタイムリミットを越えたら投資を引き上げられるので、そういう条件下だと別の力学が働き始めるといいますか😆」「そういう場合でなければこの図のように考えたいですね☺️」
⚓Railsマイグレーションのちょっとしたコツ(Hacklinesより)
短い記事です。
つっつきボイス:「schema_migrationsテーブルを操作するためのつらい方法とラクな方法」「delete from schema_migrations
とか普通やらなくない?🤣」「db:rollback
でやりましょう🤣」
# 同記事より
delete from schema_migrations where version in (20190726133807, 20190726191446);
「ラクな方はdb:rollback
でやると記事にあるけど先に言われちゃいましたね😆」
rake db:rollback
「うっかりブランチ切り替えたときなんかに、殺したくないテーブルがあるからそれを避けて手動で消すとかありそうですけど😆」「ありがちなのは、マイグレーションコードの中にエラーを仕込んで途中で止まっちゃったときとか、add columnsされたものを手動でちまちま消さないと次のマイグレーションができなくてイラつくとか😆」
「そうそう、db:migrate:down
でバージョン指定できる↓...ってこれRailsガイドに書いてあるんじゃ?😆」「ありますね〜😆」「tipsでもtrickでもなく、仕様でした😆」「hard wayの方は...」「hard wayはつらいヨ、とだけ言っておきます😭」
rake db:migrate:down VERSION=20190726133807
参考: 4.4 特定のマイグレーションのみを実行する Active Record マイグレーション - Rails ガイド
⚓WSL2とVScodeでのRails開発はイイゾ(Hacklinesより)
つっつきボイス:「お、WSL2」「中身まだ読んでませんが、喜びが伝わってくる感じです」「今の自分は、WindowsのDockerコンテナでRailsを動かして、RubyMineで書いたコードをそこにコピーして普通にやれてます☺️」「そういえば今日Docker for Windowsの新しいstable版も出てDockerのバージョンも上がったし、Docker DesktopのDockerバージョンも19.03になったので、やった幸せになれるという感じ❤️」「先週話題にした19.03ですね(ウォッチ20190731)」「docker-composeのバージョンが上がったのが特にありがたい🙏」
参考: Docker Desktop for Mac and Windows | Docker
「もうWindowsでもDocker動かせばRails開発できますヨ😋」「最近のVSCodeにはRubyMineみたいなオートデプロイメント機能があったと思いますし」
「ちなみにWSL2を使ってる人はこの中にいらっしゃいます?」「お、まだ少ない」「使ってみたいとは思ってるんですけど😅」「Windows 10のHomを使ってる人ならWSL2欲しいですよね😋: ProならHyper-V使えますけど」
参考: Windows 10 Home と Pro はどう違うの?比較表などで解説するよっ! | Tanweb.net
前編は以上です。
バックナンバー(2019年度第3四半期)
週刊Railsウォッチ(20190730-2/2後編)Docker 19.03の新機能に注目、ngrokはスゴい、redis-namespaceほか
- 20190729-1/2前編 Rails 6のリリースは近そう?、Evil MartiansのRails+Docker記事、Railsパフォーマンス測定ほか
- 20190723-2/2後編 Rails 6 rc2がリリース、「MySQLパフォーマンスチューニングTips」が超便利、Aurora Serverlessほか
- 20190722-1/2前編 Rails 6エラー画面の改良点、Dateを四捨五入できるtime_calc、Rackミドルウェアのデザインパターンほか
- 20190717-2/2後編 NFSのよさとは、Linuxカーネル5.2リリース、Puppeteerでメモリリーク検出ほか
- 20190709-2/2後編 strong_password v0.0.7がハイジャックされていた、TerraformとCloudFormation、CSSの設計ミスリストほか
- 20190708-1/2前編 ActiveRecord::FixtureSetがめちゃ強くなってた、MacだとRubyが遅い理由、Puma 4登場ほか
- 20190701 RMagickのメモリ使用量が劇的に改善、インスタンス変数の定義順で速度が変わる?、GitLab CIランナーをローカルで回すほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSなど)です。