2010-11-06 9 views
8

Supongamos que cada Projecthas_manyTasks.¿Guardar cambios SOLAMENTE a una asociación has_many cuando guarda con éxito el objeto padre?

Si hago

some_project.tasks = list_of_tasks 
some_project.save 

tareas del proyecto se actualizan incluso si la salvación falla. Si list_of_tasks consta de nuevos registros, las tareas del proyecto obtienen borrado ¡incluso si el guardado falla! ¡WHOA!

Si falla la operación de guardar, el proyecto debería tener las mismas tareas que tenía antes de empezar a jugar con él. ¿Cómo obtengo este comportamiento y por qué no es el predeterminado?

Respuesta

8

encierran las sentencias en una transacción:

Project.transaction do 
    p.tasks = task_list 
    p.save! 
end 

El método save! se produce una excepción en caso de error, que retrotrae los cambios hechos a la lista de tareas.

Puede leer documentation si desea profundizar un poco más sobre el tema.

+0

Esto funciona, pero este enfoque significa que cada vez que actualizo tareas en toda mi aplicación tendré que acordarme de envolverla en una transacción (y estoy seguro de que la olvidaré). Sería mucho mejor si pudiera encapsular este comportamiento en el modelo 'Proyecto' de forma SECA. –

+4

Bueno, defina un método 'replace_tasks (new_tasks)' que hace exactamente eso – glebm

0

Antes de llamar al #save puede preguntar si some_project#valid?. Eso ayuda a solucionar el problema si el guardado falla debido a que some_project es un registro no válido, pero no es una solución completa.

En cuanto al comportamiento predeterminado de los Rails, tiene sentido. Diciendo some_project.tasks = list_of_tasks es como decir "eliminar todas las tareas existentes de some_project y asignar estas nuevas". Está abandonando la referencia a la asociación existente Array y asignando una nueva. Esto se refleja en ActiveRecord en la base de datos.

+1

Esto NO FUNCIONA. 'some_project.tasks = list_of_tasks' agota la lista de tareas existente tan pronto como se invoca, independientemente de cuándo o si invoco' some_project.save'. Realmente no puedo creer que este sea el comportamiento predeterminado, ¡es realmente contra-intuitivo! –

+1

"Decir' some_project.tasks = list_of_tasks' es como decir "eliminar todas las tareas existentes de some_project y asignar estas nuevas" ". Creo que es más como decir "eliminar todas las tareas existentes de' some_project' y asignar estas nuevas * cuando guardo 'some_project' *.' Some_project.name = "whatever" 'no surte efecto hasta que guarde, ni tampoco' some_project.tasks = list_of_tasks' –

+0

@HoraceLoeb: bestia diferente, reglas diferentes. IIRC, la mayoría de los ORM siguen este razonamiento porque es mucho más fácil de implementar que el comportamiento esperado (créeme, no eres el primero en proponer esto) . Realmente no puedo nombrar una fuente, lo siento. – pkoch

3

creo que accepts_nested_attributes_for() proporcionará el comportamiento que desea:

class Project < ActiveRecord::Base 
    accepts_nested_attributes_for :tasks 
end 

Esto debe envolver todo dentro de una transacción. Luego debe compilar el formulario que completa las tareas en consecuencia. Se llama al método tasks_attributes en su modelo de proyecto en lugar del método tasks. Ver the API para más información.

3

Puede encontrar que la característica AutosaveAssociation hace lo que quiere.

class Project < ActiveRecord::Base 
    has_many :tasks, :autosave => true 
end 

Esto debería incluir el guardado en una transacción de forma automática.

Cuestiones relacionadas