2011-05-25 24 views
5

tengo un modelo UserFile que belongs_to un Folder:Validación ignorado cuando se clona un registro recién creado

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 

La siguiente prueba falla inesperada:

test 'cannot copy a file to anything other than a folder' do 
    folder = Factory(:folder) 
    file1 = UserFile.create(:attachment => File.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 

El validates_presence_of :folder_id se ignora cuando se utiliza un recién creado objeto, pero cuando hago un ActiveRecord#find FUNCIONA. Creo que tiene algo que ver con llamar al clone en el método copy, pero no puedo resolverlo. ¿Alguien sabe lo que está pasando o cómo hacer que pase la prueba?

+0

'file1' es una instancia de userfile pero para' file2' está utilizando 'UserFile.find (archivo1)', i pensar es 'UserFile.find (file1.id)' – JCorcuera

+1

No importa. Puede usar 'UserFile.find (file1.id)' o 'UserFile.find (file1)'. Es lo mismo. – Mischa

+0

¿Está planteando una excepción por el motivo que usted cree que es? Tal vez, su validación no funciona en ninguno de los casos, pero una validación diferente está fallando en el segundo guardado, ¿quizás debido a un valor duplicado en una columna única? –

Respuesta

3

Mischa, clonación es una bestia.

record.errors se memoriza y la variable de instancia @errors se clona también.

file1.errors = new_file.errors 

esto va a ser no nulo puesto que create llamados validaciones en file1.

ahora, ¿qué ocurre cuando clonas el archivo 1 y dices new_file.save!? En el fondo valid? llama a errors.clear en new_file pero aún apunta al mismo objeto de error que file1. Ahora brutalmente, el validador presencia se implementa como esto:

def validate(record) 
    record.errors.add_on_blank(attributes, options) 
end 

la que (obviamente) sólo puede acceder errors.base http://apidock.com/rails/ActiveModel/Errors/add_on_blank

que sí, aunque, las validaciones se ejecutan en new_file como el registro, la validación presencia pasa desde

new_file.errors.instance_eval { @base } == file1 

y para file1.folder_id NO es en blanco.

Ahora, su segunda prueba se pasa porque si lee la entrada del archivo de la base de datos, file2.errors es nula, entonces cuando la clona y llama a validaciones en el clon, el objeto de errores se crea nuevamente con la base correcta (el clon) para el cual folder_id estará en blanco debido a la línea new_file.folder = target_folder.

su problema se resuelve mediante la simple adición

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 

esperanza que esto ayudó

+0

¡Gracias! Lo intentaré si tu solución funciona.Si lo hace, intentaré entender el razonamiento detrás de él ;-) – Mischa

+0

Awesome !! Muchas gracias. Esto me estaba volviendo loco. Estoy leyendo sobre la memorización e intentando entender completamente tu explicación. – Mischa

+1

memorización es solo este bit: 'def errors @errors || = Errors.new (self) end', lo llamativo es que las validaciones toman el registro de la base de errores, que es idiota y no transparente, dado que dado las validaciones tienen un registro pasado a ellos. pero los desarrolladores del núcleo de los rieles podrían decir: clonar a su propio riesgo :) –

Cuestiones relacionadas