Slide 1

Slide 1 text

Kaigi on Rails Kaigi on Rails ActiveRecord の歩み⽅ ActiveRecord の歩み⽅

Slide 2

Slide 2 text

突然ですが、 突然ですが、 皆さんは ActiveRecord のコードを 皆さんは ActiveRecord のコードを 読んだことはありますか? 読んだことはありますか?

Slide 3

Slide 3 text

知らない⼈もいると思うんですが 知らない⼈もいると思うんですが ActiveRecord って Ruby で ActiveRecord って Ruby で 書かれているんですよ 書かれているんですよ

Slide 4

Slide 4 text

つまり Ruby を知っていれば つまり Ruby を知っていれば ActiveRecord のコードを ActiveRecord のコードを 読むことができるんです! 読むことができるんです!

Slide 5

Slide 5 text

ちょっと ActiveRecord コードを ちょっと ActiveRecord コードを 覗いてみましょう… 覗いてみましょう…

Slide 6

Slide 6 text

Relation::VALUE_METHODS.each do |name| method_name = \ case name when *Relation::MULTI_VALUE_METHODS then "#{name}_values" when *Relation::SINGLE_VALUE_METHODS then "#{name}_value" when *Relation::CLAUSE_METHODS then "#{name}_clause" end class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{method_name} # def includes_values default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes] @values.fetch(:#{name}, default) # @values.fetch(:includes, default) end # end def #{method_name}=(value) # def includes_values=(value) assert_mutability! # assert_mutability! @values[:#{name}] = value # @values[:includes] = value end # end CODE end

Slide 7

Slide 7 text

QUERYING_METHODS = [ :find, :find_by, :find_by!, :take, :take!, :first, :first!, :last, :last!, :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, :exists?, :any?, :many?, :none?, :one?, :first_or_create, :first_or_create!, :first_or_initialize, :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :create_or_find_by, :create_or_find_by!, :destroy_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by, :find_each, :find_in_batches, :in_batches, :select, :reselect, :order, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins, :where, :rewhere, :preload, :extract_associated, :eager_load, :includes, :from, :lock, :readonly, :extending, :or, :having, :create_with, :distinct, :references, :none, :unscope, :optimizer_hints, :merge, :except, :only, :count, :average, :minimum, :maximum, :sum, :calculate, :annotate, :pluck, :pick, :ids ].freeze # :nodoc: delegate(*QUERYING_METHODS, to: :all)

Slide 8

Slide 8 text

module ActiveRecord class Base extend ActiveModel::Naming extend ActiveSupport::Benchmarkable extend ActiveSupport::DescendantsTracker extend ConnectionHandling extend QueryCache::ClassMethods extend Querying extend Translation extend DynamicMatchers extend Explain extend Enum extend Delegation::DelegateCache extend Aggregations::ClassMethods include Serialization include Store include SecureToken include Suppressor end

Slide 9

Slide 9 text

こんなのぼくが知ってる こんなのぼくが知ってる Ruby じゃない!!! Ruby じゃない!!!

Slide 10

Slide 10 text

⾃⼰紹介 ⾃⼰紹介 名前 : osyo Twitter : github : ブログ : 趣味で Ruby にパッチを投げたり、気になった Ruby のチケットをブ ログにまとめたりしてる Ruby で⼀番好きな機能は Refinements 普段は を開発してる に参加したり エディタは Vim 質問や気になる点があればぜひ Twitter で! @pink_bangbi osyo-manga Secret Garden(Instrumental) activerecord-bitemporal RubyKaigi Takeout 2020 感想戦 @ 仮想松本

Slide 11

Slide 11 text

ActiveRecord の歩み⽅ ActiveRecord の歩み⽅

Slide 12

Slide 12 text

アジェンダ アジェンダ ActiveRecord を読む前に Ruby の知識を蓄える 知っておくとよい Ruby の知識 コードを読むときに使う道具の紹介 ActiveRecord を読むときのコツ ActiveRecord の構造を知る 読む⽬的を明確にする ライブリーディング コードを読む前の下準備 ActiveRecord を読んでみる まとめ 話さないこと ActiveRecord ⾃体の機能解説はしない 今⽇の話を聞いて実際に⾃分で読んでみよう!!

