5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

attribute, read_attribute, _read_attribute の違い

Last updated at Posted at 2021-04-29

環境 / 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 で定義されています。

read.rb
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) では このような違いはありませんでした。

attributeread_attribute も ActiveRecord 6.1.3.1 と同じように read.rb で定義されていましたが、 シンボルを渡した場合でも文字列を渡した場合でも、 同じ結果を返していました。 そのとき _read_attribute は存在しませんでした。

read.rb
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
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?