Обновите устаревший столбец «ID», который не является первичным ключом, с помощью ActiveRecord - PullRequest
0 голосов
/ 15 сентября 2018

Я использую Rails и activerecord-sqlserver-adapter gem , чтобы попытаться добавить данные в устаревшую базу данных MS SQL, чья таблица dbo.Condition имеет первичный ключ с именем ConditionSeq и внешний ключ. столбец 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 ) добавить метод write_id_attribute, который отменяет проверку attr_name == "id":

# 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

Теперь я могу вызвать этот метод, но он выдает исключение NoMethodError для _write_attribute.

Три вопроса:

  1. Это правильный подход (учитывая, что я не могу изменить устаревшую схему БД)?
  2. Я правильно делаю? Я никогда раньше не открывал класс [edit: или модуль] (это даже правильная терминология?)
  3. Почему я не могу вызвать существующий метод _write_attribute?

1 Ответ

0 голосов
/ 18 сентября 2018

Я работал с некоторыми более опытными разработчиками 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 = ::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

Как только закончится наш текущий спринт, моя цель - добавить тест в ActiveRecord, чтобы продемонстрировать это поведение и поднять его.

Дополнительная информация

В Rails есть тесты для пользовательских первичных ключей. У него также есть тесты на наличие столбца ID, который не является первичным ключом. Случай необработанного края, кажется, когда вы оба одновременно ...

Каждый раз, когда вы пытаетесь обновить столбец идентификатора (передавая атрибут id), пользовательская обработка первичного ключа запускается и обновляет первичный ключ.

Ответы на мои конкретные вопросы:

  1. Учитывая, что я не могу изменить устаревшую схему, у меня нет большого выбора. Я закончил тем, что переписал метод write_attribute (только когда мы включили патч обезьяны), а не добавил метод write_id_attribute в существующий модуль.
  2. Я правильно открывал модуль (но смотри предыдущий и следующий ответы)
  3. Я все еще не уверен, почему я не смог вызвать существующий метод _write_attribute. Я подозреваю, что есть какая-то "магия" Ruby или Rails, которая по-разному относится к _методам. В конце концов, мне тоже пришлось это переопределить (см. Решение выше)
...