Struggling with ActiveRecord auto assigning the :id attribute as the primary key even though it is a separate column.
Table - legacy-table
id - int
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
.