Rails CHANGELOG について10記事を書いたのでふりかえる

ふりかえりをしておく。

おさらい

8月末に RailsのCHANGELOG.mdのフィードを購読し始めた - tanaken’s blog

9月のコミットを memo: Rails CHANGELOG 2019-09 - tanaken’s blog でまとめたが、1ヶ月分を一気に書くのはダルそうと思ったのでざっくり週1のペースで書いてみることに決めた。

書いた記事

決めたこと

  • いつ書くのか = 毎週月曜
  • なにを書くのか = 前週の月曜から日曜の夜までの各コミットについての説明
    • 「このコミットはなにをしているか」or「このコミットのおかげでどうなるのか」をひとことで表現する(= 見出し)
    • コミット内容の説明をざっくりと書く
  • どのように書くのか
    • できるだけコードを併記する(引用元を書く)
    • 簡単に動作確認できるものは動作確認をする
    • わからないことがあって良い、という気持ちで書く

ふりかえり:YWT(やった・わかった・つぎやる)

Y:やったこと

W:わかったこと

  • 毎週4コミットくらいについて読んでコメント書くのは1時間あればできる(そんなにしんどくない)
    • 倍くらい(2時間くらい)の量だと結構しんどい
  • サンプルコードが書かれているコミットは読みやすい
  • 自分は自分自身が思っているよりも Rails について知らない
  • RailsCHANGELOG.md を読んでも、そこに書かれていること自体がすぐに役立つことはあまりない
  • Active Storage についてよくわかっていない(ということがわかった)
  • インターネットについてよくわかっていない(ということがわかった)
    • ネットワークとか

T:つぎやること

おわりに

やってみてわかったことがいっぱいあった(主に、わからないことがわかった)ので良かった。

何かをやるとき10回継続してみると何か分かる気がする(経験則)。毎日やる系だと10日間でちょうどいいし、平日だけやると2週間になってちょうどいい。毎週やる系だと2.5ヶ月やって、残りの0.5ヶ月でふりかえりをして「次の2.5ヶ月なにやろうか〜」となるのでちょうどいい。気がする。

「つぎやること」には書いてないけど他にもやりたいこと(お仕事で Rust を使うけどまだあまりやってないのでやりたい、自然言語処理で遊びたい、など)がたくさんあるので、時間の使い方をどうするのか考えたい。もうすぐ2020年だし。

忍者スリスリくんの影武者を作っている

この記事は GMOペパボ Advent Calendar 2019 - Qiita の15日目の記事です。

kagemusha0

オリジナルグッズ作成・販売サービスのSUZURI(スズリ) には、公式忍者の「忍者スリスリくん(@suzurijp)」がいる。

@suzurijp

私はスリスリくんのファンだ。

そんなスリスリくんについて、1つだけ気になっていることがある。

それは

follower

忍者のくせにフォロワー数が多すぎることだ。

控えめに言って隠密行動に向いていない。露出しすぎ。

だがここまで露出してしまったことを悔いても仕方がない。この状況を打破するためには、何か対策を打つ必要がある。

こんな時に有効なのが「影武者」だ。

敵の目を欺くために、大将などと同じ服装をさせた身代わりの武者。

出典: 影武者(かげむしゃ)の意味 - goo国語辞書

忍者スリスリくんにそっくりの影武者がいれば、この問題の解決策になる可能性がある。 私は忍者スリスリくんのファンなので、彼の身の安全を確保し、安心して隠密行動をしてもらうために「忍者カゲムシャくん」を作ることにした。

忍者カゲムシャくんとは

作ったものがこれだ。https://kagemushakun.herokuapp.com/

kagemusha1

カゲムシャくんの見た目は特殊メイク*1でスリスリくんそっくりに作った。98%の人はカゲムシャくんだと気づかないだろう。

そして何かメッセージをいれて「しゃべらせる」を押すと↓

kagemusha2

