Reputation: 10413
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.
Upvotes: 2
Views: 736
Reputation: 10413
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
Upvotes: 1