週刊Railsウォッチ(20190507-1/2前編)Rails 6.0.0rc1が4/24にリリース、Rails 6の新メソッド群、RubyリポジトリがCgitに移行ほか

こんにちは、hachi8833です。10日間の休みを挟むと少々調子狂いますね。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

※今回のつっつきはGW前の4/25に行われました。

お知らせ: “令和初の” 第10回公開つっつき会

今週木曜開催の公開つっつき会、引き続き募集しています。当日エントリでもOKですので、皆さまお気軽にご応募ください😋。

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

ここにきて続々と新しいメソッドが追加されてます。

ActiveRecord::Relation#cache_versionを追加

# activerecord/lib/active_record/collection_cache_key.rb #L54
      if timestamp
-       "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
+       "#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
      else
-       "#{key}-#{size}"
+       "#{size}"
  • ActiveRecord::Relation#cache_versionを追加。これはActiveSupport::Cacheでバージョニングされたエントリ経由のキャッシュキーを再利用可能にする。これによりActiveRecord::Relation#cache_keyが返すキーにmax timestampやcountが含まれなくなり、キーが安定する。
    注: この機能はデフォルトでオフであり、ActiveRecord::Base.collection_cache_versioning = trueを設定しない限りcache_keyは引き続きtimestamp含みとなる。
    これはRails 6.0以後のすべてのアプリで用いられる設定である。
    Lachlan Sylvester
    ChangeLogより大意

つっつきボイス:「rc1が出ているこのタイミングで他にも新メソッドが続々増えていて攻めてる感ありました😳」「timestampを指定してキャッシュを取れていたということか」

dirty tracking関連の高速化とメソッド追加

# activemodel/lib/active_model/attribute_mutation_tracker.rb#L9
-   def initialize(attributes)
+   def initialize(attributes, forced_changes = Set.new)
      @attributes = attributes
-     @forced_changes = Set.new
+     @forced_changes = forced_changes
    end

    def changed_values
      attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
        if changed?(attr_name)
-         result[attr_name] = attributes[attr_name].original_value
+         result[attr_name] = original_value(attr_name)
        end
      end
    end

つっつきボイス:「あ〜Railsのdirty: どうしてこれがちゃんと動いているのか未だに不思議😅」「kamipoさんの高速化で2倍、ものによっては30倍ってスゲー🚀」「forced_changesが渡されたらそれを使い、渡されなければ初めてSet.newするのか: よく見つけるな〜こういうの」「その分メモリコピーが減って高速化するっぽいですね」「使い回せるSetがあればそれを使おうと」「そもそも今までもSet(集合)クラスを使ってたのね」

参考: class Set (Ruby 2.6.0)

「dirtyはRailsの中でものすごく使われているから、これを高速化する意義は大きいでしょうね☺️」

after_save_commitを追加

DHH自らのコミットです。

# activerecord/lib/active_record/transactions.rb#L237
+     def after_save_commit(*args, &block)
+       set_options_for_callbacks!(args, on: [ :create, :update ])
+       set_callback(:commit, :after, *args, &block)
+     end

つっつきボイス:「そもそもafter_saveafter_commitって何が違うんだろか?🤔」「after_saveはsaveだからDELETEがないヤツ?」「あー、commitはDELETEの場合も含むのかなるほど」

after_commit :hook, on: [ :create, :update ]のショートカットってありますね」「after_save_commiton:でCREATEとUPDATEを行うのね」「after_commitだとDELETEも含まれるからそれをスキップすると」「DELETE要らない派が多いのかも😆」

参考: after_commit — ActiveRecord::Transactions::ClassMethods

register_tagsSourceAnnotationExtractor::Annotationに追加

# railties/lib/rails/commands/notes/notes_command.rb#L5
module Rails
  module Command
    class NotesCommand < Base # :nodoc:
-     class_option :annotations, aliases: "-a", desc: "Filter by specific annotations, e.g. Foobar TODO", type: :array, default: %w(OPTIMIZE FIXME TODO)
+     class_option :annotations, aliases: "-a", desc: "Filter by specific annotations, e.g. Foobar TODO", type: :array, default: Rails::SourceAnnotationExtractor::Annotation.tags

つっつきボイス:「ソースアノテーションが今までOPTIMIZE FIXME TODOみたいにタグが生書きだったのをちゃんとデータ構造に入れたのか」「お〜」「rake notesでは今までもカスタマイズ可能ではあったけど、SourceAnnotationExtractorに責務を分けてやれるように変えたのね」

