【问题标题】:Rails ActiveRecord handle an id column that is not the primary keyRails ActiveRecord 处理不是主键的 id 列
【发布时间】:2014-04-25 16:25:40
【问题描述】:

努力使用 ActiveRecord 自动将 :id 属性分配为主键,即使它是一个单独的列。

Table - legacy-table

    id - int
    pk_id - int (primary key)
    name - varchar2
    info - varchar2

型号

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

我不关心 ActiveRecord 自动将主键 (pk_id) 分配给 :id 属性,但是我失去了对实际 id 列的所有访问权限。尝试使用别名只是将我指向主键。

但是,对这个问题的一个警告是,从视图中我可以使用@legacymodel[:id] 访问 id 列。但是再次调用@legacymodel.id 时,我得到了 pk_id 列的值。我想要的是能够调用 @legacymodel.other_id 并让它指向 id 列。相反,@legacymodel.service_id、@legacymodel.id 和 @legacymodel.pk_id 都指向同一列 pk_id

请注意,这是一个遗留数据库,修改列是不可能的。我正在使用带有 MySql 的 Rails 4。

有没有办法解决这个问题?为什么@legacymodel[:id] 给我的结果与@legacymodel.id 不同?

【问题讨论】:

    标签: mysql ruby-on-rails rails-activerecord primary-key


    【解决方案1】:

    @cschroed 的答案在最新的 Rails (v4.2) 中对我不起作用。深入研究 Rails 源代码,如果传递的键等于“id”,read_attribute 似乎也将使用主键值:

      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

    由于 [] 方法使用 read_attribute,因此不再有效。

    我发现直接从属性哈希中读取是可行的:

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

    这提供了一种通过模仿 _read_attribute 来绕过 read_attribute 的方法。

    【讨论】:

      【解决方案2】:

      read_attribute method 将从@attributes 哈希中读取一个值。 [] method 使用 read_attribute。所以@legacymodel[:id] 获取id 列的值。

      write_attribute method 总是尝试将id 翻译成主键的名称...

      # 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
      

      ...而[]= method 使用write_attribute。所以@legacymodel[:id] = &lt;value&gt; 会在主键列中设置一个值pk_id

      id method 是一种特殊方法,在此处别名为 primary_key

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

      所以@legacymodel.id 将获得pk_id 列的值。

      如果您只想通过@legacymodel.other_id 读取id 列,那么您可以定义如下方法:

      # LegacyModel class
      def other_id
        self[:id]
      end
      

      但是如果您还需要通过@legacymodel.other_id= 写入id 列,那么您可能需要尝试找到一种安全的方法来覆盖write_attribute 方法,以便您可以解决attr_name = self.class.primary_key if attr_name == 'id' &amp;&amp; self.class.primary_key 语句.

      【讨论】:

      • 同意,是否有任何简单的解决方法可以通过 write_attribute 逻辑?还是我唯一的选择是覆盖 ActiveRecord?
      • 我想不出解决这个问题的好方法。要考虑的一个选项是创建一个名为write_id_attribute 的新方法,它是write_attribute 的副本,没有primary_key 逻辑。然后在你的模型中你可以有def other_id=(value) write_id_attribute(:id, value) end。这将确保特殊逻辑仅用于此模型中的id,而标准write_attribute 仍将用于其他所有内容。
      • 我遇到了类似的问题,所以我尝试添加一个write_id_attribute 方法......它原则上有效,但最后我在一个问题中覆盖了write_attribute 方法,只在需要时才包含它。我的相关问题和answer 中的详细信息
      猜你喜欢
      • 2021-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-10
      • 1970-01-01
      相关资源
      最近更新 更多