まるでスリスリくんのようにしゃべらせることができる。スリスリくんは「マス」などの表現が独特なのだ。

これで99%の人を欺くことができる。

さらに「あいさつさせる」にチェックを入れてしゃべらせると↓

kagemusha3

スリスリくんの独特なあいさつを再現することもできる。小粋だ。

これでスリスリくんと完全一致する。作った私ですらカゲムシャなのかホンモノなのか区別がつかなくなってきている。

ぜひ忍者カゲムシャくんで遊んでみてほしい。リンクを再掲しておく。 https://kagemushakun.herokuapp.com/

ここから先は忍者カゲムシャくんのカラクリを書いていく。(ここでページを閉じてもらっても構わない)

忍者カゲムシャくんのカラク

忍者カゲムシャくんのカラクリを紹介する。

ただし、この情報は極秘情報だ。なぜなら、この情報が拡散されるとカゲムシャのカラクリが露呈し、スリスリくんの身に危険が及ぶかもしれないからだ。

むやみやたらに拡散してはいけない。この約束を守ると誓ってくれる人は、このページの一番下にある☆を押してほしい。*2

ラクリの概要

忍者カゲムシャくんは Ruby on Rails 6.0 で動いている。DBはいまのところ必要ないので使っていない。Rails 6 から Webpacker がデフォルトになっているが、その辺も不要なので --skip-javascript をつけて skip した。画面の見た目はCSSフレームワークBulma (正確には bulma-rails) を使って簡単なデザインをしている。特に変わったことはしていない。

カゲムシャくんの機能的な特徴はスリスリくんに似せた「しゃべり」と「あいさつ」だ。 これは suri_lang という rubygem を開発することで実現した。 この自作 gem について紹介していくことで、カゲムシャくんのカラクリを明らかにする。

ラクリ①「しゃべり」

スリスリくんのtwitterおよび筆者が独自ルートで手に入れた情報*3を元に、しゃべりの特徴を分析すると

  • 語尾の表記にカタカナを使う傾向がある
  • 次のような一部の単語はひらがなを使う傾向がある
    • 「等」「事」などの一部の名詞
    • 「是非」「沢山」などの一部の副詞
    • 「及び」「但し」などの一部の接続詞

ということが分かる。特定の単語を上記の法則に則って変換するだけで、まあまあスリスリくんっぽくなると予想できる。

これを実現するための手順は次の通りだ。

  1. 変換したい単語のリストを用意する
  2. しゃべらせたい文章を単語に分割(= 形態素解析)する
  3. 分割した単語をひとつずつ、変換したい単語のリストと比較する
  4. 変換したい単語と一致したら変換する

1. はコツコツ*4やる。
私はスリスリくんのファンなのでまったく苦にならずにリストを用意することができた。

2.MeCab という形態素解析エンジンを用いる。
厳密には MeCabRuby で扱いやすくした natto という gem を使って形態素解析をする。

natto を使うとこのような実装で形態素解析をすることができる。詳しい解説は natto/README.md at master · buruzaemon/nattoHome · buruzaemon/natto Wiki を参照してほしい。

require 'natto'

nm = Natto::MeCab.new
nm.enum_parse('私の名前は田中です').each do |elm| 
  print "#{elm.surface}\t#{elm.feature}\n"
end
# 私    名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ
# の    助詞,連体化,*,*,*,*,の,ノ,ノ
# 名前  名詞,一般,*,*,*,*,名前,ナマエ,ナマエ
# は    助詞,係助詞,*,*,*,*,は,ハ,ワ
# 田中  名詞,固有名詞,人名,姓,*,*,田中,タナカ,タナカ
# です  助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
#       BOS/EOS,*,*,*,*,*,*,*,*

3.4. はやや荒い実装だ。
まず 1. のリストから key が 変換前の単語,その品詞 , value変換後の単語 となる Hash オブジェクトを作成しておく。 そして 2. で分割した単語とその品詞を使って、その Hash にアクセスし、該当のキーがあれば 変換後の単語 を、なければ 変換前の単語 を返す。

