Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ(20210125前編)Railsリポジトリのデフォルトブランチがmainに変更、Rails 6.1はMySQLのENUM型に対応済みほか

こんにちは、hachi8833です。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今回は以下のコミットリストを中心に見繕いました。

type_for_attribute周りを修正

マージされたプルリク#39929によって、cast_type (:encrypted)でprev_decoratorstoreの中のserialize)を用いると#41138のリグレッション(注: 修正したバグが再発すること)が発生した。

複数のデコレーション(元の属性のstore:encrypted)はこれまで全くサポートされていなかったので、これまで動いていたのは偶然に過ぎないが、サブクラスの型の省略を許すことと引き換えに複数のデコレーション機能が動かなくなるようにはしたくない。

個人的には、たとえ以下のようにサブクラス側で型を省略可能だとしても、開発者がサブクラス側で型を省略することはおすすめしたくない。

# 同PRより
# app/models/post.rb
class Post < ActiveRecord::Base
  attribute :x, :integer
end

# app/models/special_post.rb
class SpecialPost < Post
  # :integerは省略可能だが、明快さが落ちる
  attribute :x, default: 42

  # :integerは以下のように省略なしで記述することを推奨
  attribute :x, :integer, default: 42
end

コメント94ba417#r41923157の視点もこれに似ている。

修正されたissue: #41138
同PRより大意

参考: リグレッション(りぐれっしょん) - ITmedia エンタープライズ


つっつきボイス:「そういえばRailsのActiveRecord::Attributesにはこういう機能がありますね(自分はこの書き方はあまりしないんですが): 現在は上のように、元のクラスを継承した後でActiveRecord::Attributesを上書きすると、元のattributeのintegerが上書きされて消えることになる」「おぉ」「上のコードの末尾にあるこの:intergerは、実際には省略しないで明示的に書くべきという指摘もされてますね」

「issue #41138の方が見やすいかな↓: 元のコード(上)にあったものが、継承したコード(下)で上書きされている」「なるほど!」「プルリクはこのリグレッションの修正ということですね」「修正に伴って#39929も取り消されていました」

# issue #41138より
class Cache < ApplicationRecord
  store :data
  encrypts :data
end
# issue #41138より
def encrypts(name)
  attribute name, :encrypted, subtype: type_for_attribute(name)
end

「元のクラスの他のattributeは自分が使わないとしても、storeencryptedあたりは使うこともあるでしょうから、知らないうちにこの問題を踏むということはありそう」「名前が重複するから、これまで上書きされていたのも無理はないか」「内部の処理が見えないと、この問題を踏んでも何が起こったのか見当がなかなかつかなくて厄介でしょうね」

Rails5: ActiveRecord標準のattributes APIドキュメント(翻訳)

timeのunknown type errorを定義時にraiseするようにした


つっつきボイス:「これもActiveRecord::Attributesの改修かな: unknown type errorをランタイム時ではなく定義時に出力するように変わってますね」

# activerecord/lib/active_record/attributes.rb#L215
        when Symbol
-         type = cast_type
-         cast_type = -> _ { Type.lookup(type, **options, adapter: Type.adapter_name_from(self)) }
+         cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self))

「postgresql/datatype_test.rbのテストにも手が入っているから、PostgreSQLアダプタ関連の修正なのかな」「#41166のコメントを見ると、PostgreSQLアダプタでの場合分けが必要だったみたい」

「定義時にエラーとわかったらその時点でエラーにしようよということですね」「検知できるエラーは早いタイミングで出すべきというのはもっともですね」

高速化: empty?length == 0に変更


つっつきボイス:「こちらは高速化のプルリクです」「条件のassociations.nil?を冒頭に移動したのと、records.empty?records.length == 0に変えたのと、2つの変更が行われてますね↓」「あ、タイトルの変更以外にもうひとつ変更があったのか」「associations.nil?の方が処理が速いのかもしれない」

# activerecord/lib/active_record/associations/preloader.rb#L100
      def call
-       return [] if records.empty? || associations.nil?
+       return [] if associations.nil? || records.length == 0

        build_preloaders
      end

「改修後は1.3倍ほど速くなったようです↓」「そういえばRubyの書き方としては== 0より.empty?の方がRubyらしいとよく言われますね」「== 0にするとメソッド呼び出しを行わなくなる分速くなるのかな?」「ともあれ、この測定結果だけでは、== 0への変更とassociations.nil?の移動のどちらがどれだけ効いているかはわからないですね」「たしかに」

before:

Warming up --------------------------------------
prefill associations   252.000  i/100ms
Calculating -------------------------------------
prefill associations      2.549k (± 1.4%) i/s -     12.852k in   5.043288s
After:

Warming up --------------------------------------
prefill associations   335.000  i/100ms
Calculating -------------------------------------
prefill associations      3.361k (± 0.5%) i/s -     17.085k in   5.082847s

