🦥
ActiveModel::Modelモジュールのinitializeメソッドは何をやっているか
はじめに
ActiveModel::Modelをincludeすると、
インスタンス生成時には単純にインスタンス変数への代入が行われているのではなくセッターメソッドを通して代入が行われていることがわかります。
# initializeメソッドを作成する場合
class User
attr_reader :name
def initialize(attributes = {})
@name = attributes[:name]
end
def name=(name)
@name = "#{name}君"
end
end
User.new(name: "俺")
=> #<User:0x00007f8c42d26b28 @name="俺">
# ActiveModel::Modelをincludeする場合
class User
include ActiveModel::Model
attr_reader :name
def name=(name)
@name = "#{name}君で〜す"
end
end
User.new(name: "俺")
=> #<User:0x00007f8c52d93150 @name="俺君で〜す">
そこで今回はActiveModel::Modelをincludeした時に呼び出されるinitializeメソッドを調査してみようと思います。
ActiveModel::Model
# rails/activemodel/lib/active_model/model.rb
module ActiveModel
..(省略)..
module Model
include ActiveModel::AttributeAssignment
..(省略)..
def initialize(attributes = {})
assign_attributes(attributes) if attributes
super()
end
end
end
initializeメソッドを見てみると、assign_attributesというメソッドが呼び出されています。このメソッドはActiveModel::AttributeAssignmentというモジュールに含まれています。
assign_attributes メソッド
# rails/activemodel/lib/active_model/attribute_assignment.rb
module ActiveModel
module AttributeAssignment
include ActiveModel::ForbiddenAttributesProtection
def assign_attributes(new_attributes)
# new_attributesがeach_pairメソッドを持たなければ(= hashでなければ)エラー
unless new_attributes.respond_to?(:each_pair)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
end
return if new_attributes.empty?
# sanitize_for_mass_assignmentはマスアサインメントの際の許可に関するメソッドで、
# インスタンス生成時にhashをそのまま入れる場合は関係ないので今回は省略します
_assign_attributes(sanitize_for_mass_assignment(new_attributes))
end
alias attributes= assign_attributes
private
def _assign_attributes(attributes)
attributes.each do |k, v|
_assign_attribute(k, v)
end
end
def _assign_attribute(k, v)
setter = :"#{k}="
if respond_to?(setter)
public_send(setter, v)
else
raise UnknownAttributeError.new(self, k.to_s)
end
end
end
end
assign_attributesメソッドでは_assign_attributesというprivateメソッドが呼ばれていることがわかります。_assign_attributeメソッドではpublic_sendメソッドでセッターメソッドを呼んでいることがわかります。
# public_methodでメソッドを呼び出す例
class Child
def greeting(name)
p "おはようございます、#{name}さん!"
end
end
Child.new.public_send("greeting", "太郎")
=> "おはようございます、太郎さん!"
これにより、ActiveModel::Modelをincludeするとinitializeメソッドではセッターメソッドが呼ばれていることがわかりました。
includeではなく、コードをそのまま持ってくると以下のようになります(エラー処理などは省略)
class Person
attr_reader :name
def initialize(attributes = {})
assign_attributes(attributes) if attributes
end
def name=(name)
@name = "#{name}君"
end
private
def assign_attributes(attributes)
_assign_attributes(attributes)
end
def _assign_attributes(attributes)
attributes.each do |k, v|
_assign_attribute(k, v)
end
end
def _assign_attribute(k, v)
setter = :"#{k}="
if respond_to?(setter)
public_send(setter, v)
else
raise UnknownAttributeError.new(self, k.to_s)
end
end
end
Person.new(name: "太郎")
=> #<Person:0x00007f8c52b22a60 @name="太郎君">
Discussion