Active Model の attribute はどのように書かれているのか? ②
4行目
no_defaultという引数
まずは、no_defaultが気になります。
ここはこのdefの引数の時に、以下のようになっていました。
def attribute(name, type = nil, default: (no_default = true), **options)
知らなかった書き方です。()とかいけるんだ・・・
ちょっと検証してみました。
以下のようなmethodを定義します。
class Test
# attr
attr_accessor :name, :age
def test_method(default: (no_default = true))
puts "no_default: #{no_default}"
puts "default: #{default}"
end
end
パターン1:defaultを渡さないで実行
> test.test_method
no_default: true
default: true
=> nil
この時は、no_defaultがtrueになると。
no_defaultが引数のところで初期化できるのが驚きです。
ただ、この時にdefaultがtrueが入るのが不思議だし、注意しないと・・・・
パターン2:defaultを渡して実行
> test.test_method(default: "a")
no_default:
default: a
=> nil
この時は、no_defaultは何も入らなくなりましたね。
nilでもないのが不思議です。defaultにaが入るのは想像通り。
ちょっとtest_methodの内容がよくなかったので、以下のように修正
def test_method(default: (no_default = true))
puts "default: #{default}" if !no_default
puts "no_default: #{no_default}" if no_default
end
結果
> test.test_method
no_default: true
=> nil
> test.test_method(default: "a")
default: a
=> nil
この結果から見るに、no_defaultの真偽値を条件の判定に使用できることが確認できました。
PendingType???
さあ、戻って、そのno_defaultと、typeがある時(if type || no_default
)に、PendingType.new(name, type)
としていますね。
PendingTypeとか聞いたことないですね~
Pendingって、保留している?遅延実行的なことをしている?
いつもみたいに他のmoduleにあるのかなと思ったら、下の方で定義されていました。
こんな感じで変数を定義できたんですね!知らなかったです。defしか定義したことなかった。
まずは、Structクラスというみたこと無いクラスを調べます。
rubyで構造体を使ったことがない・・・とうか、すごい昔に学校でCを習った時にしか使ったことがないような。
さて、PendingTypeというStructを見てみます。
PendingType = Struct.new(:name, :type) do
まずは、:nameと:typeのメンバを定義していますね。また、doブロックを次で渡していそうです。
ブロックでは、次のようなインスタンスメソッドが定義されていますね。
def apply_to(attribute_set)
attribute = attribute_set[name]
attribute_set[name] = attribute.with_type(type || attribute.type)
end
attribute_setというParamsを期待していますね、また、次の行の
attribute = attribute_set[name]
からみるに、attribute_setは、何かしらのクラスなどだと予想。
このactivemodel/lib/active_model/attribute_registration.rb
のrequireにそれっぽい定義がありました、
require "active_support/core_ext/class/subclasses"
>ココ require "active_model/attribute_set"
require "active_model/attribute/user_provided_default"
module ActiveModel
そのファイルに寄り道します・・・ 難しいのに読むとこ多くてツライ!!
寄り道 class AttributeSet
さて、先ほどの名前からして、まずはAttributeSetクラスから見ていきましょう。
class AttributeSet # :nodoc:
class Builder # :nodoc:
attr_reader :types, :default_attributes
def initialize(types, default_attributes = {})
@types = types
@default_attributes = default_attributes
end
def build_from_database(values = {}, additional_types = {})
LazyAttributeSet.new(values, types, additional_types, default_attributes)
end
end
end
ええっと、まず普通になんでBuilderっていうクラスでラップしてるんだ・・・
この時のBuilderクラスのようなインナークラスは、親クラスの(今回はAttributeSet)の定数として扱われるみたいです。
また、名前空間の一部になるので、使うならスコープ解決演算子(::)を使用し、AttributeSet::Builder
とする必要があると。
調べてみると、Builderパターンという記事がいくつかありました。
また、各種AIチャットにも聞きましたが、Builderパターンだと判定しました。
今の知識ではこのBuilderパターンのメリットがあんまり納得できないので、
まあ生成に一貫性を持たせれて、関するロジックを区切れていいな〜くらいで次に行きたいと思います。
このBuilderの内部の、肝心のbuild部分では、LazyAttributeSetをインスタンス化していますね。
ここら辺はコレを呼び出しているコードに会うまで置いておきます。
戻る
少し寄り道をしたので振り返ると、今は4行目を見ていました。
module ClassMethods # :nodoc:
def attribute(name, type = nil, default: (no_default = true), **options)
name = resolve_attribute_name(name)
type = resolve_type_name(type, **options) if type.is_a?(Symbol)
type = hook_attribute_type(name, type) if type
> pending_attribute_modifications << PendingType.new(name, type) if type || no_default
pending_attribute_modifications << PendingDefault.new(name, default) unless no_default
reset_default_attributes
end
~~~省略~~~
private
PendingType = Struct.new(:name, :type) do # :nodoc:
def apply_to(attribute_set)
attribute = attribute_set[name]
attribute_set[name] = attribute.with_type(type || attribute.type)
end
end
そうですね、もしtypeがある時か、default値がないときはPendingType.new(name, type)
をすると。
そしてその結果を、pending_attribute_modifications
に入れていますね。
コレあれですね、ここで入れとくだけで、PendingTypeが持つApply_toは行っていないので、遅延実行のための準備なんでしょうか・・・?
まだよくわかりません。
5行目
お次はこちらです。
pending_attribute_modifications << PendingDefault.new(name, default) unless no_default
先にPendingDefaultから見ると、以下の様になっています。
PendingDefault = Struct.new(:name, :default) do # :nodoc:
def apply_to(attribute_set)
attribute_set[name] = attribute_set[name].with_user_default(default)
end
end
メンバは、nameとdefaultを受け取り、
またPendingTypeのようにapply_toを持っていますね。
そして、unless no_defaultなのでデフォルト値があるときは、4行目と同じように
pending_attribute_modifications
に入れていますね。
4,5行目に関しては、これからどの様にしてpending_attribute_modifications
を呼び出したり使ったりしていくのか、気にしていきたいところです。
6行目
さて、やっとdef attributeの最後の行です。
reset_default_attributes
privateにありました。
def reset_default_attributes
reset_default_attributes!
subclasses.each { |subclass| subclass.send(:reset_default_attributes) }
end
def reset_default_attributes!
@default_attributes = nil
@attribute_types = nil
end
面白いのが、
reset_default_attributes
の中で、reset_default_attributes!
を呼び出していますね。
reset_default_attributes
の方は、さらに以下の処理をしています。
subclasses.each { |subclass| subclass.send(:reset_default_attributes) }
これ面白いです。まずはsubclasses
っていうメソッドがあるのか?調べてみます。
おそらく、3行目でrequireしているこの中にありそうです。
なんだ、標準のObjectクラス全般で使えるのかと・・・
ありました〜。これはactive support moduleなのかあ
いや、ここでもsubclassesが使われていますね・・・
def descendantsという名前のものしかないですし_・・
これもrequireしてるファイルを見にいきます。
こちらでしょうか。
コレはまた、読むのに一苦労しそうなファイルです・・・
ちょっと置いておきます。また別途調べてみます。
6行目に戻ると、
でも要は最後はattributeをnilでクリアしときましょうよ!ということですね!!
まとめ
今回はこちらのattributeクラスメソッドを見てみました。
普段の開発では書かないような記法であったり、クラス設計であったりするので、とても勉強になりました。
ただ、コレだけだとまだ謎な部分が多いですので、pending_attribute_modifications
の呼び出しについても追っていきたいなと思いました。(余力があれば。)
Discussion