【保存版】Rubyスタイルガイド(日本語・解説付き)総もくじ

rails newのGitデフォルトブランチ指定オプションを--mainに変更


つっつきボイス:「お、ついにrails newするときのGitデフォルトブランチ名指定オプションが--masterから--mainに変わったのか」「はい、とうとう」「今はいろんなところでGitのデフォルトブランチ名をmainにするのが世の中の流れですよね」「--no-master--no-mainに変わってる」

参考: 新しいGitHubリポジトリではmainブランチがデフォルトに


なお昨年にも同様のPRが上がっていたのを見つけましたが、こちらはopenのままです↓。

🔗 Railsリポジトリのmasterブランチもmainに変更された

「上の--mainオプションの他にこの変更も行われました」「--mainなしのrails newでもmainブランチができる…のかと思ったら、RailsフレームワークそのもののGitHubリポジトリまでデフォルトブランチがmainブランチに変わったのか!」「はい、『先週の改修』用に今週のコミットリストを取ろうとしたらいつものURLが無効になっていたことで気が付きました↓」


github.com/rails/railsより

「ついにRails本体までmainに変わるのかー」「RailsのCIもいろいろ変えないといけなくなるんでしょうか?」「Rails本体のmasterが急に消えるとえらいことになりそうなので、さすがにしばらくはmastermainが共存しそうな気がしますけどね(調べる): …本当にmasterブランチない↓」

「もしかするとmasterがGitHubのプルダウンにないだけで、masterブランチそのものは取れるんじゃないかな?: やっぱりなかった↓」「完全にmainに移行しちゃったんですね…」

「普通はあまりしないと思いますが、自分たちのCIでGitHubのrails/railsリポジトリを指定するときに気を利かせてmasterブランチまで指定していたら落ちそうではある」「あ、そうかも」


念のため自分も後でやってみましたが、やはりmasterブランチはありませんでした。

$ git clone https://github.com/rails/rails.git; cd rails
# 略
$ git co master
error: pathspec 'master' did not match any file(s) known to git
$ git branch -a
* main
  remotes/origin/1-2-stable
  remotes/origin/2-0-stable
  remotes/origin/2-1-stable
  remotes/origin/2-2-stable
  remotes/origin/2-3-stable
  remotes/origin/3-0-stable
  remotes/origin/3-1-stable
  remotes/origin/3-2-stable
  remotes/origin/4-0-stable
  remotes/origin/4-1-stable
  remotes/origin/4-2-stable
  remotes/origin/5-0-stable
  remotes/origin/5-1-stable
  remotes/origin/5-2-stable
  remotes/origin/6-0-stable
  remotes/origin/6-1-stable
  remotes/origin/HEAD -> origin/main
  remotes/origin/activemodel-multiparam-attrs
  remotes/origin/add-github-actions-to-rails
  remotes/origin/add-spaces-integration-test
  remotes/origin/add_autoload_paths_to_load_path
  remotes/origin/backport-30638
  remotes/origin/bump-z
  remotes/origin/conductor6
  remotes/origin/ditch-premature-optimization-for-collection-cache-keys
  remotes/origin/do-no-shallow-name-errors
  remotes/origin/fix-array-builder-wheres
  remotes/origin/fix-as-with-api-apps
  remotes/origin/fix-build
  remotes/origin/fix-duration-modulo
  remotes/origin/fix-find-root-on-ruby-2-2
  remotes/origin/fix-nil-params-in-ap
  remotes/origin/fixtures-refactor
  remotes/origin/freeze-configuration_hash
  remotes/origin/fxn/docker
  remotes/origin/fxn/load-paths-per-environment
  remotes/origin/issue-33155
  remotes/origin/json-visitor
  remotes/origin/main
  remotes/origin/matthewd/inotify
  remotes/origin/modernize-scaffold
  remotes/origin/new-autoloading-guide
  remotes/origin/no-html-fallback
  remotes/origin/postgresql_type_map_lookup_fix
  remotes/origin/proxy-pg-result
  remotes/origin/railties-split
  remotes/origin/record-timezone-when-writing-from-user
  remotes/origin/remove-json-tests
  remotes/origin/remove-qc-ivar
  remotes/origin/settings-file
  remotes/origin/test-remove-marshal-support-from-schema-cache
  remotes/origin/tzinfo2
  remotes/origin/use-any-bundler
  remotes/origin/verify-release
  remotes/origin/ಠ_ಠ

rails/rails - GitHub

closed: ‘Directly inheriting’メッセージを修正


つっつきボイス:「これはclosedですね?」「マージされてないプルリクですが、気になったので拾ってみました」

「プルリクのコメントを読むとclass #{subclass} < ActiveRecord::Migration[4.2]4.2は、元々Rails 4.2以前の仕様でバージョン番号なしで実装されていたものに対するエラーメッセージなんですが、Rails 4.2以前の仕様で書かれたものを最新のmigaration number(最新では6.1)に書き換えて使ってしまうのは、前方互換が無くなった機能などを踏むリスクがより高いので適切ではないだろうということのようですね」「あ、そういうことでしたか!」

