環境 / Environment
- Rails 6.1.3.1
- Ruby 3.0.1
- OS: macOS
attribute とは
attribute
は Rails での ActiveRecord モデルオブジェクト のメソッドで、 属性名を引数として渡すとその値を返してくれます。
ただしこれは モデルの内部でのみ使えるプライベートメソッドとなっています。
そして実態は _read_attribute
へのエイリアスで、 _read_attribute
はパブリックメソッドになっています。
Rails Console での実行例
>> u = User.new
=>
# <User:0x00007fb142bdd288
...
>> u.name = 'a'
=> "a"
>> u.name
=> "a"
>> u._read_attribute('name')
=> "a"
>> u._read_attribute(:name)
=> nil
文字列を渡すと値が返りますが、シンボルでは値が nil
となります。
u.name
のように、 カラム名をメソッドとして実行した場合にも この _read_attribute
が実行されます。
つまづくポイント
object.column_name = 1
のように値を代入しても、 attribute(:column_name)
では値を取得できません。
もし メソッド column_name
をオーバライドしてしまった場合で、 属性値を直接取得したい場合は、 下の read_attribute
を使う必要があります。
read_attribute とは
read_attribute
は、 _read_attribute
と同様に属性値を返すメソッドですが、 _read_attribute
よりも複雑な処理を行います。
引数を文字列に変換する処理を行っているため、シンボルを引数とした場合でも正しく属性値を返します。
Rails Console での実行例
>> u = User.new
=>
# <User:0x00007fb142bdd288
...
>> u.name = 'a'
=> "a"
>> u.read_attribute('name')
=> "a"
>> u.read_attribute(:name)
=> "a"
文字列を渡した場合でも、シンボルを渡した場合でも、同じ結果となっています。
内部的に、引数を文字列に変換しているため、シンボルを渡しても文字列と同じ結果となります。
定義
紹介した3つのメソッドは、 ActiveRecord で定義されています。
module ActiveRecord
module AttributeMethods
module Read
def read_attribute(attr_name, &block)
name = attr_name.to_s
name = self.class.attribute_aliases[name] || name
name = @primary_key if name == "id" && @primary_key
@attributes.fetch_value(name, &block)
end
# This method exists to avoid the expensive primary_key check internally, without
# breaking compatibility with the read_attribute API
def _read_attribute(attr_name, &block) # :nodoc
@attributes.fetch_value(attr_name, &block)
end
alias :attribute :_read_attribute
private :attribute
実はその昔...
昔の Rails (たとえば version 3.0.1) では このような違いはありませんでした。
attribute
も read_attribute
も ActiveRecord 6.1.3.1 と同じように read.rb
で定義されていましたが、 シンボルを渡した場合でも文字列を渡した場合でも、 同じ結果を返していました。 そのとき _read_attribute
は存在しませんでした。
def read_attribute(attr_name)
attr_name = attr_name.to_s
attr_name = self.class.primary_key if attr_name == 'id'
if !(value = @attributes[attr_name]).nil?
if column = column_for_attribute(attr_name)
if unserializable_attribute?(attr_name, column)
unserialize_attribute(attr_name)
else
column.type_cast(value)
end
else
value
end
else
nil
end
end
def attribute(attribute_name)
read_attribute(attribute_name)
end