开发者

Validation ignored when cloning a newly created record

开发者 https://www.devze.com 2023-03-08 13:12 出处:网络
I have a model UserFile that belongs_to a Folder: class UserFile < ActiveRecord::Base has_attached_file :attachment

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

0

精彩评论

暂无评论...
验证码 换一张
取 消