require "natto"

module SuriLang
  class Translator
    DICTIONARY = {
      'です,助動詞': 'デス',
      'ます,助動詞': 'マス',
      # ... 略 ...
    }

    def self.translate(text)
      nm = Natto::MeCab.new('-F%m,%f[0]')
      features = nm.enum_parse(text)
                   .select{|n| !n.is_eos?}
                   .map{|n| n.feature}

      features.map{|feature| to_suri_word(feature)}.join
    end

    private

    def self.to_suri_word(feature)
      DICTIONARY[feature.to_sym] || feature.split(',')[0]
    end
  end
end

実際に suri_lang をこのように使ってスリスリくんのようにしゃべらせることができる。

require 'suri_lang'

SuriLang::Translator.translate('SUZURIの忍者スリスリくんです')
# => "SUZURIの忍者スリスリくんデス" 

ラクリ②「あいさつ」

私はスリスリくんのファンなので 忍者スリスリくん ( surisurikun )のジャーナルズ ∞ SUZURI(スズリ) をよく読んでいる。

すると、ある特徴に気づく。記事冒頭の小粋なあいさつだ。

例えばこれだ。

石器ブンブン! SUZURIの公式忍者、忍者スリスリくんデス。

引用: 忍者スリスリくん ( surisurikun ) の「 【終了】ビキビキ!800円引きセール 」というジャーナル ∞ SUZURI(スズリ)

意味はわからないが、なんとなく可愛い。

また、例えばこのように

そうめんツルツル!忍者スリスリくんデス。

引用: 忍者スリスリくん ( surisurikun ) の「 人気のTシャツに学ぶ!デザインのヒント 」というジャーナル ∞ SUZURI(スズリ)

夏の風物詩である「そうめん」を使った、季節感のあるあいさつもある。どうやら季節に合わせてあいさつを変えているようだ。

これを実現するための手順は次の通りだ。

  1. 忍者スリスリくんのジャーナルから冒頭のあいさつをリストアップする
  2. あいさつをyamlファイルにしておく
  3. ランダムにあいさつを表示する

1. はコツコツ*5やる。
私はスリスリくんのファンなのでまったく苦にならずにリストアップすることができた。

2. は以下のようなyamlファイルを作った。

- message: 石器ブンブン! SUZURIの公式忍者、忍者スリスリくんデス。
  source: https://suzuri.jp/surisurikun/journals/2019-05-07
  season: none
- message: そうめんツルツル!忍者スリスリくんデス。
  source: https://suzuri.jp/surisurikun/journals/2019-07-02
  season: summer
- ...

3.message, source, seasonインスタンス変数とする Greeter クラスを実装し、yaml を読み込んで作られる Array からランダムに1つ取り出して Greeter クラスのインスタンスを作るようにした。

実際に suri_lang をこのように使うとあいさつさせることができる。

require 'suri_lang'

greeter = SuriLang::Greeter.random_build
greeter.message
# => "石器ブンブン! SUZURIの公式忍者、忍者スリスリくんデス。" 

忍者カゲムシャくんの今後の展望

以上が忍者カゲムシャくんのカラクリだ。長々と書いてきたが、大したことはしていない。

今後の展望として、改良点を2つ考えている。

改良点①「入力された文章の季節に合わせてあいさつをさせる」

例えばこのように、冬の内容の文章を入力しても、真夏のようなあいさつをしてくる場合がある。

kagemusha4

忍者スリスリくんは賢いので、このような季節が食い違う発言はしない。

これについては、「入力された文章がどの季節っぽいか」を判定するカラクを実装しようと考えている。

具体的には、次のような手順を想定している

  1. なんらかの文章データを用いて、なるべく多くの単語の分散表現(ベクトル)を用意する
  2. 各季節を代表する単語(例えば、春を代表する単語として「桜」など)をリストアップする
  3. 入力された文章を構成する単語のベクトルと各季節を代表する単語のベクトルとを比較し、ベクトルの類似度を数値化する
  4. 各季節ごとに類似度の合計値を算出し、類似度が低い季節を特定する
  5. 類似度が低い季節のあいさつは選択されないようにする

