STI,代表并成为

时间:2013-04-16 12:17:04

标签: ruby-on-rails ruby-on-rails-3 sti

我有一些像这样的STI设置:

class Document < ActiveRecord::Base
  attr_accessible :name, description

  # Basic stuff omitted
end

class OriginalDocument < Document
  has_many :linked_documents, foreign_key: :original_document_id, dependent: :destroy
end

class LinkedDocument < Document
  belongs_to :original_document

  # Delegation, because it has the same attributes, except the name
  delegate :description, to: :original_document
end

现在我要复制LinkedDocument并将其存储为OriginalDocument,并使用自己的名称并将属性值保留为重复。但是,我的方法失败了,因为在某个地方,副本仍然想要在after_ *回调中访问它的委托方法。

class LinkedDocument < Document
  def unlink_from_parent
    original = self.original_document

    copy = self.becomes OriginalDocument
    copy.original_document_id = nil
    copy.description = original.description
    copy.save 
  end
end

这会抛出RuntimeError: LinkedDocument#description delegated to original_document.description, but original_document is nil

执行额外的copy.type = 'OriginalDocument'执行操作不起作用,因为保存查询涉及类型; UPDATE documents SET [...] WHERE documents.type IN('OriginalDocument') [...]。这失败了,因为在事务处理时,对象仍然是LinkedDocument类型。

复制对象并让它成为另一个对象的干净方法是什么?我想为update_column调用type以及我要复制的每个属性,但在这样做之前,我想问这里。

1 个答案:

答案 0 :(得分:1)

我将在这里添加我的解决方案,以防没有人有更好的解决方案。希望它会帮助某人。

为了让对象成为另一个而没有错误的查询,因为where子句正在检查错误的类型,我在调用become之前手动更新了类型列而没有调用任何回调。

# This is for rails3, where +update_column+ does not trigger 
# validations or callbacks. For rails4, use 
#
#   self.update_columns {type: 'OriginalDocument'}
#
self.update_column :type, 'OriginalDocument' 
document = self.becomes OriginalDocument

现在,对于分配,存在两个问题:首先,由于委托,属性设置器可能以某种方式触发异常。其次,我想要批量分配的属性没有列在例如attr_accessible故意因为它们是内部属性。所以我使用了一个带有丑陋的update_column语句的循环,产生了太多的查询(因为rails3没有update_columns)。

original.attributes.except('id', 'name', 'original_document_id').each do |k,v|
  document.update_column k.to_sym, v
end