使用ActiveRecord更新不是主键的旧“ID”列

我正在使用Rails和activerecord-sqlserver-adapter gem来尝试将数据添加到旧的MS SQL数据库,其中dbo.Condition表具有名为ConditionSeq的主键和存储用户ID的外键列ID。

 class Condition < ActiveRecord::Base # using lowercase_schema_reflection = true self.table_name = :condition self.primary_key = 'conditionseq' end 

每次我写Condition.new(conditionseq: nil, id: 12345) (或甚至Condition.new(id: 12345) ),希望让MS SQL自动增加conditionseq列,ActiveRecord无益地假设我实际上想要设置主要12345的关键。

基于类似的问题 ,特别是@cschroed的回答和后续评论 ,我尝试重新打开ActiveRecord :: AttributeMethods :: Write( source )来添加一个放弃attr_name ==“id”检查的write_id_attribute方法:

 # config/initializers/write_id_attribute.rb module ActiveRecord module AttributeMethods module Write extend ActiveSupport::Concern # Add a method to allow us to update a column called "ID" instead of # Rails trying to map ID attribute to the legacy tables primary key column def write_id_attribute(attr_name, value) name = if self.class.attribute_alias?(attr_name) self.class.attribute_alias(attr_name).to_s else attr_name.to_s end primary_key = self.class.primary_key sync_with_transaction_state if name == primary_key _write_attribute(name, value) end end end end 

我现在可以调用该方法,但它会为_write_attribute抛出NoMethodErrorexception。

三个问题:

  1. 这是正确的方法(鉴于我无法更改遗留数据库架构)?
  2. 我做得对吗? 我以前从未重新打开过类[编辑:或模块] (即使是正确的术语?)
  3. 为什么我不能调用现有的_write_attribute方法?

当我回到工作岗位时,我与一些经验丰富的Ruby开发人员一起完成了这项工作,我们得出结论,这是ActiveRecord中未处理的边缘情况(更多信息见下文)。

我最终的解决方法(带注释)是覆盖关注点中的现有方法,只在我们需要时才包含这个问题:

 module Concerns::ARBugPrimaryKeyNotIDColumnWorkaround extend ActiveSupport::Concern # Override this (buggy?) method to allow us to update a column called "ID", even if there's a different primary key # ActiveRecord tries to map ID attribute to the legacy table's primary key column... # Doesn't check to make sure there isn't already another column called ID that we're actually trying to update # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_methods/write.rb # TODO: Submit a patch to rails/activerecord def write_attribute(attr_name, value) name = if self.class.attribute_alias?(attr_name) self.class.attribute_alias(attr_name).to_s else attr_name.to_s end primary_key = self.class.primary_key # name = primary_key if name == "id".freeze && primary_key # BUG: (?) assumes primary key is always called ID sync_with_transaction_state if name == primary_key _write_attribute(name, value) end # Also need to clone this for some reason # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_methods/write.rb def _write_attribute(attr_name, value) # :nodoc: @attributes.write_from_user(attr_name.to_s, value) value end end 

我们仍然无法调用Condition.new(id: 12345, other_column: other_value) (或Condition.create(...condition.id ),但解决方法确实让我们使用condition[:id]显式设置condition[:id]

 condition = ::Condition.new(mapped_attributes) # Don't include ID in mapped_attributes condition[:id] = 12345 # Must be set explicitly using [:id] due to AR bug condition.save 

一旦我们当前的sprint结束,我的目标是向ActiveRecord添加一个测试来演示这种行为,并提出它是一个问题。

附加信息

Rails测试自定义主键。 它还测试了ID列不是主键。 未处理的边缘情况似乎是当你同时拥有两个…

每次尝试更新ID列(通过传入id属性)时,自定义主键处理都会启动并更新主键。

我的具体问题的答案是:

  1. 鉴于我无法改变遗留架构,我没有太多选择。 FWIW,我最终覆盖了write_attribute方法(仅当我们包含monkey补丁时),而不是将write_id_attribute方法添加到现有模块。
  2. 我正在重新打开模块(但请参阅前一个和下一个答案)
  3. 我仍然不确定为什么我不能调用现有的_write_attribute方法。 我怀疑有某种Ruby或Rails“魔术”以不同的方式对待_methods。 最后我不得不重新实现它(见上面的解决方案)