使用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。
三个问题:
- 这是正确的方法(鉴于我无法更改遗留数据库架构)?
- 我做得对吗? 我以前从未重新打开过类[编辑:或模块] (即使是正确的术语?)
- 为什么我不能调用现有的
_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属性)时,自定义主键处理都会启动并更新主键。
我的具体问题的答案是:
- 鉴于我无法改变遗留架构,我没有太多选择。 FWIW,我最终覆盖了
write_attribute
方法(仅当我们包含monkey补丁时),而不是将write_id_attribute
方法添加到现有模块。 - 我正在重新打开模块(但请参阅前一个和下一个答案)
- 我仍然不确定为什么我不能调用现有的
_write_attribute
方法。 我怀疑有某种Ruby或Rails“魔术”以不同的方式对待_methods。 最后我不得不重新实现它(见上面的解决方案)