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

週刊Railsウォッチ(20200907前編)スライド『Rails 6.1で新しく入る機能について』、Railsコミュニティサーベイ、TruffleRubyでZeitwerkが動いたほか

こんにちは、hachi8833です。RubyKaigi Takeout 2020が無事開催されましたね。主催者/登壇者/参加者/スポンサーの皆さまお疲れさまでした!

1日目のライブ動画冒頭に映ったBPS株式会社のスポンサー表示です↓。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

今回のつっつき会はRubyKaigi Takeout 2020前日ということもあり、つとめて軽くしました。
ところが録画に失敗してしまったため今回はつっつき成分が少なめとなりました🙇。

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

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

whereで関連付け名をjoinしたテーブルのエイリアス名として参照できるようになった

テーブルが複数回joinされると、それらのテーブルは最初のテーブルを除いてエイリアスされる。
これは自己参照する関連付けで起きやすく、その場合現在は、where条件内のエイリアスされたテーブルでカスタム属性(型キャスト)や属性エイリアスの解決を行う方法がない。
この問題を修正するため、whereで関連付け名をテーブルエイリアスとして参照できるようになる。whereの中で関連付け名が参照されると、joinされたテーブルでそれらの名前がエイリアス名として使われる。
同PRより大意

class Comment < ActiveRecord::Base
  enum label: [:default, :child]
  has_many :children, class_name: "Comment", foreign_key: :parent_id
end

# ... FROM comments LEFT OUTER JOIN comments children ON ... WHERE children.label = 1
Comment.includes(:children).where("children.label": "child")

つっつきボイス:「生SQLを使わずにActive Rcord wayな形でJOINしたテーブルの条件を付与できるようになったのはうれしい」「神速さんと同じくdiff見ただけだとわからない…」「やっぱりActive Recordを知り尽くしている人じゃないと」「修正されたissueを見る方が早いかも↓」

Active Storageの動画ストリームがduration情報に基づいてフォールバックするよう修正

# activestorage/lib/active_storage/analyzer/video_analyzer.rb#L46
      def duration
-       Float(video_stream["duration"]) if video_stream["duration"]
+       duration = video_stream["duration"] || container["duration"]
+       Float(duration) if duration
      end

つっつきボイス:「以下のAPIドキュメントに基づいてduration情報を取れるようにしたみたい」

参考: FFprobeTips – FFmpeg

注: MatroskaやWebMなど、ストリームレベルでdurationを保存しないフォーマットではduration=N/Aとなる。Format (container) durationを参照。
trac.ffmpeg.orgより

changes_appliedの後でdecorateされたtimeオブジェクトが誤って返される問題を修正

# activerecord/lib/active_record/type/time.rb#L3
module ActiveRecord
  module Type
    class Time < ActiveModel::Type::Time
      include Internal::Timezone

      class Value < DelegateClass(::Time) # :nodoc:
      end

      def serialize(value)
        case value = super
        when ::Time
          Value.new(value)
        else
          value
        end
      end
+
+     private
+       def cast_value(value)
+         case value = super
+         when Value
+           value.__getobj__
+         else
+           value
+         end
+       end
    end
  end
end

つっつきボイス:「これは明らかにバグですね」「decorated timeって何でしょう?」「issue↓にあるように、TimeActiveRecord::Type::Time::Valueになるということだと思います」


timeオブジェクトはもともと changes_applied の後で forgetting_assignment (value_for_database)によってデコレートされていた。

 def forgetting_assignment 
   with_value_from_database(value_for_database) 
 end 
 def value_for_database 
   type.serialize(value) 
 end

#36352までは運良く、value.changeを呼ぶたびにapply_seconds_precisionがデコレートされない新しいtimeオブジェクトを毎回返していた。
現在はcast_valueでtimeの値がデコレートされているかどうかを明示的にチェックする必要がある。
同PRより大意


「おや、今GitHub画面上でメソッドをマウスオーバーしてGo to definitionをクリックしたらIDEっぽい画面にジャンプしたのが見えましたけど、それは?」「たぶんSourcegraphのChrome拡張が効いたんでしょう↓」「なるほど、後で入れてみます」

参考: Sourcegraph - Chrome Web Store

このChrome拡張いいですね😋。

Railsのrakeタスクが複数回読み込まれないよう修正