この手順でいい感じになるかはやってみないと分からない。

1. の「文章データ」を決めかねている。「スリスリくんがしゃべる単語」という意味ではスリスリくんのジャーナルやtwitterでの発言を対象にすると良さそうだが、データ量が限られているため語彙数が少ない懸念がある。そうなると入力された文章の単語の多くが「未知語」と判定されてしまう可能性があり、各季節との類似度判定の精度が低くなりそうだ。

単語の分散表現の獲得については、(Deep Learning の勉強も兼ねて)推論ベースのアプローチを取るつもりだ。具体的には CBOW(continuous bag-of-words) モデルの word2vec を実装してみる。

2. の「各季節を代表する単語」の選び方も決めかねている。主観で良いのか?和歌の季語を活用してみるか?などを考えている。

3. 4. 5. は、そもそも単語ごとの類似度の数値化をすることで文章の季節感を表現できるのか?という懸念がある。例えば文章中に否定語が入っている場合に、想定外に季節の類似度が評価されてしまう可能性がある。(「今は桜が咲いていない」= 「桜」という単語が評価されて「春」だと判断されてしまう、など)

とはいえ、やってみないと分からないので、やってみてから考えようと思う。

改良点②「リスト取得の自動化」

今回は「しゃべり」と「あいさつ」に必要なデータを手動で獲得したが、これではメンテナンスコストが掛かりそうだ。

「しゃべり」に関しては、スリスリくんの発言から特徴的なカタカナ表記やひらがな表記を、 「あいさつ」に関しては、スリスリくんのジャーナルから冒頭文の数行を、 それぞれ自動で抽出できるようにしていこうと考えている。

具体的な手順は検討中だ。

まとめ

この記事では、忍者スリスリくんの影武者である「忍者カゲムシャくん」を紹介し、そのカラクリについて説明した。 今後も「忍者カゲムシャくん」の改良を続けていくので暖かい目で見守ってほしい。

*1:画像加工とも呼ばれる。怒られないか心配だ。

*2:押してくれると筆者がよろこぶ。このもう少し下に☆があるはず。SNSシェアでも可。

*3:実は社内にいい感じのドキュメントがある。入社したら読める。we are hiring!

*4:小さなことからコツコツと。大切にしていることだ。

*5:小さなことからコツコツと。大事なことだから2回言った。

Rails CHANGELOG "2019-12-02".."2019-12-09"

この期間の CHANGELOG.md へのコミットは11件。

railtiesに関して3件、ActiveStorageに関して4件、ActiveRecordに関して1件、ActionViewに関して1件、ActionPackに関して2件。

多いすね。

railties

各環境の設定ファイルで autoload_paths, autoload_once_paths, eager_load_paths を指定できるようになった

コミット: registers 43863bf in the CHANGELOG [skip ci] · rails/rails@76bafb0

実装の中身は let environments configure load-related paths · rails/rails@43863bf これ。

このコミットは CHANGELOG に書いとくね〜 というやつ。