# activerecord/lib/active_record/migration.rb#L556
    def self.inherited(subclass) #:nodoc:
      super
      if subclass.superclass == Migration
+       major = ActiveRecord::VERSION::MAJOR
+       minor = ActiveRecord::VERSION::MINOR
        raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
-         "Please specify the Rails release the migration was written for:\n" \
+         "Please specify the Active Record release the migration was written for:\n" \
          "\n" \
-         "  class #{subclass} < ActiveRecord::Migration[4.2]"
+         "  class #{subclass} < ActiveRecord::Migration[#{major}.#{minor}]"

Rails

Rails 6.1はMySQLのENUM型に対応済み


つっつきボイス:「BPSの社内Slackに貼っていただいた@kamipoさんのツイートです」「そうそう、MySQLについてはいち早くRailsが6.1でEnumに対応しているということでしたね」

「先週のウォッチ(ウォッチ20210120)でRailsとPostgreSQLのENUMサポートについての話になったときに、RailsがデータベースのENUM型に対応していた話を以前もしていたような気がして仕方がなかったんですが、あれはこのMySQLのENUM型をサポートしたときの話だったのかも」「私も思い出せませんでした😅」

このコミットは昨年7月にマージされていました↓。

enumset:stringとして型キャストされるが、現時点の:string型はスキーマダンプでの再利用方法が正しくない。
あるカラムのキャスト型は常にsql_typeと同じとは限らないので、このプルリクではenumsetのスキーマダンプが(typeではなく)sql_typeを正しく使うよう修正した。
同PRより大意

「この追加機能はマイグレーションに多少影響しそう」「DBMS依存の部分も結構ありそうな気がしました」

最近のRailsテストに関するアンケート(Ruby Weeklyより)


つっつきボイス:「TechRacho翻訳記事でもお世話になっているarkencyによるツイートまとめ記事です」「記事に貼られているツイートはこれね↓」「RSpec派は8割、Minitest派は2割か」

「これ↓を見ると受け入れテスト(acceptance test: ここではCucumberなどによるRailsの自動テスト)を書いている人もそれなりにいるのね」「ホントだ」

「今思ったんですが、ここで受け入れテストを書いていると回答している人は、昔BDD(Behavior Driven Development)が流行った頃にそのプロジェクトでCucumberやTurnipのテストが書かれたからじゃないかな」「それ、ありそうですね」「書かれたからにはメンテし続けないといけなくなるので、そういうプロジェクトの人がそう回答していることが多いのかなと想像してみました」

参考: ビヘイビア駆動開発 - Wikipedia

cucumber/cucumber - GitHub

jnicklas/turnip - GitHub

「プロジェクト規模: データベースのテーブルが100個を超えてるプロジェクトが3割ほどある↓」「テーブル100個ですか…」「Railsアプリが育つとテーブル数もこのぐらいにはなりますよ」「Railsで書かれたプロジェクトがそれだけ育って大きくなったという傾向をある程度表しているかなと思います: ただこのアンケートの母数が142とあるので調査としてはかなり小規模ですが」「そのつもりで読まないといけないということですね」

「チームの規模↓は三分の一ぐらいが1〜3人ぐらいか: Railsだとそのぐらいの規模のチーム編成が多いでしょうね」

「テスト1件あたりの実行時間↓は、半分以上が5秒以内」「あ、5分かと思ったら5秒だった😅」「テスト全体じゃなくてテスト1件あたりの実行時間ならこのぐらいの感じかな」「しかしシングルテストで1分以上かかるようなテストを書くことってあるかな?」「結合テストなんかだとそのぐらいかかることもあるかもしれませんね」

「アンケートの母数の小ささを割り引く必要はありますが、Railsアプリ開発のテストに関するアンケートとしてはだいたい自分の感じるところに近そうに思えますね」

その他Rails


つっつきボイス:「今ならDockerでやるかなと思いつつ、新し目の記事なので一応貼ってみました」「Ruby 2.7.2を使ってる」「WSLにも2が付いてませんね」

参考: WSL 2 と WSL 1 の比較 | Microsoft Docs

「お、この記事ではWindows版のPostgreSQL↓をインストールしてますね」「あ、ホントだ」「GUIインストーラですか」

参考: PostgreSQL: Windows installers

「それにしてはapt-getも使ってるのが妙だな…起動もLinuxのsudo -u postgres -iコマンドだし」「あ、pgAdmin↓をインストールするのにapt-getを使ってるのか」

「Qiitaによくありそうなやってみた系記事かなという印象ですね」


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210120後編)Ruby 3.0の新機能で遊ぶ、RubyスニペットをJSに変換するRuby2JS、rspec-parameterized gemほか

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

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

Rails公式ニュース

Ruby Weekly


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。