#39137ではrakeコマンドを呼び出すたびに新しいRake::Applicationインスタンスが作成されていた。RailsタスクがRake::Applicationごとに定義されるよう、rails/tasks.rbがインスタンスごとに読み込まれていた。しかしRake::Application#load_rakefileがアプリケーションのRakefileを読み込み、これがRails.application.load_tasksを呼び出すのでこのRailsタスクも読み込まれる。あるrakeタスクが複数回定義されていると、タスク実行時にすべての定義ブロックが実行される。つまりRailsのタスクブロックが2回実行された。
このコミットは、二重実行回避のため不要な読み込みを削除する。
同PRより大意

# railties/lib/rails/commands/rake/rake_command.rb#L18
          Rake.with_application do |rake|
-           load "rails/tasks.rb"
            rake.init("rails", [task, *args])
            rake.load_rakefile
            if Rails.respond_to?(:root)
              rake.options.suppress_backtrace_pattern = /\A(?!#{Regexp.quote(Rails.root.to_s)})/
            end
            rake.standard_exception_handling { rake.top_level }
          end

ZeitwerkがTruffleRubyでデフォルトで有効になった


つっつきボイス:「TruffeRubyでZeitwerkが動くようになったとは!」「おめでとうございます🎉」

Rails

スライド: Rails 6.1で新しく入る機能について


つっつきボイス:「@willnetさんがまとめてくれた🎉」「なるほど見やすい」「ありがたいです😂」「既視感ある機能も結構ありますね」「まあうちらは毎週追ってますから😆」


参考までに、スライドで紹介されているRails 6.1の新機能のうち、これまでRailsウォッチの「先週の改修」で追ってきたものを漁ってみました(スライド順)。

以下は「先週の改修」で扱ってなかった機能です。ルーティングファイルの分割は話題に出たような覚えがあったのですが…頑張ります。

Railsの低レベルキャッシュをマスターする

# 同記事より
class UpdatePricesJob < ApplicationJob
 def perform
   Commodity.each { |commodity| commodity.update!(price: <fetch_api_price>) }
 end
end

class Commodity < ApplicationRecord
 belongs_to :invoice, touch: true
end

class Invoice < ApplicationRecord
 has_many :commodities

 def total_value
  Rails.cache.fetch([self, :total_value]) { commodities.map(&:price).sum }
 end
end

見出しより

「キャッシュとは、ある種のコードの結果の保存を意味する一般的な用語」

  • 低レベルキャシュとは
  • しくみ
  • 低レベルキャッシュを使うとき
  • キャッシュのキーと期限
  • モデルでtouch:する
  • 時間ベースの期限設定
  • ユースケースの例
    • APIからフェッチした商品の「現時点の価格」に依存して大量の計算が発生する
    • さまざまなレベルのネステッド集計が発生する
  • 注意点
  • ビューがボトルネックになるとき

Railsコミュニティサーベイ


つっつきボイス:「この間取り上げようと思って見落としてました」「参加者の国のグラフ↓に日本がない?」「日本はその他に含まれてるみたいです」「やっぱり英語のアンケートに回答する日本人って少ないのかな🤔」


同記事より

「面白い項目いろいろある」「しかし長いアンケート結果」「たしかに長いですね」「きりがないので次へ〜」

JetBrains IDEにペアプロ機能が入るか


つっつきボイス:「とりあえずペアプロ用機能は入れてみた」「VSCodeのLive Shareっぽくやれるようになるんですね」

参考: 概要 - Visual Studio Live Share - Visual Studio Live Share | Microsoft Docs


ペアプロを極めて最強の開発チームをつくる(1/4)ペアの組み方(翻訳)

その他Rails


つっつきボイス:「ソフトウェアスタートアップ企業トップ50か」「たしかにRuby使ってるところ多い」「あくまで初期のバックエンド言語ということなので今は違う言語を使っているところもあるでしょうけど、立ち上げにRailsを使うのは理にかなっていると思います」「このランキングの基準が時価総額なので、今どきのモダンなアーリーステージの企業はランクインしにくいんですよ」「現時点の初期開発言語はこれだけではわからないということなんですね」「何年かすればまた内訳も変わってくるでしょうし」「1社だけLispのところがある😆」「ホントだ😆」

参考: LISP - Wikipedia


前編は以上です。

バックナンバー(2020年度第3四半期)

週刊Railsウォッチ(20200901後編)RubyKaigi 2020 Takeout登壇者発表、Ruby開発版が2.8から3.0へ、マイクロサービス分割ほか

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

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

Rails公式ニュース


CONTACT

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