config/environments/*.rb にこんな感じで書けば良い

# In config/application.rb, for example.
require "#{Rails.root}/lib/my_app/config"

# In config/environments/development.rb, for example.
config.foo = MyApp::Config.foo

引用: registers 43863bf in the CHANGELOG [skip ci] · rails/rails@76bafb0

Rails.application.config_for の戻り値に対して、文字列キーでのアクセスを禁止した

コミット: Remove deprecated non-symbol access to nested config_for hashes · rails/rails@e5e9c55

この修正前は deprecated になっていた。 シンボルキー以外でアクセスすると warning が出る。

ActiveSupport::Deprecation.warn(<<~MESSAGE.squish)
  Accessing hashes returned from config_for by non-symbol keys
  is deprecated and will be removed in Rails 6.1.
  Use symbols for access instead.
MESSAGE

引用: Remove deprecated non-symbol access to nested config_for hashes · rails/rails@e5e9c55

この修正後はシンボルキーのみでアクセスできるようになる。

どうしても文字列キーでアクセスしたければ with_indifferent_access を使えば?みたいな話も書いてある。

shared_configdeep_merge するようにした

コミット: Add CHANGELOG entry for 4d858b3f2a · rails/rails@2b17c11

詳細は Rails::Application#config_for merges shared configuration deeply by kirikiriyamama · Pull Request #37913 · rails/rails

このようなyamlがあった場合、

# config/example.yml
shared:
  foo:
    bar:
      baz: 1
development:
  foo:
    bar:
      qux: 2

引用: Add CHANGELOG entry for 4d858b3f2a · rails/rails@2b17c11

config_for を使うと baz 消えてしまっていた。baz が消えないように修正されている。

# Previously
Rails.application.config_for(:example)[:foo][:bar] #=> { qux: 2 }
# Now
Rails.application.config_for(:example)[:foo][:bar] #=> { baz: 1, qux: 2 }

引用: Add CHANGELOG entry for 4d858b3f2a · rails/rails@2b17c11

deep_merge に関してはこちら

ActiveStorage

Variant#service_urlPreview#service_url が非推奨になり、 #url が推奨になった

コミット: Deprecate `service_url` in favour of `url` · rails/rails@235f263

「Blobと一貫性を持たせようぜ」的なことが書いてあったので過去のコミットを掘り出した。
Permanent URLs for public storage blobs · rails/rails@feab703
Rails CHANGELOG "2019-10-07".."2019-10-13" - tanaken’s blog で紹介していた。

このときも #service_url を非推奨にして #url を推奨にしているので、合わせようぜ〜という話。

Blob の variant を DB に保存するようにした

コミット: Track Active Storage variants in the database · rails/rails@7d0327b

ActiveStorage::Blob::Representable でDBを使っているなら ActiveStorage::VariantWithRecord を使うように修正されている

def variant(transformations)
  if variable?
    variant_class.new(self, transformations)
  else
    raise ActiveStorage::InvariableError
  end
end

# 中略

private
  def variant_class
    ActiveStorage.track_variants ? ActiveStorage::VariantWithRecord : ActiveStorage::Variant
  end

抜粋: rails/representable.rb at 7d0327bbbf9fdb1f7f988ebb4e86649a776d1c65 · rails/rails

PublicDiskController を削除し DiskController に統一した

コミット: Use DiskController for both public and private files · rails/rails@fbb83d7

リファクタリング

DiskService で作られたURLに service_name フィールドを追加することで、DiskController は複数のサービスを処理できるようになっている。

DiskService のクエリパラメータのうち、dispositioncontent_type は未使用だったので削除した

コミット: Remove query params in DiskService · rails/rails@2e15092

これもリファクタリング。

ActiveRecord

connect_to メソッドにキーワード引数 database を渡すと warning が出るようにした

コミット: Deprecate `database` kwarg from `connected_to` without replacement · rails/rails@254ba46

コミットメッセージによれば、キーワード引数 database をシャーディング(データベースを水平方向に分割すること)の用途で使おうとし、それに関する多くのバグレポートがあったとのこと。で、検討の結果このキーワード引数 database は一旦削除することにしたとのことで、今回 deprecated になった。

シャーディングをサポートする機能を追加するプランはあるらしい。

いまこのキーワード引数 database があるとなんかいろいろしんどいんだろうなぁ。バグレポートを無視するわけにもいかないだろうし。

ActionView

TagBuilder で条件による値の出し分けをできるようにした

コミット: Add support for conditional values to TagBuilder · rails/rails@f1c63d8

修正前はこう書かないと行けなかったところを

content_tag(
  "My username",
  class: "always #{'sometimes' if current_user.special?} another"
)

引用: Add support for conditional values to TagBuilder by joelhawksley · Pull Request #37872 · rails/rails

こう書けるようにした

content_tag(
  "My username",
  class: ["always", "another", { 'sometimes' => current_user.special? }]
)

引用: Add support for conditional values to TagBuilder by joelhawksley · Pull Request #37872 · rails/rails

ActionPack

system test の driver オプションを non-headless ブラウザ用の selenium driver に渡すようにした

コミット: forward system test driver options configured · rails/rails@769188e

コミットメッセージを見る限りそんな感じ。コードベースではあんまり理解できてない。

config/routes.rb の中で外部のroutesファイルをロードできるようにした

コミット: Bring back feature that allows loading external route iles: · rails/rails@33bf253

使い方はこんな感じ。 draw メソッドが生えている。

# config/routes.rb

Rails.application.routes.draw do
  draw(:admin)
end

# config/routes/admin.rb

get :foo, to: 'foo#bar'

引用: Bring back feature that allows loading external route iles: · rails/rails@33bf253

ルーティングがたくさんあるアプリケーションだと有用そう。

コミットメッセージによると2012年にこの機能はあったんだけど、リバートされていたとのこと。
Revert "Allow loading external route files from the router" · rails/rails@5e7d6bb

やっぱり便利っぽいから入れようぜ、的な議論があったらしい。

Rails CHANGELOG "2019-11-25".."2019-12-01"

この期間の CHANGELOG.md へのコミットは2件。

ActiveJobに関して1件、ActionPackに関して1件。

ActiveJob

ActiveJob::Exceptions.retry_on に キーワード引数 :jitter を追加することで、複数のjobの再試行が同時に行なわれないようにした

コミット: Add jitter to :exponentially_longer · rails/rails@5f76218

rails/exceptions.rb at 5f7621878dda1b3c77bf31e6fa44a32579782bc1 · rails/rails

determine_delay メソッドにもキーワード引数 :jitter が追加され、 Karnel.rand:jitter を使って計算した値を渡すことでリトライジョブの待ち時間がランダムになる。

これにより複数のリトライジョブが同時に実行されにくくなる。

ちなみに「単独のプロセスしか処理できない状況で複数のプロセスが同時に作られ、プロセスがリソースを奪い合う状態」のことをどうやら Thundering herd problem と呼ぶらしい。

ActionPack

redirect_to.action_controller の notifications に request を含めるようにした

コミット: Add request to the payload for notifications to redirect_to.action_co… · rails/rails@23b7382

似たような変更が前にもあったな。この辺り。

今回の変更で、リダイレクト先でもリクエスト元の情報を取り扱うことができるようになる、ということかな?

コミットメッセージに↓のように書いてあった。

This change will allow subscribers to the notification to report on anything related to the request that they might need

だいたい認識あってそうかな。たぶん。

鹿児島Ruby会議01 発表資料リンク集

2019/11/30 に開催された 鹿児島Ruby会議01 の発表資料へのリンクを集めた。

招待講演01 Ruby 3 の型解析に向けた計画(仮)

wip

一般講演01 bruby

wip

一般講演02 ruby-jp

一般講演03 ruby-vipsを利用した画像処理Tips

一般講演04 Haconiwaが越えたあの夏〜3年間を振り返る

一般講演05 RubyOSSコードリーディング

一般講演06 福岡の方から参りました Fukuoka.rb です

一般講演07 かごっま弁のDeep LearningRubyできばっ

一般講演08 How to make a gem with Rust

一般講演09 あまり知られていないRubyの便利機能

一般講演10 "regional” wasn’t going to mean “provincial”

一般講演11 Rubyで作るネット回線の自動速度測定ツール

一般講演12 Location-based API with Ruby

wip

招待講演02 Rails Girlsのお話や、初めての方向けのコミュニティについてなどお話

www.slideshare.net

Rails CHANGELOG "2019-11-18".."2019-11-24"

この期間の CHANGELOG.md へのコミットは6件。

ActiveRecordに関して1件、ActiveSupportに関して1件、ActionPackに関して4件。

そのほか Fixup CHANGELOGs [ci skip] · rails/rails@214f439 でマークダウンの整形(インデントの調整や文末のピリオド付与など)もされている。

ActiveRecord

nil を含むArrayをwhere句に指定した場合に、そのwhere句に対する unscope が正しく動作しないバグを修正した

コミット: Fix unscoped grouped where · rails/rails@12afdba

unscope ってなんだっけ

Removes an unwanted relation that is already defined on a chain of relations. This is useful when passing around chains of relations and would like to modify the relations without reconstructing the entire chain.

引用: ActiveRecord::QueryMethods

「すでに定義されたリレーションから不要なリレーションを消すよ。これはリレーション全体を作り直すことなくリレーションを変更できるから便利だよ。」的な感じだ。

手元(Rails 6.0.1)でバグを確認してみる

[1] > users = User.where(id: [1, 2])
=>   User Load (0.9ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2)
[2] > users.unscope(where: :id)
=>   User Load (1.0ms)  SELECT "users".* FROM "users"
[3] > users = User.where(id: [nil, 1, 2])
=>   User Load (1.0ms)  SELECT "users".* FROM "users" WHERE ("users"."id" IN (1, 2) OR "users"."id" IS NULL)
[4] > users.unscope(where: :id)
=>   User Load (1.1ms)  SELECT "users".* FROM "users" WHERE ("users"."id" IN (1, 2) OR "users"."id" IS NULL)

なるほど確かに [4] で unscope が効いていない。これが効くように修正したとのこと。

Arel.fetch_attribute を修正している。

Allow attributes to be fetched from Arel node groupings.

引用: Fix unscoped grouped where by gmcgibbon · Pull Request #37733 · rails/rails

「ノードグループから属性を取得できるようにしたよ」ということなので、

多分ノードグループというのが手元で動かしたときの ("users"."id" IN (1, 2) OR "users"."id" IS NULL) のことで、ここから "users"."id" と特定できるようにしたよ、的なことじゃないかなぁと思う。

修正前まではこれが特定できなかったから、 unscope しようとしてもその対象が見つからないため、 unscope できなかったんじゃないかな。

ActiveSupport

定数名の修正

コミット: Follow up 997770f5955a36f0c800388c4592c961e184aec4 · rails/rails@9714d5d

Check the actual constant used · rails/rails@997770f の修正の follow up

元々の修正を辿ると、issue は これ `cpu_time` in `ActiveSupport::Notifications::Instrumenter` using incorrect clock? · Issue #37441 · rails/rails で、CLOCK_PROCESS_CPUTIME_ID じゃなくて CLOCK_THREAD_CPUTIME_ID を使おうぜ〜、というやつ。

CHANGELOG の定数名が CLOCK_PROCESS_CPUTIME_ID のままだったので CLOCK_THREAD_CPUTIME_ID に書き換えている。

ActionPack

ActionController::Parameters にkeyの存在確認をする member? メソッドを追加した

コミット: Add params.member? to mimic Hash behavior · rails/rails@34b6e8c

もともと has_key? はあったから機能的には十分だったはず。

だけど、 Hashが has_key?member? を両方使えるのに ActionController::Parameters では has_key? しか使えないのはややこしい?から統一した、という感じかなぁ。

システムテストでスクショを保存するとき、pathの / \- に書き換えるようにした

コミット: Remove slashes and backslashes from image paths · rails/rails@3c3b80e

ActionDispatch::SystemTesting::TestHelpers::ScreenshotHelper の修正。

スクショのディレクトリが階層構造になっていると tmp:clear でファイルを消そうとしたときにエラーになってしまうとのこと。

respond_to#any に関して、リクエストに基づくContent-Typeではなく、blockで指定されたContent-Typeを返すようにした

コミット: Modify respond_to behaviour always setting the request's content type: · rails/rails@42c5157

def my_action
  respond_to do |format|
    format.any { render(json: { foo: 'bar' }) }
  end
end

get('my_action.csv')

引用: Modify respond_to behaviour always setting the request's content type: · rails/rails@42c5157

修正前はCSVが返るようになっていたけど、修正後はJSONが返るようになる。

もし今 respond_to { |format| format.any } を使っているコードがある場合、この修正前後でアプリケーションの挙動が変わる可能性があるので注意が必要。

↑と同じ内容

コミット: Merge pull request #37617 from Edouard-chin/ec-respond-to-contenttype · rails/rails@4fe7675

さっきのと全く同じ内容っぽいけど、コミットは別なのか 😮

Rails CHANGELOG "2019-11-10".."2019-11-17"

この期間の CHANGELOG.md へのコミットは4件。

ActiveRecordに関して2件、ActiveSupportに関して1件、ActionPackに関して1件。

ActiveRecord

implicit_order_column を指定しているとき、結果を確定的にするために primary_key でもソートするようにした

コミット: Additionally order by primary key if implicit_order_column is not uniq · rails/rails@31d31fc

もともとimplicit_order_column= メソッドのドキュメントには

Note that using a non-unique column can result in non-deterministic results.

引用: ActiveRecord::ModelSchema

「一意じゃないカラムを指定すると結果が確定的にならないから気をつけてね」と書いてある。

implicit_order_column に指定したカラムに重複している値がある場合に、環境の違いで取得するレコードの順番が変わったりする、ということ。

ああ、そういえば昔MySQLを使っていて、同じクエリでもInnoDBMyISAMで取得順序が違っている現象に遭遇したことがあるなぁ。

他にも同じクエリに対して取得順序が変わるケースがあったりするのかな。

で、「確定的な結果が得られた方が良いよね?」ということで今回の修正。

implicit_order_column に指定したカラムが primary_key ではない場合、その指定したカラムに加えて primary_key でもソートするようになったので、確定的な結果が得られるようになった。

ConnectionAdapters::Resolver を削除して DatabaseConfigurations を優先するようにした

コミット: Remove ConnectionAdapters::Resolver in favor of DatabaseConfigurations · rails/rails@8d5a4ff

ConnectionAdapters::ResolverDatabaseConfigurations は共通するメソッドが多い一方で微妙に一貫性がない部分もあって複雑だから共通化しようぜ?、ということ。

この結果として establish_connection メソッドに不正な引数(adapter が指定されていないHashなど)が渡された時に例外を吐くようになった。

ActiveSupport

ActiveSupport::Testing::TimeHelpers#travel_back にブロックを渡せるようになった

コミット: Accept block in travel_back time helper · rails/rails@21f907b

そもそも travel_back って何かというと、

Returns the current time back to its original state, by removing the stubs added by travel, travel_to, and freeze_time.

引用: ActiveSupport::Testing::TimeHelpers

travel, travel_to, freeze_time によって追加されたスタブを削除することで、現在の時刻を元の状態に戻すよ」というもの。

今回の修正でブロック引数に対応したので、そのブロックのなかだけ時間を戻せるようになった。

ブロックの外はスタブした時間のままになる。

具体例はこんな感じ↓

Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00

travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00

travel_back do
  Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
end

Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00

引用: rails/time_helpers.rb at 21f907bb51df95a26f129277fe817019b8c37f0a · rails/rails

ActionPack

process_action.action_controller の notifications に response を丸ごと含めるようにした

コミット: Provide the whole response · rails/rails@b5c6f33

Rails CHANGELOG "2019-11-04".."2019-11-09" - tanaken’s blog で紹介した、「 process_action.action_controller の notifications に Location レスポンスヘッダを含めるようにした」に関連した修正。

response.filtered_location だけ含めるのではなく、 response を丸ごと使えば良いじゃん、となったらしい。