Slide 13

Slide 13 text

ActiveRecord を読む前に ActiveRecord を読む前に

Slide 14

Slide 14 text

Ruby の知識を蓄える Ruby の知識を蓄える ActiveRecord は Ruby で書かれている なので Ruby が読めれば ActiveRecord も読むことはできる しかし ActiveRecord では普段はあまり使わないような Ruby の機能が 使われている メタプロだったり動的なオブジェクト⽣成だったり などでちゃんと読むためにはある程度 Ruby に精通した知識が必要に なってくる 知らない Ruby の書き⽅が出てきてもギョッとせずに冷静に対応する 事が⼤事 知らない機能やメソッドが出てきたらちゃんと調べる!

Slide 15

Slide 15 text

知っておくとよい Ruby の知識 知っておくとよい Ruby の知識 メタプログラミングの知識 #define_method や #respond_to?, #method_missing, #send 動的なクラス/ モジュール⽣成 Class.new や Module.new が出てきてもびっくりしない コンテキストを切り替えたブロックの評価 instance_exec や instance_eval, class_eval 継承関連 いろんなところで include/prepend や extend してる User.ancestors とか⾒るとめっちゃ継承してる… ActiveSupport::Concern や delegate の機能 Ruby ではなくて Rails の拡張機能 ActiveRecord で広く使われているので知っておくとよい

Slide 16

Slide 16 text

コードを読むときに使う道具の紹介 コードを読むときに使う道具の紹介 Object#method メソッドの定義位置などを調べる メタ情報などを取得するメソッド __method__, caller, .class ⾃作デバッグ出⼒ gem : 実⾏時デバッグ機能 binding.irb や binding.pry や $debug = true と pp hoge if $debug の組み合わせ 必要なときのみデバッグ出⼒を ON にする ActiveRecord の #to_sql 実⾏される SQL ⽂を取得する エディタを便利にする エディタ上でファイラをつかったり grep したり binding-debug ruby_jard

Slide 17

Slide 17 text

メソッド定義位置を調べる メソッド定義位置を調べる Object#method を使⽤してメソッドの定義位置を調べられる # test.rb class Super def hoge; end end class Sub < Super def hoge; end end obj = Sub.new # hoge メソッドのオブジェクトを取得する meth = obj.method(:hoge) # メソッドの定義位置が出⼒される pp meth.source_location # => ["/home/hoge/test.rb", 7] # こうすると親メソッドも参照する事ができる pp meth.super_method.source_location # => ["/home/hoge/test.rb", 3]

Slide 18

Slide 18 text

メタ情報をデバッグ出⼒ メタ情報をデバッグ出⼒ よく利⽤するデバッグ出⼒情報 def plus(a, b) # メソッド名を出⼒ pp __method__ # => :plus # クラス名を出⼒ pp a.class.name # => "Integer" # コールスタックを出⼒ pp caller a + b end plus 1, 2

Slide 19

Slide 19 text

⾃作デバッグ出⼒ gem ⾃作デバッグ出⼒ gem pp { expr } を実⾏すると "expr" とその結果の両⽅が出⼒される gem install binding-debug require "binding/debug" using BindingDebug def plus(a, b) # メソッド名を出⼒ pp { __method__ } # output: __method__ # => :plus # クラス名を出⼒ pp { a.class.name } # output: a.class.name # => "Integer" a + b end plus 1, 2

Slide 20

Slide 20 text

$debug = true と pp hoge if $debug の組み合わせ $debug = true と pp hoge if $debug の組み合わせ pp でデバッグ出⼒を仕込むと調べたい箇所以外のタイミングでも出 ⼒される事がある pp だけではなくて pp hoge if $debug で制御することで任意のタイミ ングでのみデバッグ出⼒できる def twice(a) pp __method__ if $debug pp a if $debug a + a end twice(42) $debug = true # ここで呼び出された場合のみデバッグ出⼒する twice("homu") $debug = false twice(42)

Slide 21

Slide 21 text