「このタグってどこで使うタグでしたっけ?」「これはソースコードに付けるタグですね🧐: 以前からrails notesで表示できますよ」「まあrailsコマンドでやらなくてもという気はしないでもない😆、CIなんかでTODOがソースコードに残っていたらmergeさせないみたいな処理をやるには便利ですね」「なるほど!」

参考: rails notes — Rails のコマンドラインツール - Rails ガイド

冪等なdb:prepareタスクを追加

# activerecord/lib/active_record/railties/databases.rake#L225
+ desc "Runs setup if database does not exist, or runs migrations if it does"
+ task prepare: :load_config do
+   ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+     ActiveRecord::Base.establish_connection(db_config.config)
+     db_namespace["migrate"].invoke
+   rescue ActiveRecord::NoDatabaseError
+     db_namespace["setup"].invoke
+   end
+ end

つっつきボイス:「db:prepareって今までなかったっけ?」「見た覚えがあるような気がするんですがこのコミット番号は過去のウォッチにはありませんでした」「このdb:prepareはデータベースがなければ作成して、あればマイグレーションを実行するヤツですね」「データベースがあるかどうかを調べずに実行できると」「だから冪等なんですね」

参考: 情報工学における冪等 - Wikipedia

Active Supportにdetach_fromを追加

# activesupport/lib/active_support/subscriber.rb#L47
      # Detach the subscriber from a namespace.
      def detach_from(namespace, notifier = ActiveSupport::Notifications)
        @namespace  = namespace
        @subscriber = find_attached_subscriber
        @notifier   = notifier

        return unless subscriber

        subscribers.delete(subscriber)

        # Remove event subscribers of all existing methods on the class.
        subscriber.public_methods(false).each do |event|
          remove_event_subscriber(event)
        end

        # Reset notifier so that event subscribers will not add for new methods added to the class.
        @notifier = nil
      end

つっつきボイス:「なるほど、サブスクライバからdetachすると」「detach自体は前からできてたと思うけど、これはnotifierのインスタンスがなくても名前空間を指定して一気にdetachできるっぽい」「コミットメッセージにあるこのコード↓がまさにそれか」「とりあえず便利メソッドとして使えるし、1つずつdetachするとマルチスレッドでタイミングの問題が生じそうなときにもよさそう」

# PRより
ActiveJob::Logging::LogSubscriber.detach_from :active_job
CustomActiveJobLogging::LogSubscriber.attach_to :active_job

「リリースが近づいているのにいろいろ追加されてますね」「まあ追加ですし、既存のコードが壊れるような仕様や変更でもないから大丈夫かと」「今のうちに駆け込みで入れちゃえみたいな😆」

bin/setupbin/updateを冪等にした

# railties/lib/rails/generators/rails/app/templates/bin/setup.tt#L29
  puts "\n== Preparing database =="
  system! 'bin/rails db:setup'
  system! 'bin/rails db:prepare'
<% end -%>

つっつきボイス:「お、さっきの冪等なdb:prepareに置き換わってるし」「今までのbin/setupはデータベースがあるとエラーだったのか」「地味にありがたい🙏」

Rails

Rails 6がrc1に

GW中にもしや最終リリースと思いきや、順当に順延の流れですね。


つっつきボイス:「アバウト1000コミット!」「タイムスケジュールではrc1は3/1だけど」「ふた月遅れ」「予定通りに遅れ中🤣」「🤣」「平成とおさらばすると同時にリリースできたらよかったけど😆 」

参考: Timeline for the release of Rails 6.0 | Riding Rails

同プレスリリースで、以下の書籍がbeta状態とありました。同時リリースを狙ってるでしょうね。

また、4/30〜5/2にミネアポリスで開催されたRailsConf 2019は4/25の時点でソールドアウトでした。例によってスロットが7つもあるカンファレンスなので、現地では7分の1のセッションしか見られないこと確定でした😢。

日本からRailsConfに参加した方が何人かいらっしゃいました。

RubyCopの出力をPretter for Rubyっぽくした(Ruby Weeklyより)


同記事より


つっつきボイス:「Prettier for Rubyってのがあるのね」「Prettierって最近人気高いっぽいですね」「やっぱり名前がいいから?😆Rufoは名前がイマイチ感」


