Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Rails 1.0 のコードで学ぶ find_by* と method_missing の仕組...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
maimu
March 01, 2025
Programming
1.4k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Rails 1.0 のコードで学ぶ find_by* と method_missing の仕組み / Learn how find_by_* and method_missing work in Rails 1.0 code
maimu
March 01, 2025
More Decks by maimu
See All by maimu
Gentoo 1年生 ビルドは終わらない
maimux2x
0
96
rails_girls_is_my_gate_to_join_the_ruby_commuinty
maimux2x
0
690
ruby-flip-flop
maimux2x
0
150
before_rails_girls_after_rails_girls
maimux2x
0
850
my_study_of_ruby_method
maimux2x
1
200
one_liner_fizzbuzz
maimux2x
0
170
about_rails_girls_document_translation
maimux2x
0
6.3k
best_for_fbc
maimux2x
0
79
homemade_service_release_front_and_back
maimux2x
0
480
Other Decks in Programming
See All in Programming
The NotImplementedError Problem in Ruby
koic
1
670
A2UI という光を覗いてみる
satohjohn
1
120
The ROI of Quarkus for Spring Boot Applications
hollycummins
0
100
AIチームを指揮するOSS「TAKT」活用術 / How to Use “TAKT,” an OSS Tool for Orchestrating AI Teams
nrslib
6
860
Oxcを導入して開発体験が向上した話
yug1224
4
300
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
2
330
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
170
AIで効率化できた業務・日常
ochtum
0
120
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
200
OSもどきOS
arkw
0
470
並列実装の現場、2ヶ月間実務でAIを使い倒したAIもPCも私も限界が近い
ming_ayami
0
120
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
190
Featured
See All Featured
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.7k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
122
22k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
230
Into the Great Unknown - MozCon
thekraken
41
2.6k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.5k
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
270
Code Reviewing Like a Champion
maltzj
528
40k
Information Architects: The Missing Link in Design Systems
soysaucechin
0
960
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.7k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.8k
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
1
170
Transcript
Rails 1.0 のコードで学ぶ find_by_* と method_missing の仕組み 2025/03/01 TokyoWomen.rb #1
Mai Muta @maimux2x
Mai Muta(maimu) @maimux2x フィヨルドブートキャンプ卒業生🎓 甘党🍩
None
None
TokyoWomen.rb #1のテーマ Rubyっておもしろい!
https://www.oreilly.co.jp/books/9784873117430/ Rubyっておもしろい!~その1~
メタプログラミングRuby 第2版 自分が知っているRubyから、 知らない世界が広がるおもしろさ
Rubyっておもしろい!~その2~ Rails 1.0 のコードリーディング
なぜRails 1.0 なのか? きっかけ && しおいさん!
一人で読むのは難しそう・・・
しんめ.rbでコードリーディングを開催
Rails 1.0 のコードリーディング RailsがRubyでどのように実装されているか 月日を経て実装がどう変化しているのか に触れたおもしろさ
特におもしろいと感じた部分 find_by_* と method_missing
おもしろいと感じた理由 一緒に見ていきましょう!!
準備 1. https://github.com/rails/rails をローカルにclone 2. $ cd rails 3. $
git checkout refs/tags/v1.0.0
準備 1. $ curl https://gist.githubusercontent.com/en30/d4fff101aec19c546da6b0b415c6cde6/ra w/26c845254a3649b84c101ea09b5a8277ec14cc16/gistfile1.txt | patch -p1 2.
$ cd activerecord 3. $ rake rdoc 4. $ open doc/ActiveRecord/Base.html
Rails 1.0 の頃の世界にいざ出発〜!!
Rails頻出メソッドといえば find, find_by, where
Rails 1.0 の頃は find, find_by, where
疑問 find_by や where がなくて どうやってデータの検索をしていたのか?
RDocを読んでみる
RDocを読んでみる ・findの引数で色々指定できる ・:conditionsがwhere相当?
RDocを読んでみる
疑問 find_by や where がなくて どうやってデータの検索をしていたのか?
答え find で処理されていた!
例を見ながら find メソッドの使い方を確認 id name email active 1 Alice
[email protected]
true 2 Bob
[email protected]
false 3 Carol
[email protected]
false
name が Alice のレコードを1件取得したい場合 ⬇Rails 1.0 の時はこのように書いていた User.find(:first, :conditions =>
"name = ?", "Alice") # => #<User id: 1, name: "Alice", email: "
[email protected]
", active: true> ⬇今だと find_by で書くことができる User.find_by(name: "Alice") # => #<User id: 1, name: "Alice", email: "
[email protected]
", active: true>
active が false の user を取得したい場合 ⬇Rails 1.0 の時はこのように書いていた User.find(:all,
:conditions => "active = ?", false) # => [#<User id: 2, name: "Bob", email: "
[email protected]
", active: false>, #<User id: 3, name: "Carol", email: "
[email protected]
", active: false>] ⬇今だと where で書くことができる User.where(active: false) # => [#<User id: 2, name: "Bob", email: "
[email protected]
", active: false>, #<User id: 3, name: "Carol", email: "
[email protected]
", active: false>]
findメソッドまとめ • find メソッドの役割の範囲が今よりも広い • :first, id, :all, :conditionsなどの引数の指定が可能だった •
:conditions などで指定された条件に応じてSQLを組み立ててデータを検索して返し ていた ◦ Rails 1.0 の find メソッドの実装もおもしろいです!!
ここまでの感想 find メソッドで検索条件を書くのが大変そう・・・
もう一度RDocを読んでみる
Userの名前とメールアドレスを条件に検索したい場合 User.find(:first, [:conditions => “name = ? AND email
= ?” "Alice", "
[email protected]
"]) は以下のように書くことができる User.find_by_name_and_email("Alice", "
[email protected]
") # => #<User id: 1, name: "Alice", email: "
[email protected]
", active: true> User.find_by_name_and_email_and_acitve… のように属性名は and で繋いでいくことできるらしい
find メソッドで検索条件を書くのが大変そう・・・
find_by_* という別の書き方がある!
疑問 find_by_* の * 部分は メソッド定義されてないのに どうやって検索結果を取得して返してるのか?
メソッド定義されていないといえば
https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/method_missing.html
def method_missing(method_id, *arguments) if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s) finder = determine_finder(match)
attribute_names = extract_attribute_names_from_match(match) super unless all_attributes_exists?(attribute_names) conditions = construct_conditions_from_arguments(attribute_names, arguments) if arguments[attribute_names.length].is_a?(Hash) find(finder, { :conditions => conditions }.update(arguments[attribute_names.length])) else send("find_#{finder}", conditions, *arguments[attribute_names.length..-1]) # deprecated API end # … end method_missing を探してみる activerecord/lib/active_record/base.rb:970 BasicObject#method_missingをオーバーライドしている!!
今回は find_by_* のパターンのみを追っていきます
method_missing の実装を見ていく def method_missing(method_id, *arguments) if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s) finder
= determine_finder(match) (all_by|by) で、find_all_by_ と find_by_ のどちらであるかを判定 ([_a-zA-Z]\w*) で、属性名(例:name、name_and_email)を取得
def determine_finder(match) match.captures.first == 'all_by' ? :all : :first end
method_missing の実装を見ていく activerecord/lib/active_record/base.rb:995 def method_missing(method_id, *arguments) if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s) finder = determine_finder(match) # … 変数 match をcaptures した最初の要素をチェックして :all または :firstを 返している
def extract_attribute_names_from_match(match) match.captures.last.split('_and_') end method_missing の実装を見ていく activerecord/lib/active_record/base.rb:999 attribute_names =
extract_attribute_names_from_match(match) super unless all_attributes_exists?(attribute_names) 変数 match を captures した最後の要素に対して split で属性名の配列 (例:[“name”] や [“name”, “email”]) を返している 属性名が存在しない場合は super で通常の method_missing に処理を任せ ている
method_missing の実装を見ていく activerecord/lib/active_record/base.rb:1003 conditions = construct_conditions_from_arguments(attribute_names, arguments) def construct_conditions_from_arguments(attribute_names,
arguments) conditions = [] attribute_names.each_with_index { |name, idx| conditions << "#{table_name}.#{connection.quote_column_name(name)} / # 本来は1行 #{attribute_condition(arguments[idx])} " } [ conditions.join(" AND "), *arguments[0...attribute_names.length] ] end find_by_name("Alice") の場合、 ["users.name = ?", "Alice"] となるように属性名と指 定された値を組み立てている
method_missing の実装を見ていく activerecord/lib/active_record/base.rb:979 if arguments[attribute_names.length].is_a?(Hash) find(finder, { :conditions =>
conditions }. # 本来は1行 update(arguments[attribute_names.length])) else # deprecated API send("find_#{finder}", conditions, *arguments[attribute_names.length..-1]) end 引数の最後に :order や :limit などのオプションが存在する場合は :conditions のハッシュ にマージして組み立てた条件を find メソッドに渡している else の場合は deperecated API とコメントされているが、ここでは send で find_first を実 行している
find_first 定義箇所を見てみる activerecord/lib/active_record/deprecated_finders.rb:21 module ActiveRecord class Base class <<
self # … def find_first(conditions = nil, orderings = nil, joins = nil) # :nodoc: find(:first, :conditions => conditions, :order => orderings, :joins => joins) end # … end end method_missing の send() はここを呼び出している さらに古いRailsのバージョンで使われていたらしい?
method_missing の実装を見ていく activerecord/lib/active_record/base.rb:979 if arguments[attribute_names.length].is_a?(Hash) find(finder, { :conditions =>
conditions }. # 本来は1行 update(arguments[attribute_names.length])) else # deprecated API send("find_#{finder}", conditions, *arguments[attribute_names.length..-1]) end スライド上で例に使用したサンプルコードでは else 節が実行されてsend が find_first を呼び出して最終的に find メソッドが実行される deprecated API となっているのは互換性を維持するためだろうと推測
find_by_* が method_missing で どのように処理されているかが 見終わりました🙌
find_by_* とmethod_missing まとめ • find メソッドに:first, :all, options などを指定する代わりに使用する •
find_by_* はメソッドとして個別に定義されているわけではなく method_missing を オーバーライドして動的に実現されている • method_missing の処理としては最終的に find メソッドが実行される ◦ 条件分岐で偽の場合は send メソッド で find_first メソッドを経由していた
実は find_by_* は今もあります 今は find_by や where が使える ため、あえて使う必要はない https://railsguides.jp/active_record_querying.html#%E5%8B%95%E7%9A%84%E6%A4%9C%E7%B4%A2
Rails 8.0 の find_by_* の実装箇所 activerecord/lib/active_record/dynamic_matchers.rb module ActiveRecord module
DynamicMatchers # :nodoc: private def respond_to_missing?(name, _) if self == Base super else match = Method.match(self, name) match && match.valid? || super end end def method_missing module として切り出されていて、 内容も Rails 1.0 の頃とはかなり違う!
特におもしろいと感じた部分 find_by_* と method_missing
おもしろいと感じた理由 Rails 1.0 のころは find_by や where など 今は当たり前にあるメソッドがなかったという 当時特有の背景
おもしろいと感じた理由 method_missing はお仕事のコードでは使わないので、Rails で使 用箇所を見てテンションが上がった!! コードの内容もわかりやすい!
おもしろいと感じた理由 find_by_* は今も使用できるけれど、 実装が Rails 1.0 の頃とは全然違う! 月日を経た Rails の変遷
Ruby っておもしろい!!