Validation ignored when cloning a newly created record
I have a model UserFile
that belongs_to
a Folder
:
class UserFile < ActiveRecord::Base
has_attached_file :attachment
belongs_to :folder
validates_attachment_presence :attachment
validates_presence_of :folder_id
def copy(target_folder)
new_file = self.clone
new_file.folder = target_folder
new_file.save!
end
end
The following test fails unexpectedly:
test 'cannot copy a file to anything other than a folder' do
folder = Factory(:folder)
file1 = UserFile.create(:attachment => F开发者_运维技巧ile.open("#{Rails.root}/test/fixtures/textfile.txt"), :folder => Folder.root)
file2 = UserFile.find(file1)
# Should pass, but fails
assert_raise(ActiveRecord::RecordInvalid) { file1.copy(nil) }
# Same record, but this DOES pass
assert_raise(ActiveRecord::RecordInvalid) { file2.copy(nil) }
assert file1.copy(folder)
end
The validates_presence_of :folder_id
is ignored when using a newly created object, but when I do an ActiveRecord#find
it DOES work. I think it has something to do with calling clone
in the copy
method, but I cannot figure it out. Does anyone know what is going on or how to make the test pass??
Mischa, cloning is a beast.
record.errors is memoized and the @errors instance variable gets cloned too.
file1.errors = new_file.errors
this will be non-nil since create
called validations on file1
.
now what happens when you clone file1 and say new_file.save!
?
Deep inside valid?
calls errors.clear on new_file but it still points to the same error object as file1.
Now viciously, the presence validator is implemented like this:
def validate(record)
record.errors.add_on_blank(attributes, options)
end
which (obviously) can only access errors.base http://apidock.com/rails/ActiveModel/Errors/add_on_blank
so, although, the validations do run on new_file as the record, the presence validation passes since
new_file.errors.instance_eval { @base } == file1
and for file1.folder_id
is NOT blank.
Now, your second test passes because if you read the file entry from the db, file2.errors
is nil so when you clone it and call validations on the clone, the errors object is created anew with the correct base (the clone) for which folder_id
will be blank because of the line new_file.folder = target_folder
.
your problem is solved by simply adding
def copy(target_folder)
new_file = self.clone
new_file.instance_eval { @errors = nil } # forces new error object on clone
new_file.folder = target_folder
new_file.save!
end
hope this helped
精彩评论