更新旧的“ID"不是 ActiveRecord 主键的列 [英] Update a legacy "ID" column that isn't the primary key with ActiveRecord

查看:52
本文介绍了更新旧的“ID"不是 ActiveRecord 主键的列的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using Rails and the activerecord-sqlserver-adapter gem to try and add data to a legacy MS SQL database whose dbo.Condition table has a primary key called ConditionSeq and a foreign key column ID that stores the user 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)),希望能让MSSQL 自动增加 conditionseq 列,ActiveRecord 无益地假设我实际上想将主键设置为 12345.

Every time I write Condition.new(conditionseq: nil, id: 12345) (or even Condition.new(id: 12345)), hoping to let MS SQL auto increment the conditionseq column, ActiveRecord unhelpfully assumes I actually want to set the primary key to 12345.

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

Based on a similar question and particularly @cschroed's answer and follow up comment, I have tried reopening ActiveRecord::AttributeMethods::Write (source) to add a write_id_attribute method that forgoes the attr_name == "id" check:

# 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 抛出 NoMethodError 异常.

I can now call that method but it throws a NoMethodError Exception for _write_attribute.

三个问题:

  1. 这是正确的方法吗(因为我无法更改旧数据库架构)?
  2. 我做得对吗?我以前从未重新打开过课程(这是否是正确的术语?)
  3. 为什么我不能调用现有的 _write_attribute 方法?
  1. Is this the right approach (given I can't change the legacy DB schema)?
  2. Am I doing it right? I've never reopened a class [edit: or module] before (is that even the right terminology?)
  3. Why can't I call the existing _write_attribute method?

推荐答案

当我回到工作岗位时,我与一些更有经验的 Ruby 开发人员一起解决了这个问题,我们得出的结论是,这是 ActiveRecord 中未处理的边缘情况(请参阅更多信息请参见下文).

I worked through this with some more experienced Ruby devs when I was back at work and we came to the conclusion that this is an unhandled edge case in ActiveRecord (see below for more info).

我的最终解决方法(带注释)是覆盖关注点中的现有方法,并且仅在我们需要时才包含该关注点:

My eventual workaround (with comments) is to override the existing method(s) in a concern and only include that concern when we need it:

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] 显式设置 id:

We still can't call Condition.new(id: 12345, other_column: other_value) (or Condition.create(... or condition.id) but the workaround does let us explicitly set the id using 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 添加一个测试来演示这种行为并提出它是一个问题.

Once our current sprint is over my goal is to add a test to ActiveRecord to demonstrate this behaviour and raise it is an issue.

附加信息

Rails 对自定义主键进行了测试.它还对具有不是主键的 ID 列进行了测试.未处理的边缘情况似乎是当您同时拥有两者时...

Rails has tests for custom primary keys. It also has tests for having an ID column that isn't the primary key. The unhandled edge case seems to be when you have both at the same time...

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

Every time you try and update the ID column (by passing in an id attribute) the custom primary key handling kicks in and updates the primary key instead.

我的具体问题的答案是:

The answers to my specific questions are:

  1. 鉴于我无法更改旧架构,我没有太多选择.FWIW,我最终覆盖了 write_attribute 方法(仅当我们包含猴子补丁时),而不是向现有模块添加一个 write_id_attribute 方法.
  2. 我正确地重新打开了模块(但请参阅上一个和下一个答案)
  3. 我仍然不确定为什么我不能调用现有的 _write_attribute 方法.我怀疑有某种 Ruby 或 Rails魔法"以不同的方式对待 _methods.最后我也不得不重新实现它(见上面的解决方案)
  1. Given I can't change the legacy schema, I don't have a lot of choice. FWIW, I ended up overwriting the write_attribute method (only when we include the monkey patch) rather than adding a write_id_attribute method to the existing module.
  2. I was reopening the module correctly (but see previous and next answers)
  3. I'm still not sure why I couldn't call the existing _write_attribute method. I suspect there's some sort of Ruby or Rails "magic" that treats _methods differently. In the end I had to reimplement it too (see solution above)

这篇关于更新旧的“ID"不是 ActiveRecord 主键的列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