#to_sql で SQL ⽂を取得 #to_sql で SQL ⽂を取得 #to_sql で実⾏される SQL ⽂を取得する事ができる # puts で出⼒する puts User.where(active: true).to_sql # output: SELECT "users".* FROM "users" WHERE "users"."active" = 1 # p や pp だと " がエスケープされるので注意 p User.where(active: true).to_sql # => "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"active\" = 1"

Slide 22

Slide 22 text

エディタを便利にする エディタを便利にする 使い勝⼿をよくするためにエディタを拡張する 道具にこだわるのは⼤事 エディタ上でコードを実⾏させる カーソル下の単語をハイライトさせる 任意の単語をハイライトする 検索数を表⽰する SQL を整形する vim-quickrun vim-brightest vim-quickhl anzu.vim SQLUtilities

Slide 23

Slide 23 text

ActiveRecord を読むときのコツ ActiveRecord を読むときのコツ

Slide 24

Slide 24 text

ActiveRecord の構造を知る ActiveRecord の構造を知る ActiveRecord にはいろいろな機能が実装されている モデル定義 関連付けの設定 バリデーション SQL の実⾏‧読み込み‧モデルのインスタンス化 etc... 調べたい機能に対してどこを読むべきなのかを調べるのが難しい 単純に ActiveRecord ⾃体のコード量が多い ある程度 ActiveRecord の構造を知っておくとあたりをつけて読みや すい 慣れの部分もあるがある程度全体像を把握しておくとよい

Slide 25

Slide 25 text

例えば… ActiveRecord::Persistence モデルの⽣成、更新、削除 .create, #update, #destroy ActiveRecord::Relation や ActiveRecord::QueryMethods クエリメソッドの定義や Arel の構築処理 .where, .order, .limit ActiveRecord::Reflection 関連付けの設定定義 has_many, has_one, belongs_to ActiveRecord::Associations #associations や関連先の読み込みの処理 ActiveRecord::Migration migration 関連の処理 などなど… ActiveRecord 以外で定義されている処理もあるので注意する

Slide 26

Slide 26 text

読む⽬的を明確にする 読む⽬的を明確にする ActiveRecord というのはいろいろなことをやっている ActiveRecord を読む!と⾔っても漠然としてて迷⾛しがち まずは『ActiveRecord のこういう機能を読む!』という⽬的を⾒つ けると⼊り⼝が明確になって読みやすい User.where(name: "Tom").to_sql で SQL がどう構築されるのか has_many :comments を⾏うと何が起きるのか バリデーションの仕組みって… ?コールバックの仕組みって… ? 特に『このメソッドを呼ぶと何が起きるのか』みたいな⽬的だとコ ードリーディングに⼊りやすい has_many がなにをやっているのか調べる、ってなると has_many のメソッドを調べるところから始められる

Slide 27

Slide 27 text

ライブリーディング ライブリーディング

Slide 28

Slide 28 text

コードを読む前の下準備 コードを読む前の下準備 何を読むのか⽬的を明確にする 今回は scope の実装を読んでみる 調べたい機能の挙動を調べておく その挙動がどうやって Ruby で表現されているのか⾒るのが⼤事 scope のドキュメントは ドキュメントを参照する場合は がおすすめ 最⼩構成のコードを⽤意する 最⼩構成のほうが余計な処理がなく実⾏も早いし、意図しない動 作が発⽣することも少ない Rails が を⽤意しているのでそれを 使うと1 ファイルだけで簡単に ActiveRecord のコードを動かすこ とができる ActiveRecord 以外のテンプレートもあるのでよしなに利⽤できる こちらここ バグレポート⽤のテンプレート

Slide 29

Slide 29 text

ActiveRecord を読んでみる ActiveRecord を読んでみる

Slide 30

Slide 30 text

01. その機能を使ってみる 01. その機能を使ってみる まず実際にどういう挙動をするのか使ってみる class User < ActiveRecord::Base # こんな感じで where を scope として定義する scope :actives, -> { where(active: true) } scope :owners, -> { where(owner: true) } end # 定義した scope はクラスメソッドのような形で呼び出すことができる puts User.actives.to_sql # => SELECT "users".* FROM "users" WHERE "users"."active" = 1 # また User. だけではなくて scope をチェーンすることもできる puts User.actives.owners.to_sql # => SELECT "users".* FROM "users" WHERE "users"."active" = 1 AND "users"."owner" = 1 puts User.order(:created_at).owners.to_sql # => SELECT "users".* FROM "users" WHERE "users"."owner" = 1 ORDER BY "created_at" ASC

