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

前端 未结 2 1759
谎友^
谎友^ 2021-01-06 14:21

Struggling with ActiveRecord auto assigning the :id attribute as the primary key even though it is a separate column.

Table - legacy-table

    id - int
            


        
相关标签:
2条回答
  • 2021-01-06 14:56

    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.

    0 讨论(0)
  • 2021-01-06 15:03

    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.

    0 讨论(0)
提交回复
热议问题