Rails ActiveRecord handle an id column that is not the primary key

一个人想着一个人 提交于 2019-11-30 23:47:43

The read_attribute method will read a value out of the @attributes hash. The [] method uses read_attribute. So @legacymodel[:id] gets the value of the id column.

The write_attribute method always tries to translate id into the name of the primary key...

# ActiveRecord::AttributeMethods::Write
def write_attribute(attr_name, value)
  attr_name = attr_name.to_s
  attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key

...and the []= method uses write_attribute. So @legacymodel[:id] = <value> will set a value into the primary key column, pk_id.

The id method is a special method that is aliased to the primary_key here:

# ActiveRecord::AttributeMethods::PrimaryKey
if attr_name == primary_key && attr_name != 'id'
  generated_attribute_methods.send(:alias_method, :id, primary_key)
end

So @legacymodel.id will get the value of the pk_id column.

If you just want to read the id column through @legacymodel.other_id, then you could define a method like:

# LegacyModel class
def other_id
  self[:id]
end

But if you also need to write to the id column through @legacymodel.other_id=, then you might need to try to find a safe way to override the write_attribute method so that you can work around the attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key statement.

The answer by @cschroed did not work for me in the latest Rails (v4.2). Digging into the Rails source code, it appears that read_attribute will also use the primary key value if the key passed equals 'id':

  ID = 'id'.freeze

  # Returns the value of the attribute identified by <tt>attr_name</tt> after
  # it has been typecast (for example, "2004-12-12" in a date column is cast
  # to a date object, like Date.new(2004, 12, 12)).
  def read_attribute(attr_name, &block)
    name = attr_name.to_s
    name = self.class.primary_key if name == ID
    _read_attribute(name, &block)
  end

https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/attribute_methods/read.rb

Since, the [] method uses read_attribute, this no longer works.

I found that directly reading from the attributes hash worked instead:

# LegacyModel class
def other_id
  @attributes.fetch_value('id')
end

This provided a means of bypassing read_attribute by mimicking _read_attribute.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!