Slide 31

Slide 31 text

02. その機能(メソッド)の定義位置を調べる 02. その機能(メソッド)の定義位置を調べる 実際にどこでメソッドが定義されているのか Object#method で確認 する Ruby の場合は動的にメソッドが定義されていたりすることがあるの で Object#method を使うのが⼀番確実 class User < ActiveRecord::Base # 実際にメソッドがどこで定義されるのか調べる # これは Ruby の標準の機能かつ実⾏時に値を取得するので⼀番確実 pp method(:scope).source_location # => ["/home/worker/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord- 6.0.3/lib/active_record/scoping/named.rb", # 170] # ちなみに Ruby 2.7 以降だと p や pp でそのまま出⼒しても表⽰される pp method(:scope) # => # (ActiveRecord::Scoping::Named::ClassMethods)#scope(name, body, &block) /home/worker/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord- 6.0.3/lib/active_record/scoping/named.rb:170> end

Slide 32

Slide 32 text

03. 実装を読む 03. 実装を読む Object#method で取得した情報を元にファイルを開いて実際に定義 されているメソッドの中⾝を読んでいく このあたり def scope(name, body, &block) unless body.respond_to?(:call) raise ArgumentError, "The scope body needs to be callable." end if dangerous_class_method?(name) raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ "on the model \"#{self.name}\", but Active Record already defined " \ "a class method with the same name." end if method_defined_within?(name, Relation) raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \ "an instance method with the same name." end

Slide 33

Slide 33 text

valid_scope_name?(name) extension = Module.new(&block) if block if body.respond_to?(:to_proc) singleton_class.define_method(name) do |*args| scope = all._exec_scope(name, *args, &body) scope = scope.extending(extension) if extension scope end else singleton_class.define_method(name) do |*args| scope = body call(*args) || all

Slide 34

Slide 34 text

実装を読んでいくと以下のような事がわかってくる # body で call メソッドが呼び出せないとエラー unless body.respond_to?(:call) raise ArgumentError, "The scope body needs to be callable." end # dangerous_class_method? とは… ? if dangerous_class_method?(name) raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ "on the model \"#{self.name}\", but Active Record already defined " \ "a class method with the same name." end # method_defined_within? とは… ? if method_defined_within?(name, Relation) raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \ "an instance method with the same name." end # 省略

Slide 35

Slide 35 text

04. 気になった部分を掘り下げていく 04. 気になった部分を掘り下げていく 先程の実装でよく知らないメソッドが出てきました dangerous_class_method? と method_defined_within? このメソッドが何をやっているのか気になるので次はこの実装を… という感じで気になったメソッドをどんどん掘り下げていきコード を読んでく また #method で掘り下げていくこともあればそのファイルで検索 したり gem 全体で grep することもある もちろん関係なさそうな処理をすっ⾶ばして次のコードを読んでい くのも OK コードリーディングは⾃由

Slide 36

Slide 36 text

まとめ まとめ

Slide 37

Slide 37 text

まとめ まとめ まずは Ruby の気持ちを理解するところからはじめる!! 実際 Ruby がわかっていれば(処理は複雑だが)読み解くのはそ こまで難しくはない 複雑なコードを⾒てもパニックにならず冷静に読んでみるのが⼤ 事 コードを読むときは⽬的を持って読んでいく! まずは⼩さな⽬的を⾒つけて読んでいくと⼊り⼝が明確になって 読みやすい Ruby には便利なデバッグ機能がたくさんあるので使い分けてコード を読んでいこう 記述したデバッグコードはちゃんと消そう… 他の⼈がコードを読んだり書いたりするのを⾒るのは楽しいのでみ んなどんどんやっていこう!!

Slide 38

Slide 38 text

Let's enjoy Ruby reading!! Let's enjoy Ruby reading!! ご清聴 ご清聴 ありがとうございました ありがとうございました