Reputation: 2843
Struggling with ActiveRecord auto assigning the :id attribute as the primary key even though it is a separate column.
Table - legacy-table
id - int
pk_id - int (primary key)
name - varchar2
info - varchar2
Model
class LegacyModel < ActiveRecord::Base
self.table_name = 'legacy-table'
self.primary_key = 'pk_id'
default_scope {order(:name => :asc)}
alias_attribute :other_id, :id
end
I don't care that ActiveRecord automatically assigns the primary key (pk_id) to the :id attribute however I lose all access to the actual id column. Trying to use the alias simply points me back at the primary key.
However one caveat to this issues is that from the view i can access the id column by using @legacymodel[:id]. But again when calling @legacymodel.id I get the value of the pk_id column. What i want is to be able to call @legacymodel.other_id and have it point to the id column. Instead @legacymodel.service_id, @legacymodel.id, and @legacymodel.pk_id all point to the same column pk_id
Please note that this is a legacy db and modifying the columns are out of the question. I am using Rails 4 with MySql.
Is there anyway to code around this? Why does @legacymodel[:id] give me different results then @legacymodel.id?
Upvotes: 5
Views: 3046
Reputation: 11647
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
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
.
Upvotes: 5
Reputation: 6834
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.
Upvotes: 4