prettier/plugin-rubyより

「JavaScript方面ではPrettierめちゃ使われてるし、Rubyがプラグインになってるならこっちの方がよさそう😍」「Linterをいくつも入れなくていいのはうれしい😂」「宗教戦争を避けるのにもよさそうだし」

「記事はRuboCopの出力をPrettierっぽくしてみたということみたい」「最初からPrettier使えばいいような気もするけどそうもいかない事情があるんでしょうね☺️」「なにしろRubyは書き方の自由度が高いし」「RubyCopをコードフォーマッターとみなしていいのかどうかは議論が分かれそうだけど😆」「rubocop -aはフォーマッターか否か😆」「さすがにちょっと乱暴な感じ😆」「記事にはRufoも一応出てますね」「RuboCopにこういうフォーマッタールール↓を入れるとできるっぽい」

todo_or_die: TODOを放置すると…!


つっつきボイス:「RubyKaigi 2019@博多で発表されてたgemです」「yancyaさん大喜び😆」

参考: The Selfish Programmer - RubyKaigi 2019

# 同リポジトリより
class UsersController < ApiController
  TodoOrDie("delete after JS app has propagated", by: Time.parse("2019-02-04"))
  def show
    redirect_to root_path
  end
end

「TODOを上のように書くと、期限切れしたときにTodoOrDie::OverdueErrorエラーで落ちるそうです😇」「TODOコメントがあると勝手に落ちてくれるというのは……いいのか?🤣」「どうなんでしょ🤣」「まあエンジニアもTODOコメントを書いている時点で何かがアブナイのはわかっているだろうし」「masterブランチにがんがんマージするようなやんちゃなプロジェクトなら、こういうgemで守るという手もあるといえばある?」「あんまりいい使い方じゃない気も😆」

なお、このgemのリポジトリに貼られている画像はむか〜しの「Skate Or Die」というファミコンゲームのジャケットのようです。

RailsのPostgreSQLデータベースで強制バリデーション


つっつきボイス:「このPaweł Urbanekさんの記事は何度かTechRachoで翻訳したことがあります」

Railsアプリと技術ブログにGDPRコンプライアンスを追加する(翻訳)

「なるほど、こうやってジョブを作ってエイヤで流してバリデーションしたと😆」「あんまりいい方法ではないことは承知でやってるみたいですね😅」

# 同記事より
class ModelDataIntegrityCheckerJob
  include Sidekiq::Worker
  sidekiq_options retry: false

  def perform(klass_name)
    klass = klass_name.constantize
    invalid_objects = []

    klass.find_each do |object|
      unless object.valid?
        invalid_objects << [object.id, object.errors.full_messages]
      end
    end

    if invalid_objects.present?
      raise "Invalid state for #{klass} objects: #{invalid_objects}"
    end

    invalid_objects
  end
end

「DBのfalsenilの意味が違っててRuby側でハマる問題とかあるある😅」「それにしてもRailsのdata integrityってどこで行えばいいのか問題があるからな〜: ぽすぐれの層でやろうとするとテーブルをきれいに設計しておかないとトリガーで消せなくなるとか、トリガーにならないぐらいビジネスロジックが立て込んじゃうとかあるし」「うんうん」

「原則的にRDBMSの世界では、テーブルをきちんと設計しておけばdata integrityを全部RDBMSに任せられるんですが、これを原理主義的にやりすぎるとRailsでテーブル2個ぐらいで済むところをテーブルごと分けないといけないみたいなことになりかねないので、そこが悩ましいところ😭」

その他Rails


つっつきボイス:「あーなるほど、こうやってdylib(ダイナミックリンクライブラリ)を使えばプロセスをforkせずにやれるので速くなりそう」「頑張り味のある記事🥰」

[dependencies]
mysql = "9.0.1"
libc = "0.2.0"

[lib]
name = "csv"
crate-type = ["dylib"]

こちらは5/6までなので期間終了です。皆さまは間に合いましたでしょうか?



つっつきボイス:「銀座Rails!」「4/24の銀座Railsに徳丸先生が登場して盛り上がったそうで、安川さんが早速記事にしてその中でTechRachoのセキュリティ記事も紹介いただきました🙇」

参考: Railsエンジニアのためのウェブセキュリティ入門に参加 🔐 - YassLab 株式会社

Ruby

