こんにちは、hachi8833です。
Rails: 先週の改修(Rails公式ニュースより)
以下のコミットリストのChangelogを中心に見繕いました。
ActionController::Live::Buffer#writeln
が追加
DHH自らによるプルリクです。
# 同PRより
send_stream(filename: "subscribers.csv") do |stream|
stream.writeln "email_address,updated_at"
@subscribers.find_each do |subscriber|
stream.writeln [ subscriber.email_address, subscriber.updated_at ].join(",")
end
end
つっつきボイス:「末尾で改行されるwriteln
ができたんですね」「名前がどことなくJavaなどのprintln
風味かも」「Javaやったことないです…」「JavaやってたのもJava 5の頃なのであまり思い出せませんが」「私も同じぐらいJava忘れてますね」
参考: Javaで文字列を出力する:print()
, println()
| UX MILK
「writeln
はend_with?
を使って末尾の改行が重複しないように処理してくれている↓」「これは賢い」
# actionpack/lib/action_controller/metal/live.rb#166
# Same as +write+ but automatically include a newline at the end of the string.
def writeln(string)
write string.end_with?("\n") ? string : "#{string}\n"
end
to_str
が使えるオブジェクトもredirect_to
に渡せるようになった
- PR: Allow passing anything with `#to_str` into `redirect_to` by ojab · Pull Request #41390 · rails/rails
(
Addressable::URI
のように)#to_str
が使えるものなら何でもredirect_to
のlocationとして渡せるようにした。
ojab
Changelogより大意
つっつきボイス:「Addressable::URI
って何だろうと思ったら、どうやら外部のgemらしい↓」
「to_str
って普段使わないけど、これってto_s
とどう違うんだっけ?」「to_str
とto_s
の違いをググったらTechRacho記事が出てきた↓」「自分も同じ記事を見てます」「to_s
やto_i
みたいな短い変換メソッドは明示的な変換、to_str
やto_int
みたいな名前の長い変換メソッドは暗黙的な変換、だそうです」「う〜んまだ違いがピンとこない」
「記事を見ると、文字列を式展開しないで直接"string" + other
と結合すると、other
でto_str
が呼ばれるのか」「へ〜、other
で呼ばれるのはto_s
じゃないんですね!」「式展開ならto_s
が呼ばれます↓」
「Rubyのドキュメントも見ると、to_str
は『文字列が使われるすべての場面で代置可能』『それ自体が文字列とみなせるもの』のときにだけ定義する、と書かれてる」「to_str
が定義されているということは、文字列とみなしてよいものということなのかな」
参考: Object#to_str
(Ruby 3.0.0 リファレンスマニュアル)
オブジェクトの String への暗黙の変換が必要なときに内部で呼ばれます。デフォルトでは定義されていません。
説明のためここに記載してありますが、このメソッドは実際には Object クラスには定義されていません。必要に応じてサブクラスで定義すべきものです。
このメソッドを定義する条件は、
- 文字列が使われるすべての場面で代置可能であるような、
- 文字列そのものとみなせるようなもの
という厳しいものになっています。
docs.ruby-lang.orgより
「to_str
は定義されているとは限らないけど、to_s
↓はObject
に実装されているからすべてのオブジェクトに対して使えますね」
参考: Object#to_s
(Ruby 3.0.0 リファレンスマニュアル)
「元コードは、when
節のRegexp#===
比較が暗黙型変換(to_str
)で評価されるのにwhen
節内でoptionsをそのまま返してしまっていたため、_compute_redirect_to_location
の戻り値がStringにならないケースがあったのを、#41390では明示的にoptions.to_str
することで戻り値がStringであることを保証するようにしたんですね↓」
# actionpack/lib/action_controller/metal/redirecting.rb#101
def _compute_redirect_to_location(request, options) #:nodoc:
case options
...
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
- options
+ options.to_str
when String
request.protocol + request.host_with_port + options
when Proc
_compute_redirect_to_location request, instance_eval(&options)
else
url_for(options)
end.delete("\0\r\n")
end
fixtureのhas_many :through
でタイムスタンプを設定するようになった
has_many :through
関連付けのfixtureがjoin tableでタイムスタンプを設定するようになった。
以下のfixtureがあるとする。
### monkeys.yml
george:
name: George the Monkey
fruits: apple
### fruits.yml
apple:
name: apple
このjoin table (
fruit_monkeys
)がcreated_at
かupdated_at
を含む場合、fixtureの読み込み時に展開されるようになった。従来はこれらのカラムをrequireするとクラッシュし、しない場合はnullのままになった。
Alex Ghiculescu
同Changelogより大意
つっつきボイス:「fixtureで中間テーブルにタイムスタンプ定義がある場合にcreated_at
やupdated_at
が自動設定されずにnullになっていたのが、このプルリクで修正されたようです」「お〜」「修正されたのはactive_record_fixture_set/のfixtureですね↓」「中間テーブルにタイムスタンプを付けることはありうるので、修正されてよかった」
# activerecord/lib/active_record/fixture_set/table_row.rb#L37
+ def timestamp_column_names
+ ModelMetadata.new(@association.through_reflection.klass).timestamp_column_names
+ end
end
...
def add_join_records(association)
# This is the case when the join table has no fixtures file
if (targets = @row.delete(association.name.to_s))
table_name = association.join_table
column_type = association.primary_key_type
lhs_key = association.lhs_key
rhs_key = association.rhs_key
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
joins = targets.map do |target|
{ lhs_key => @row[model_metadata.primary_key_name],
rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
- join = { lhs_key => @row[model_metadata.primary_key_name],
- rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
+ association.timestamp_column_names.each do |col|
+ join[col] = @now
+ end
+ join
+ end
+ @table_rows.tables[table_name].concat(joins)
end
ActiveSupport::CurrentAttributes
のキーワード引数を修正
最近のRubyで(キーワード引数周りが)変更された後にキーワード引数を引き続き使いたい人向けのシンプルな改良。
respond_to?
変更は、RSpecでパーシャルのダブル(double)をチェックするときのCurrentAttributes
のスタブ化の問題を解決するのが目的。
キーワード引数が動かないという前提を置く理由がないので、ドキュメントは変更していない。
同PRより大意
つっつきボイス:「CurrentAttributes
のmethod_missing
でキーワード引数を中継できるように**kwargs
が追加されてますね↓」
# activesupport/lib/active_support/current_attributes.rb#L158
- def method_missing(name, *args, &block)
+ def method_missing(name, *args, **kwargs, &block)
# Caches the method definition as a singleton method of the receiver.
#
# By letting #delegate handle it, we avoid an enclosure that'll capture args.
singleton_class.delegate name, to: :instance
- send(name, *args, &block)
+ send(name, *args, **kwargs, &block)
+ end
+
+ def respond_to_missing?(name, _)
+ super || instance.respond_to?(name)
+ end
「今は、受け取った引数をすべて受け取ってdelegateできるようにするには、上のようにmethod_missing(name, *args, **kwargs, &block)
と書くのか」「へ〜、今はこうなんですね」「これはname
が必ず存在する前提ですが、そういうのがない場合は*args, **kwargs, &block
と書けばすべての引数を受け取れるんでしょうね」
ドキュメント更新: redirect_to
の危険な利用法について
つっつきボイス:「APIドキュメントとガイドの更新です↓」「redirect_to
にユーザー入力をそのまま渡すのは一般に危険、たしかに」「これはもうおっしゃるとおりとしか言いようがない」「そんなことをする人がいるんだろうかと思いますが、たぶんいたから明示的にドキュメントとガイドにも書くことにしたんでしょうね」
# guides/source/security.md#L345
-If it is at the end of the URL it will hardly be noticed and redirects the user to the attacker.com host. A simple countermeasure would be to _include only the expected parameters in a legacy action_ (again a permitted list approach, as opposed to removing unexpected parameters). _And if you redirect to a URL, check it with a permitted list or a regular expression_.
+If it is at the end of the URL it will hardly be noticed and redirects the user to the `attacker.com` host. As a general rule, passing user input directly into `redirect_to` is considered dangerous. A simple countermeasure would be to _include only the expected parameters in a legacy action_ (again a permitted list approach, as opposed to removing unexpected parameters). _And if you redirect to a URL, check it with a permitted list or a regular expression_.
Rails
@kamipoさん記事より
つっつきボイス:「MySQL 8.0のクライアントでMySQL 5.7のサーバーに接続、これやっちゃってました」「記事を見た感じではMySQLの接続のハンドシェイクでの問題なので、接続した後でSET NAMES
コマンドを渡して設定する分には大丈夫そうですね: ちょうど記事にも書いてあった↓」「お〜」
一応、接続後に
SET NAMES utf8mb4
すればサーバー側のutf8mb4のdefault collationが設定されるが、最悪のケースをカバーするために適切に設定してるひとには必要ない処理が増えて損をすることになるのでなんとか回避したい気持ちがあるけど、現状はそういう感じ。
同記事より
「元々この41403↓で上がってたissueを解決しようとしていたんですね」
「そうそう、記事にもあるように、MySQL 8.0.1からデフォルトのコレーションがutf8mb4_general_ci
からutf8mb4_0900_ai_ci
に変わっているんですよ↓: 後者はMySQL 5.7の頃にはなかったから認識されなくて、MySQL 5.7サーバーのデフォルトのコレーションにフォールバックしてたのか」「え、そこ変わっちゃってたんですか!」「MySQL 5.7サーバーには新しいutf8mb4_0900_ai_ci
のid:255
がないから、MySQL 8.0クライアントからこれを渡しても認識しようがありませんね」「このことを知らなかったら原因を見つけるのは難しそう…」
ここで表題の “MySQL 8.0のクライアントでMySQL 5.7のサーバーに接続するとcharsetが設定されないかもしれない” についてなんですが、MySQL 8.0.1からutf8mb4のdefault collationが
utf8mb4_general_ci
(id: 45)からutf8mb4_0900_ai_ci
(id: 255)に変更されたため、MySQL 8.0のクライアントがuff8mb4でサーバーに接続するとid: 255のcs_numberを送るけどMySQL 5.7はid: 255のcs_numberを知らないのでサーバー側のデフォルトの設定が採用されるという仕組み。
同記事より
AS句で作ったカラムにDBの型情報はない
つっつきボイス:「永和システムマネジメントさんのブログです」「『AS句で作ったカラムにDBの型情報はない』、そういうカラムはスキーマに定義がありませんのでそのとおりですね」
「ただ、これはActive Recordがどこまでよしなにマジックを効かせてくれるかという流れを何となくでも把握していないと、すぐには見当がつかないと思います」「あ、そういうことですか」「Active Recordが内部的にSHOW FIELDS(MySQLの場合)を使ってスキーマの型情報を取得していることを理解していれば、AS句で取ったカラムに型情報がないことは推測できると思います」「ふむふむ」「SHOW FIELDSはテーブルに対して実行するものなんですが、テーブルがなければSHOW FIELDSを実行できないので、AS句でSELECTした結果に対してはSHOW FIELDSできません」「なるほど」
「もしやりたければ、VIEW(データベースビュー)↓を作ればやれますよ: VIEWならSHOW FIELDSが使えるので」「あ〜、なるほど」
「記事にも、スキーマにないカラムの型はデフォルトでActiveModel::Type::Value
になると書かれていますね↓」
その過程で、AS 句で作ったカラムに型情報がつかない理由もわかります。 スキーマにないカラムはデフォルトで ActiveModel::Type::Value 型になるから、です。
同記事より
「今つっつきの場にいませんが、たしかBPS Webチームのkazz氏が、AS句で組み立てたクエリに何らかの方法で型ヒントを渡してカラムの型を認識させるというのをやっていた覚えがありますので、何か方法はあると思います」「お〜、やれそうなんですね」「でなければVIEWを使うか、さもなければAS句を使わないことでしょうね」
「Active Recordがあまりにいろんなことをよしなにやってくれるので、その内部構造に興味を持ってない人はこういうところでハマるでしょうね」「ですね、自分はハマる自信あります」
Date
の比較で<
や>
を混同しないようにする
つっつきボイス:「Boring Railsっていう名前が面白い」「Date
同士を比較するときには<
とか>
よりもActive Supportのbefore?
やafter?
の方が便利だよという話のようですね」「この辺は自分もよく不安になるので動かして確かめてます」
# 同記事
start_date = Date.new(2019, 3, 31)
end_date = Date.new(2019, 4, 1)
start_date.before? end_date
#=> true
end_date.after? start_date
#=> true
start_date = Date.new(2020, 8, 11)
end_date = Date.new(2018, 8, 11)
start_date.before? end_date
#=> false
「英語圏的にはbefore?
やafter?
の方がわかりやすいのかな?」「記事のコード例だと、start_date
が2019, 3, 31で、end_date
が2019, 4, 1だと、start_date.before? end_date
はtrueになる」「えっ、falseになるのかなという気がしましたけど?」「パッと見に逆かと思っちゃいますよね: 先行するstart_date
が主語で、それに対してend_date
がbefore?
と読まないといけないんでしょうね」「あ、それでちょっとわかってきたかも」「いつものようなオブジェクト指向的な語順で読まずに、普通の関数のような語順で読むとよさそう」「before?
とafter?
はRails 6で追加されたんですね↓」
参考: Rails 6 adds before? and after? to Date and Time | BigBinary Blog
「そういえば、このboringrails.comの他の記事にも反応を見つけました↓」「link_to_unless_current
って見たことなかった」
こんなメソッドあるの、知らなかった〜 > link_to_unless_current
Use Rails link_to_unless_current for navigation links | Boring Rails: Skip the bullshit and ship fast https://t.co/A9JlsZVmvj
— Junichi Ito (伊藤淳一) (@jnchito) February 24, 2021
「なるほど、この図↓のようにWebページのメニューなどで現在のページだけリンクを生成しないで、それ以外のリンクを有効にできるメソッドなのか」「あ、それちょっと便利かも」「こんなメソッドあるの知らなかった〜」
参考: link_to_unless_current
— ActionView::Helpers::UrlHelper
Rails 6.1.3がリリース
つっつきボイス:「お、Rails 6.1.3が出たんですね」「リリース情報見落としててRuby Weeklyの見出しで知りました」「今回の更新は少なそう」
前編は以上です。
バックナンバー(2021年度第1四半期)
週刊Railsウォッチ(20210222)ActiveRecord::Relationの新メソッドload_asyncとexcluding、Active Jobのperform_laterの改善ほか
- 20210209後編 Rubyでミニ言語処理系を作る、Kernel#getsの意外な機能、CSSのcontent-visibilityほか
- 20210208前編 Rails次期リリースがバージョン7に決定、thoughtbotのアプリケーションセキュリティガイドほか](/hachi8833/2021_02_08/103801)
- 20210202後編 Ruby 3 irbのmeasureコマンド、テストを関数型言語のマインドセットで考えるほか
- 20210201前編 Webpackerのガイドがマージ、RailsはRuby 3でどのぐらい速くなったかほか
- 20210126後編 Google Cloud FunctionsがRubyをサポート、Ruby 3のパターンマッチングでポーカーゲームほか
- 20210125前編 Railsリポジトリのデフォルトブランチがmainに変更、Rails 6.1はMySQLのENUM型に対応済みほか
- 20210120後編 Ruby 3.0の新機能で遊ぶ、RubyスニペットをJSに変換するRuby2JS、rspec-parameterized gemほか
- 20210113後編 Ruby 3.0 Ractor解説記事、Vercelホスティングサービス、教育用OS xv6ほか
- 20210112前編 Active Recordの範囲指定バリデーション改善、soleとfind_sole_byメソッド、AlgoliaとRailsほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)