ruby-on-rails - formulario - validaciones rails
¿Cómo se puede validar la presencia de un miembro de la asociación con Rails? (6)
En lugar de validar la presencia de la identificación del artículo, se podría validar la presencia del artículo.
validates_presence_of :article
Entonces cuando estés creando tu comentario:
article.comments.build :article => article
Digamos que tengo una aplicación básica de Rails con una relación básica de uno a varios donde cada comentario pertenece a un artículo:
$ rails blog
$ cd blog
$ script/generate model article name:string
$ script/generate model comment article:belongs_to body:text
Ahora agrego el código para crear las asociaciones, pero también quiero asegurarme de que cuando creo un comentario, siempre tenga un artículo:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
validates_presence_of :article_id
end
Entonces ahora digamos que me gustaría crear un artículo con un comentario de una vez:
$ rake db:migrate
$ script/console
Si haces esto:
>> article = Article.new
=> #<Article id: nil, name: nil, created_at: nil, updated_at: nil>
>> article.comments.build
=> #<Comment id: nil, article_id: nil, body: nil, created_at: nil, updated_at: nil>
>> article.save!
Obtendrás este error:
ActiveRecord::RecordInvalid: Validation failed: Comments is invalid
Lo que tiene sentido, porque el comentario aún no tiene page_id.
>> article.comments.first.errors.on(:article_id)
=> "can''t be blank"
Entonces, si comment.rb
validates_presence_of :article_id
de comment.rb
, entonces podría guardar, pero eso también le permitiría crear comentarios sin un ID de artículo. ¿Cuál es la forma típica de manejar esto?
ACTUALIZACIÓN : Basado en la sugerencia de Nicholas, aquí hay una implementación de save_with_comments que funciona pero es fea:
def save_with_comments
save_with_comments!
rescue
false
end
def save_with_comments!
transaction do
comments = self.comments.dup
self.comments = []
save!
comments.each do |c|
c.article = self
c.save!
end
end
true
end
No estoy seguro de que quiera agregar algo como esto para cada asociación de uno a muchos. Probablemente Andy tenga razón, ya que es mejor evitar tratar de guardar en cascada y usar la solución de atributos anidados. Dejaré esto abierto por un tiempo para ver si alguien tiene alguna otra sugerencia.
Estás en lo correcto. El artículo necesita una identificación antes de que esta validación funcione. Una forma de evitar esto es guardar el artículo, así:
>> article = Article.new
=> #<Article id: nil, name: nil, created_at: nil, updated_at: nil>
>> article.save!
=> true
>> article.comments.build
=> #<Comment id: nil, article_id: 2, body: nil, created_at: nil, updated_at: nil>
>> article.save!
=> true
Si está creando un nuevo artículo con un comentario en un método o acción, le recomendaría crear el artículo y guardarlo, luego crear el comentario, pero envolver todo dentro de un bloque Article.transaction para que no finalice. con cualquier artículo extra.
Esto falla porque no hay un mapa de identidad en Rails 2.3 o 3.0. Se puede arreglar manualmente cosiéndolos juntos.
a = Article.new
c = a.comments.build
c.article = a
a.save!
Esto es horrible, y lo que el mapa de identidad en 3.1 ayudará a solucionar (además de las mejoras de rendimiento). c.article
y a.comments.first.article
son objetos diferentes sin un mapa de identidad.
Si está utilizando Rails 2.3, está utilizando el nuevo modelo de modelo anidado. He notado las mismas fallas con validates_presence_of
que usted señaló, o si :null => false
fue especificado en la migración para ese campo.
Si su intención es crear modelos anidados, debe agregar accepts_nested_attributes_for :comments
to article.rb
. Esto le permitiría:
a = Article.new
a.comments_attributes = [{:body => "test"}]
a.save! # creates a new Article and a new Comment with a body of "test"
Lo que tienes es la forma en que debería funcionar para mí, pero veo que no es así con Rails 2.3.2. before_create
un before_create
en un comentario usando su código y no se proporciona article_id
través de la compilación (es nula), por lo que esto no va a funcionar. Necesitaría guardar el artículo primero como lo señaló Nicholas, o eliminar la validación.
Solucioné este problema agregando esta línea de seguimiento a mi _comment.html.erb:
"NUEVO" si form.object.new_record? %>Ahora, la validación funciona en forma independiente, y también en forma múltiple.
También he estado investigando este tema y aquí está mi resumen:
La causa raíz por la que esto no funciona OOTB (al menos cuando se usa validates_presence_of :article
y no validates_presence_of :article_id
) es el hecho de que rails no usa un mapa de identidad internamente y, por lo tanto, no sabrá por sí mismo que article.comments[x].article == article
He encontrado tres soluciones para que funcione con un poco de esfuerzo:
- Guarde el artículo antes de crear los comentarios (los rieles pasarán automáticamente la identificación del artículo que se generó durante el guardado de cada comentario creado recientemente; vea la respuesta de Nicholas Hubbard)
- Establezca explícitamente el artículo en el comentario después de crearlo (vea la respuesta de W. Andrew Loe III)
- Utilice inverse_of:
class Article < ActiveRecord::Base has_many :comments, :inverse_of => :article end
Esta última solución fue un bot aún mencionado en este artículo, pero parece ser la solución de solución rápida de Rails por la falta de un mapa de identidad. También me parece la menos intrusiva de las tres.