Вопрос

I have some STI setup like this:

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

Now I want to dup the LinkedDocument and store it as an OriginalDocument, with its own name and keep the attribute values on duplication. However, my approachs are failing because somewhere, the duplicate still wants to access its delegated methods in the after_* callbacks.

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

This throws a RuntimeError: LinkedDocument#description delegated to original_document.description, but original_document is nil.

Doing an additional copy.type = 'OriginalDocument' to enforce things won't work, since the save query involves the type; UPDATE documents SET [...] WHERE documents.type IN('OriginalDocument') [...]. This fails, because at the time of the transaction, the object still is of type LinkedDocument.

What would be a clean way to copy an object and let it become another one? I thought of calling update_column for type and every attribute I want to copy over, but before doing it that inelegant way, I wanted to ask here.

Это было полезно?

Решение

I am going to add my solution here, in case no one has a better one. Hopefully, it will help someone.

To let the object become another without having wrong queries because the where clause is checking for the wrong type, I manually updated the type column without invoking any callbacks before calling 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

Now for the assignments, there were two problems: First, the attribute setters somehow may trigger an exception because of the delegations. Second, the attributes I wanted to mass-assign were not listed in e.g. attr_accessible intentionally because they were internal attributes. So I resorted to a loop with an ugly update_column statement producing way too much queries (since rails3 has no update_columns).

original.attributes.except('id', 'name', 'original_document_id').each do |k,v|
  document.update_column k.to_sym, v
end
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top