RubyWeeklyにもいろいろ載っていたのですがきりがないので相当絞りました。

リポジトリがSubversionからGitに移行(Ruby公式ニュースより)


つっつきボイス:「ついにRubyのリポジトリがSubersionからCgit管理に!」「 『not GitHub』ですって」「以下の記事にも書いた後で正式に発表されていました」 「Rubyに新しくコントリビュートする人がSubversionだとウッとなるという気持ちはワカル☺️ 」

キーワードで振り返るRubyKaigi 2019@博多(#1)


svn.apache.orgより

「今更ですが、Subversionってsvn checkoutのときにものすごく時間かかるんですよね😭」「そうそうっ😤」「一晩かかるとかざらにあるし😇」「コミットを1つ1つ追いかけるうえにsvnサーバーとのやりとりでネットワークトラフィックも凄いことになってホント重い」「そんなに重いんだ…」「リクエストとレスポンスを繰り返すからネットワーク帯域を有効に使えていないし」

「あの頃はsvn checkoutしたら『さて帰るか』でしたし😆」「ローカルのリポジトリをうっかり消すとまたやらないといけなかったりしたし😆」

参考: svn checkout

「それがイヤだったので、ローカルでgitコマンドを使うために一時期git-svn使ってましたよ」「今回のRubyKaigiでも『cloneがめちゃ速くなった❤️』って言ってましたね☺️」「Gitだと平常運転😆」「歴史の長いプロジェクトだとどこかのタイミングで一度squashしないとみたいな話になったり、今までのRubyみたいにメインはSubversionでミラーはGitHubにみたいにやったりしてますよね」

参考: git-svn の使い方メモ

「その一方で、RubyがこれまでSubversionを使ってたのはそれはそれでよかったかもという話もワカル: RubyKaigiでも誰かが話していましたけど、Rubyにコミットする人たち以外はSubversionを使わないんだし、GitHubのミラーにあるコミットはSubversionとちゃんと同期していて、履歴を追いかけるとかならGitHubミラーでやれてたし」「たしかに〜」「それにRubyコミッター以外でもGitHubの方にコミットを投げればコミッターがSubversionの方で取り込めるようにはなってたので、コミッターがこの二度手間を面倒と思わなければ今回の移行はそれほど切実ではなかったという見方もできるといえばできますね☺️」「時代の流れといえばそうかも」

「自分も一応バージョン管理システムはCVS->Subversion-Gitと使ってきましたけど、最近だとCVSどころかSubversionも知らない人とかいそうですよね」「学部生の頃にCVS使ってて、そのあたりでSubversionはいいぞみたいな話が出始めたし」「…自分はVSSから😇」「その頃の自分はもう働いてましたが😆、当時のSubversionは確かにヨカッタ」「CVSと比べるとSubversion速かったような気がする」「JavaやってたのでSubversionのディレクトリ移動のやりやすさがよかった覚えがありますね」

参考: Concurrent Versions System - Wikipedia
参考: Microsoft Visual SourceSafe - Wikipedia

「自分はCVSのままでもよかったかななんて😆」「でもまあCVSにはブランチという概念がなかったから」「Subversionはtrunkとbranchという概念を生み出しましたよね」「ブランチが全コピーだから恐ろしく効率低いけど🤣」「そうそうっ🤣」「ブランチを増やせば増やすほどサーバー容量も増える😆」

「その点Gitは生ファイルをサーバーに置かずにGit独自のバイナリに保存するという発想でできていて、やはりあれは偉大な発明」「やっぱそうなるよねみたいな😆」「あれはGitより前にMercurialとかBazaarあたりからやってましたね」「Bazaar、一瞬使ってたナ」「Linus TorvaldsがGitを作るきっかけになったかつてのLinuxのバージョン管理システムってどれでしたっけ?」「えっとPerforceじゃなくてBitKeeperか」

参考: Mercurial - Wikipedia


Wikipediaより(GPLv2)

参考: Bazaar - Wikipedia


http://bazaar.canonical.comより

参考: BitKeeper - Wikipedia


bitkeeper.orgより

RubyKaigi 2019の余韻


rubykaigi.orgより


つっつきボイス:「RubyKaigiはGW明けに社内勉強会で時間を取ることになってるので詳しくはそのときに☺️」「ですね☺️」

キーワードで振り返るRubyKaigi 2019@博多(#1)

(撮った写真をいくつか眺めて)「今回は写真にがっつりキャプションを入れたので情報の価値が上がったなと思いました」「ロケーション情報と時刻が出るだけでもかなりありがたい」「博多で見つけた例の焼きラーメン(上記事)の店の場所も後でわかりました😆」

「お、RubyKaigi会場横のメルチャリ乗ったな〜」「乗る暇なかった😢」「これ無料で乗れたんでしたっけ?」「有料ですけど最初はクーポンか何かで無料で乗れましたね😋」

参考: メルチャリ


merchari.bikeより

Ruby 2.7のパターンマッチング第一印象(Ruby Weeklyより)


つっつきボイス:「例のBrandon Weaverさんの発表でした」「Rubyの新機能『パターンマッチング』今回見られなかった😇」「私も」「半分ぐらい見た😆」「Erlangか何かの機能なんですよね」「Haskellにもあったかも」

参考: Erlang - Wikipedia
参考: Haskell個人メモ :: 3.関数の構文 - Qiita

「そうそう、case文でinが使えるようになる」「今でもcase文でarrayのカンマ区切りで書けますけどね☺️」「発表ではJSONのネストが深いときにうれしいみたいな話をしてたかも」「単なる列挙なら今のwhenとか単純な正規表現マッチでもできるけど」「構造を持ってしまったときに欲しくなるヤツと言ってた」「lambda的なものが書けるようになったとか?」「lambdaは今でも書けるはず」

参考: Ruby の case 文で,値が配列に含まれているかを調べる - Qiita

# 同記事1より
case 0
in 0 | 1
  true
end
# => true

「このrange使いまくるヤツ↓はエグい😆」「そうそう、こういうバリデーションチェックっぽいのが書けるんだった😆」「パターンマッチングの方が評価順序的に速いとかある?」「まだそこまではわからない😅」

# 同記事1より
case [0, 1, 2, 3, 4, 5]
in [0..1, 0...2, 0.., 0..., (...5), (..5)]
  true
end

「Weaverさん、よほどうれしかったのかパターンマッチング記事を立て続けに書いてますね」「こういうのでやりたかった人には待望の機能でしょうね☺️」

その他Ruby

参考: RubyKaigi 2019 直前特集号

Ruby trunkより

なんぱら色々

他にもissueが上がっていますね。

# #15783より
->x:@2{}   # SEGVする

つっつきボイス:「ついなんぱらと略してしまいましたが😆、numbered parameterのissueがいろいろ上がっていたので」「これをtrace pointするとさらに凄いことになりそう😆」「hanachinさんのissueも上がってる」「RubyKaigiでもhanachinさんは髪の色で遠くからひと目でわかりました🌈」

「numbered parameter、自分はitがいいかな〜なんて😆」「amatsudaさんもit推しでしたね」「ネストするとどうなるのかな😆」「itにする場合、itはRubyのキーワードになる?」「なるでしょうね」

「numbered parameterが@1だけというのがまだ腑に落ちてないかな〜」@2とかはありそうでないという😆」「たぶん需要がないんじゃないかな: Rubyのブロックが1つしか渡せないのと同じような感じで」「あ〜そうかも」「Rubyではやってないけど、ブロックを複数渡せる言語ってありますし😎」「マジで?」「Rubyでもやろうと思えばできたはずだけど、現実的にほぼ誰も使わないでしょうし」「そういえばMatzがどこかで『Rubyで渡せるブロックを1つだけにしたのが自分的にヒットだった』と書いていた覚えが」「それと同じような感じでnumbered parameterは@1だけにしたんじゃないかなと思うのでそんなに不自然には感じない」「それならわかる気がする😋」

追記(2019/05/09)

morimorihoge注)
※初出時、@2はないといった記述がありましたがミスでした、申し訳ありません。正確には numbered形式(itやthisなどの単語形式ではない)だと@2以降も参照できるが、実際にはそれほど@2以降には需要がないのでは?という話の流れのなかで「需要がない」を「存在しない」に混同したのだと思われます。
ご指摘いただいた@pink_bangbiさん、ありがとうございました。


今回は以上です。

バックナンバー(2019年度第2四半期)

週刊Railsウォッチ(20190415-1/2前編)Railsバージョンアップに便利なstill_life gem、Zeitwerkの改修進む、named_capture追加ほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

夏のTechRachoフェア2019

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