Railsで見つけた気になったコード
僕はそこそこ長いことRailsで開発をしていて、今まで色んな会社で色んなコードを見てきたけど、Railsのソースコードを読んでいるとパッと見ただけでは説明できないコードと遭遇する
Ruby大好きで読めないコードがあるのはとても悔しいので、しっかり読んで意味を理解したいと思う
世界最高のRubyist達が書いたコードを読んでRuby力を上げていきましょう
define_callbacksメソッド
まずはactive_supportのdefine_callbacksメソッドのこの部分↓
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def _run_#{name}_callbacks(&block)
run_callbacks #{name.inspect}, &block
end
def self._#{name}_callbacks
get_callbacks(#{name.inspect})
end
def self._#{name}_callbacks=(value)
set_callbacks(#{name.inspect}, value)
end
def _#{name}_callbacks
__callbacks[#{name.inspect}]
end
RUBY
ヒアドキュメントを使っているのはわかるが、 __FILE__
とか __LINE__
ってなんのためにあるんだろうか?(しかも + 1
とは...?_?)
<<-RUBY
とハイフンを入れているのは、終端行の RUBY
の前にインデントを入れるためであることはわかる
https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#here
その後の __FILE__
などはmodule_evalのドキュメントを見たら理解できた
module_eval
は3つの引数を受け取ることができて、上のコードでは
- 第1引数に
<<-RUBY〜RUBY
までのヒアドキュメントの内容(すなわちメソッド定義) - 第2引数に
__FILE__
- 第3引数に
__LINE__
を受け取っている
ヒアドキュメントの挙動を小さいコードで確認してみるとより理解することができる↓
module Hakozaru2
def self.hoge
fuga 123, <<-RUBY, "arg3"
ヒアドキュメントの内容
RUBY
end
def self.fuga(arg1, arg2, arg3)
p arg1
p arg2
p arg3
end
end
# [1] pry(main)> Hakozaru2.hoge
# 123
# " ヒアドキュメントの内容\n"
# "arg3"
# => "arg3"
第2引数、第3引数はmodule_evalのドキュメントを見ると、動的に定義した第1引数の内容が、あたかも指定されたファイル(第2引数)の行数(第3引数)に書かれているかのように、スタックトレースなどで表現することができるというもの
module Hako1
module_eval <<-RUBY
def self.hoge
raise
end
RUBY
end
# [1] pry(main)> Hako1.hoge
# RuntimeError:
# from (eval):3:in `hoge'
module Hako2
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def self.hoge
raise
end
RUBY
end
# [1] pry(main)> Hako2.hoge
# RuntimeError:
# from sample_code.rb:135:in `hoge'
おおーちゃんと正しいファイル名と行数になってるー!(この例じゃ行数が正しいかはわからないかもですが)
ちなみに行数は + 1
しないと正しい行数にならないのと、 module_eval
はローカル変数を外側のスコープと共有するので内側から name
を参照することができている
1つ1つ分解すると全然普通にRubyの機能を使っているだけだけど、いくつか組み合わさると面食らいますね
あとヒアドキュメントの理解が足りてませんでしたすいませんと言う気持ち
exceptメソッド
お次は同じくactive_supportのcore_extのexceptメソッド
def except(*keys)
slice(*self.keys - keys)
end unless method_defined?(:except)
メソッド定義も後置if/unlessってできるんですね(知らなかった...)
class Hakozaru
def hoge
p "first hoge"
end
def hoge
p "second hoge"
end
end
# hako = Hakozaru.new
# hako.hoge
# "second hoge"
# => "second hoge"
普通は後勝ち
class Hakozaru
def hoge
p "first hoge"
end
def hoge
p "second hoge"
end unless method_defined?(:hoge)
end
# hako = Hakozaru.new
# hako.hoge
# "first hoge"
# => "first hoge"
はぁーすげぇ〜
ちなみにこの method_defined?(:except)
の判定が必要なのは、Ruby2.7系まではハッシュに except
メソッドがなく、Ruby3.0から追加されているからである
(最初に判定入ったのはここ)
あと関係ないけど、もしクラスメソッドで判定したいならこんな感じ↓ですかね
class Hakozaru
def self.hoge
p "first hoge"
end
def self.hoge
p "second hoge"
end unless singleton_methods.include?(:hoge)
end
# [1] pry(main)> Hakozaru.hoge
# "first hoge"
# => "first hoge"
今後も見つけ次第ストックして見ていこう