Railsの ActiveRecordで レコードの属性にアクセスする際は動的に生成されたメソッドを使いますが、そのようなメソッドを上書きしている場合、値に直接アクセスする必要があります。このような属性情報は @attributes に保存されています。

/lib/active_record/attribute_methods.rbに定義されてる attributes メソッドを経由してアクセスしていましたが、なんとなく @attributes へ直接アクセスするだけかとおもってたのですが、違ったようです。

def attributes
  attrs = {}
  attribute_names.each { |name| attrs[name] = read_attribute(name) }
  attrs
end

という定義になってました。

attribute_names は文字列で属性の一覧を返すので @attributesは 普通のHashでキーが文字列です。 もし email というの属性にアクセスしたい場合は attributes["email"] になります。 attributes[:email] ではアクセスすることができません。

しかし、 read_attributeは シンボルでも文字列でも使用することができて、 read_attribute :email でも read_attribute "email" のどちらでも良いみたいです。

ちなみにエイリアスがあって [] メソッドになります。なので self[:email] などでアクセスできます。pubilcメソッドです。

read_attribute があるということはも write_attribute もあります。

ついでにもう少し深追い

read_attributeの実装もついでにおってみると

def read_attribute(attr_name)
  self.class.type_cast_attribute(attr_name, @attributes, @attributes_cache)
end

となってました。クラスメソッドを経由するようです こいつも中身を追うと

def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc:
  return unless attr_name
  attr_name = attr_name.to_s

  if generated_external_attribute_methods.method_defined?(attr_name)
    if attributes.has_key?(attr_name) || attr_name == 'id'
      generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name)
    end
  elsif !attribute_methods_generated?
    # If we haven't generated the caster methods yet, do that and
    # then try again
    define_attribute_methods
    type_cast_attribute(attr_name, attributes, cache)
  else
    # If we get here, the attribute has no associated DB column, so
    # just return it verbatim.
    attributes[attr_name]
  end
end

渡された名前がすぐに文字列に変換されてます。

そしてまず 属性にアクセスするためのメソッドがあるかどうか確認するようです。ある場合はそちらに処理を渡すようです。attributesにまわってくるオブジェクトがHashじゃないモデルオブジェクトの場合の処理っぽいです。 また、まだ未定義なだけな場合は定義してからアクセスするようです。 それ以外のただのhashの場合は直接アクセスしにいくようです。

ここでの attributes はActireRecordの attributes メソッドではなくただのHashです。一瞬、無限ループしてるように思えたので一応書いておきます。