- Ruby / Rails関連
週刊Railsウォッチ(20201026前編)Shopifyのerb-lint gem、Form Objectを使いやすくするyaaf gem、railsrcの機能追加ほか
こんにちは、hachi8833です。
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇
⚓Rails: 先週の改修(Rails公式ニュースより)
以下のコミットリストのChangelogを中心に見繕いました。6.1.0マイルストーンは変わっていませんでした。
⚓ 新機能: values_at
メソッドをActive Recordインスタンスで使えるようになった
# Before
person = Person.new(name: 'Francesco', age: '22')
person.attributes.values_at("name", "age")
# => ["Francesco", "22"]
# After:
person = Person.new(name: 'Francesco', age: '22')
person.values_at(:name, :age)
# => ["Francesco", "22"]
このコンテキストでは
pluck
はvalues_at
のエイリアスとも言えると思う。
同PRより大意
つっつきボイス:「values_at
はあまり使ったことなかったかも」「このメソッド知りませんでした」「形としてはRubyのHash#values_at
的なメソッドをActive Recordインスタンスでも使えるようになった感じみたいです」「そういえばActive RecordのインターフェイスはRubyのHash
的なところがありますね」「values_at
を定義してflatten
してpublic_send
しているのね↓」「短く書けるのはうれしい」
# activerecord/lib/active_record/core.rb#L605
+ def values_at(*methods)
+ methods.flatten.map! { |method| public_send(method) }
+ end
+
参考: Hash#values_at (Ruby 2.7.0 リファレンスマニュアル)
⚓ read_attribute_before_type_cast
が属性エイリアスを扱えるよう修正
エイリアス化された属性の
numericality
バリデーションはtypecastするまで属性の値を取得できない。理由はActive Recordが元の属性名ではなくエイリアス名で属性値を取得しようとしていたため。
以下は無効な値を渡してもパスしてしまうバリデーションの例。
class MyModel < ActiveRecord::Base
validates :aliased_balance, numericality: { greater_than_or_equal_to: 0 }
end
MyModelを以下のようにインスタンス化したとすると、
numericality
バリデーションが走るときはtypecastされるまで値を取得できないので、typecastされた値(ここでは0.0
になる)を用いることでバリデーションが成功し、有効とされてしまう。
subject = MyModel.new(aliased_balance: "abcd")
subject.valid?
しかしMyModelを以下のように宣言したとする。
class MyModel < ActiveRecord::Base
validates :balance, numericality: { greater_than_or_equal_to: 0 }
end
そして
balance
プロパティに"abcd"という値を代入すると、バリデーションが走るときにこのモデルが無効になる(Active Recordはtypecastの前に値を取得できるようになるため)。今回の変更によって、
read_attribute_before_type_cast
を用いればattr_nameがalias_attributeであってもtypecast前に値を取得できるようになる。
同PRより大意
つっつきボイス:「numericality
はRailsガイドにも昔からあるバリデーションです」「上のコード例で:aliased_balance, numericality:
でバリデーションを設定しているのに"abcd"
みたいな文字列を渡すとvalid?
がtrueを返してしまうのか」
参考: 2.8 numericality
-- Active Record バリデーション - Railsガイド
「typecastする前に型をチェックすべきだったけど、『Active Recordが元の属性名ではなくエイリアス名で属性値を取得しようとしていた』ためにそうなっていなかったということですね」
# activerecord/lib/active_record/attribute_methods/before_type_cast.rb#L49
- def read_attribute_before_type_cast(attr_name)
+ attribute_before_type_cast(attr_name.to_s)
+ name = attr_name.to_s
+ name = self.class.attribute_aliases[name] || name
+
+ attribute_before_type_cast(name)
+ end
「ちなみに文字列をto_f
するとこうなるんですよ↓」「え」「こわい」
"abcd".to_f
#=> 0.0
「numericality
バリデーションにnumericでない値が渡されたら比較演算子でArgumentErrorなどをraiseして欲しいですね」「Rubyで文字列と数値を比較演算すると以下のようにArgumentErrorになりますけど、validates numericality
で比較したものはArgumentErrorにならずに、本来想定されない型キャスト後の値でバリデーションが走ってしまっていたんですね」
"abcd" > 0
Traceback (most recent call last):
5: from /Users/hachi8833/.anyenv/envs/rbenv/versions/2.7.2/bin/irb:23:in `<main>'
4: from /Users/hachi8833/.anyenv/envs/rbenv/versions/2.7.2/bin/irb:23:in `load'
3: from /Users/hachi8833/.config/anyenv/envs/rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
2: from (irb):7
1: from (irb):7:in `>'
ArgumentError (comparison of String with 0 failed)
「データベース側で型が指定されていれば、仮にここでバリデーションをすり抜けてしまってもsave
でエラーになりますが、バリデーターでエラーにならないのはたしかによくないですね」
⚓ MemCacheStore
のフォールバックに$MEMCACHE_SERVERS
を使うようになった
概要
このプルリクは、アドレスが指定されていない場合に
ActiveSupport::Cache::MemCacheStore
がlocalhost:11211
よりも前にENV['MEMCACHE_SERVERS']
にフォールバックするようにする。詳細
デフォルトでは、
Dalli::Client
でサーバーアドレスが渡されなかった場合のフォールバック先は以下の2つになってくる。
$MEMCACHE_SERVERS
"127.0.0.1:11211"
しかし
MemCacheStore
はアドレスを独自にチェックし、存在しない場合は"localhost:11211"
にフォールバックするようになっている。これが原因で、(Dalliが提供している)非推奨化された
:dalli_store
からmem_cache_store
に移行するときのバグにつながることがある。
-config.cache_store = :dalli_store # could be implicitly relying on $MEMCACHE_SERVERS
+config.cache_store = :mem_cache_store # ignores $MEMCACHE_SERVERS
自分たち独自のフォールバックを削除して単に
Dalli::Client
にnil
を渡すことでこのフォールバックロジックがただで手に入る。テストが追加されたので変更があれば検出できる。動機
Dalliで
:dalli_store
が非推奨化されて:mem_cache_store
が推奨になった(削除はgemのバージョン3の予定)。移行では、新しいアダプターが$MEMCACHE_SERVERS
もチェックしてくれると仮定できれば楽なのだが、この仮定は正しくなく、使うと事実上キャッシュが無効になる。
$MEMCACHE_SERVERS
が別の値に設定されているシステムではlocalhost:11211
へのフォールバックに頼るとは考えにくいしそこに接続したいということもないだろうから、この変更は安全なはず。さらにこの変更によって使いやすさが向上し凡ミスも防げる。🦶 🔫 🙅その他
このプルリクから#40419を切り出した。これによって自分が
MemCacheStore
のテストをローカルで正しく実行できた。
#40420より大意
つっつきボイス:「先週にもあったShopifyのMemcached関連の新たな改修か(ウォッチ20201020)」「MemCacheStore
が使うMemcachedサーバーのアドレスは、環境変数があればデフォルトのフォールバックよりも環境変数を参照するようになったんですね」「既に環境変数MEMCACHE_SERVERS
が設定されている環境で現在のlocalhost:11211
へのフォールバックを利用していることはないだろうと考えられるのでこの変更は安全なはず、とプルリクに書かれているのも納得」
「そういえば来月の銀座Railsの出張RailsウォッチでRailsのコンフィグ周りの話をしようかなと思っていたところだったんですけど、ちょうどこのような話です」「おぉ」「Railsのコンフィグって、デフォルト値があったり環境変数で渡したりyamlで渡したり引数で渡したりと、作法や優先順位にいろんな仮定というか暗黙の期待値があるので、自分でも整理しつつそういう話をしようかなと考えています」「自分もこの間Qiitaにそういう記事を書きました」
「最初からこの改修後のような設計になっていればよかったのにと思われそうですけど、最初からこういうものを見通して設計するのは難しいんだなと改めて思いますね」「人間はエスパーにはなれないか...」「自分が作ったものが将来どう使われるようになるかは本当にわからない」
「Railsでも当初はコンフィグで設定できたものが後で環境変数でも設定できるようになることがありますけど、かといって何でも環境変数でできるようにするのがいいかというとそうではないんですよ」「もしやったらたぶんえらいことになりそうですね」「どの設定は環境変数で上書きできるとうれしくて、どの設定だとよくないかというのは、世の中のニーズによって変わってくるところもあるんじゃないかと思ったりします: 実際今回の改修もこれまで問題になっていなかったぐらいなので」
⚓ railsrcファイルの置き場所をXDGに沿ってリファクタリングした
- PR: Refactor railsrc file location to be xdg compliant by quintrino · Pull Request #39411 · rails/rails
概要
XDG Base Directory Specification(現在はGitやTmuxやPryやRspecなどのFOSSプロジェクトで用いられている)は、config/rcなどのさまざまなファイル形式のデフォルトの置き場所を提供している。
このコミットはapp_generator.rbをリファクタリングし、
XDG_CONFIG_HOME
が設定されていて、かつXDG_CONFIG_HOME
の場所にrails/railrcファイルが存在する場合はそこからrailsrcを読み出す。後方互換性維持のため、
XDG_CONFIG_HOME
が未設定またはrails/railsrcファイルが存在しない場合はデフォルトを~/.railsrcにする。
同PRより大意
つっつきボイス:「XDG_CONFIG_HOME
がなければこれまでどおりホームディレクトリの`/.railsrcを参照するからこれまで使っている人には影響しませんね」
「railsrcって最近追加されたのかと思ったらだいぶ前からあったみたい↓(Rails 3.2〜)」「rails new
のオプションを書ける設定ファイルなんですね」「railsrc、使ったことなかった」「名前からしてLinuxのいわゆるrcファイルと同じような扱いなんでしょうね」
参考: .railsrcにrails newするときのオプションを書いておける - かみぽわーる
「rails new
をしょっちゅう実行する人には便利そう」「自分はそんなにしょっちゅうはやらないかな」「こうやって既存の仕様に沿って動くようになったのはいいことだと思います」「そういえばrailsrcの話題を最近Twitterかどこかで見かけた気がする🤔」
「今回はrailsrcをXDGに合わせて、XDG_CONFIG_HOME
が設定されていたらそれに対応するディレクトリからrailsrcを読み出すようになったんですね」「XDGって何だろう」「POSIX的な規格の一種のようですね」「XDGはX Desktop Groupの略で今はfreedesktop.orgなのか」
参考: freedesktop.org - Wikipedia
かつてX Desktop Groupと名乗っていたため、"XDG" という省略形も、ディレクトリ名などあちこちにいまだによく使われている。
Wikipediaより
つっつき後に見つけたツイートを念のため貼っておきます。
~/.config/railsrc が使えるようになるプルリク。
ちょっと気になってたプルリクなので、無事にマージされていて嬉しい。https://t.co/sFk4OK1rjz— 神速 (@sinsoku_listy) October 21, 2020
⚓Rails
⚓ Railsアプリのサイズを見積もる
つっつきボイス:「アプリのサイズというのは依存関係やコードサイズなどのことか」「Railsの標準機能としてrails stats
でコードの統計情報を表示できます↓」「開発案件の事前調査段階での現行アプリケーションの状況をヒアリングするときに、ソースコードがまだ閲覧できない状況だとrails stats
の結果をもらって多少の参考にすることはありますね」「なるほど」
参考: 1.15 その他のタスク -- Rails のコマンドラインツール - Railsガイド
「rails stats
で取れる情報はあくまで目安なので当てにしすぎない方がいいでしょうね: たとえば行数が多い場合でも、コピペコードのせいで多いのか、ちゃんと書いたコードが多いのかは実際にコードを見ないとわからないので」「テストコードが存在するかどうかをさっと見たいときなどには便利だと思います」
同記事では以下のgemに触れていました。
⚓ yaaf: RailsのForm Objectを使いやすくするシンプルなgem
# 同リポジトリより
# app/forms/registration_form.rb
class RegistrationForm < YAAF::Form
validates :phone, presence: true
validate :a_custom_validation
# ...
def a_custom_validation
# ...
end
end
以下の記事で知りました。Form Objectでバリデーションやコールバックも使えるようです。
つっつきボイス:「Yet Another Active Formの略でyaafだそうです」「Form Objectの書き方は人それぞれ好みがありますね」「この人の好きなForm Objectをgem化したのかな」「READMEを眺めた限りではシンプルで、トリッキーなことはしていなさそう」
「記事を見ると、同じアプリの中でForm Objectのインターフェイスがまちまちになるのを何とかしたくて作ったようです」「そういうアプリはそもそも書き方がよくないので修正しておくべきでしょうね」「自分もそう思います」
「話がちょっとそれますけど、記事の見出しに『Bob Ross of form objects』とあったのを調べると、30分ぐらいですごい絵を描く『ボブの絵画教室』という米国のTV番組のことみたいです」「ああ、そんな番組ありましたね」「ほら簡単でしょって言いながら描くけど簡単じゃないという番組😆」「このgemでForm Objectを簡単にやれるというニュアンスかなと思いました」
⚓ Form Objectよもやま話
「ところで、最近Form Objectという用語を以前ほど見かけなくなった印象がちょっとあります」「Form Objectという呼び方に『Form』という単語が使われているんですけど、その単語に引きずられて、HTMLビューのフォームを抽象化するものだという印象を与えている気がするんですよ」「Form ObjectがビューのHTML UIに寄っているというか依存している感じがするということですね」「本来Form Objectは単体で呼び出しても構わないものなんですが、Form ObjectがビューのUIに一対一で結び付けられるものだと見なされてしまうと、クラスの自由度が下がると思うんですよ」「あ〜たしかに」
「自分はForm Objectを主にビジネスロジックの置き場所に使っていますしTechRachoにForm Objectの記事を書いたこともありますけど、Form Objectという名前で呼ぶと、書かれるコードもフォーム寄りになりやすいところがあるかなと思います」「たしかにそうなると汎用性が落ちそうですね」
「Form Objectという呼び方は、そういう誤解を招きそうなところがもしかするとあまりよくないのかなと最近思っているところです」「かといってこれをService Objectと呼ぶのも違うんですよね...」「う〜む」
「いわゆるビジネスモデルオブジェクトのような感じの名前がいいのかな?🤔」「UIのフォームに相当するモデルオブジェクトではあるけど、ビジネスモデルという言葉を使うことで、コントローラでの利用に限定せずにrakeタスクやバッチなどのCLIで使ってもいいんだよ、というニュアンスを出せたらいいなと思うんですけどね」「そういう雰囲気を伝えたいということですね、ちょっと腑に落ちました」
「たとえばBusiness Objectのような用語で呼ぶ方がよいのかもしれないとちょっと思いました」「あとForm Objectと呼ぶとフロントエンド側と共同作業するときにも相性が悪そうな気がしますね」「ああたしかに」「Business Objectと呼ぶことにしてUIの差異はコントローラで吸収するということにすれば、フロントエンド側に受け入れてもらいやすそう」「ともあれ、呼び出し元からの値を設定したりバリデーションしてエラーを出したりsaveしたりできるクラスが欲しくなるのはたしかで、それがBusiness Objectなのかもしれないと思いました」
⚓ Ruby on Rails Discussionsより
つっつきボイス:「Dockerなどに関するRailsの公式なデプロイ方法が欲しいとありました」「たしかにRailsガイドにはデプロイに関する記述がありませんよね」「Capistoranoの情報ぐらいは公式にあってもいいのかなと思いましたけど」「今ならCapistoranoよりDockerでしょう」
「ところでこのディスカッションにrails.github.io↓というサイトへのリンクがあるけど、Rails 4.2.0とか書かれていて相当古くありません?」「え、このサイト初めて見ました!」「rails.github.ioは更新するか消すなど何か対応してもよさそうな気がしますね」
⚓ erb-lint gem
# 同リポジトリより
linters:
AllowedScriptType:
enabled: true
allowed_types:
- 'application/json'
- 'text/javascript'
- 'text/html'
allow_blank: false
disallow_inline_scripts: false
つっつきボイス:「Shopifyがforkしたgemで、fork元より★が多いです」「ERBのlinterってあるんですね」
# 同リポジトリより
Not allowed ❌
<a onclick="alert(<%= some_data %>)">
Allowed ✅
<a onclick="alert(<%= some_data.to_json %>)">
Not allowed ❌
<script>var myData = <%= some_data %>;</script>
Allowed ✅
<script>var myData = <%= some_data.to_json %>;</script>
「こういうふうにERBで暗黙のto_json
を禁止したりできるのか↑」「この書き方やっちゃいそう...」「erb-lintはRuboCopとも連携できるのね」「設定ファイルのallowed_types
でホワイトリストも指定できる」「このgem入れてみようかな」「ERBでlintをかけたい場合によさそう: 取りあえず入れてみてもいいんじゃないかと思います👍」
「ところでこのチェックちょっと面白い↓」「あ、小文字のiがï
になってる😳」
# 同リポジトリより
Bad ❌
<script type="text/javacsrïpt"></script>
Good ✅
<script type="text/javascript"></script>
⚓ その他Rails
つっつきボイス:「factory_botなど多くのgemでおなじみのthoughtbotが、Railsアプリの多言語化に関するイベントを開催するそうです」「多言語対応には独自のつらみがいろいろありますね」「多言語化はデザインにも影響するのが大変」「アラビア語みたいなRTLの言語だとメニューの配置も変えないといけなくなったりしますね」
参考: RTL (右書き) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
前編は以上です。
バックナンバー(2020年度第4四半期)
週刊Railsウォッチ(20201021後編)webpack 5リリースでWebpacker対応開始、AWS Lambda Extensions発表、Pythonにマクロ構文追加提案ほか
- 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など)です。