2012-06-08 10 views
26

Me gustaría destruir un modelo anidado si sus atributos están en blanco en el formulario para el modelo principal; sin embargo, parece que no se llama al ActiveRecord::Callbacks si el modelo está en blanco.Destruir en el atributo anidado en blanco

class Artist < ActiveRecord::Base 
    using_access_control 
    attr_accessible :bio, :name, :tour_dates_attributes 
    has_many :tour_dates, :dependent => :destroy 
    accepts_nested_attributes_for :tour_dates, :reject_if => lambda { |a| a[:when].blank? || a[:where].blank? }, :allow_destroy => true 
    validates :bio, :name :presence => true 

    def to_param 
    name 
    end 
end 

y

class TourDate < ActiveRecord::Base 
    validates :address, :when, :where, :artist_id, :presence => true 
    attr_accessible :address, :artist_id, :when, :where 
    belongs_to :artist 
    before_save :destroy_if_blank 

    private 
    def destroy_if_blank 
    logger.info "destroy_if_blank called" 
    end 
end 

Tengo una forma de artista que utiliza fields_for para mostrar los campos de fechas asociadas del artista, que trabaja para la edición y la adición de nuevas fechas, pero si simplemente en blanco fuera de una fecha de gira (para eliminarlo), nunca se llama al destroy_if_blank. Es de suponer que la línea @artist.update_attributes(params[:artist]) del controlador Artist no considera una entidad en blanco que valga la pena actualizar.

¿Echo de menos algo? ¿Hay alguna forma de evitar esto?

Respuesta

5

Tiene un código que dice que el registro debe ignorarse si el 'dónde' o el 'cuándo' está en blanco, en la línea de los accept_nested _attributes, elimine el reject_if y se llamará a su destroy_if en blanco.

Normalmente no destruir, se debe establecer un atributo _destroy en el registro anidada, echa un vistazo a los documentos http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Además, sólo se utilizan capullo para algunos de esto hoy en día, y pensé que era impresionante, https://github.com/nathanvda/cocoon

1

Con su código actual no es posible, debido a la opción reject_if pasada al accepts_nested_attributes_for.

Como dijo Christ Mohr, la forma más fácil es establecer el atributo _destroy para el modelo anidado al actualizar el elemento primario, y se destruirá el modelo anidado. Consulte los documentos para obtener más información sobre esto, o this railscast.

O puede usar una gema como cocoon o awesome_nested_fields.

Para hacer específicamente lo que desee, debe eliminar la opción reject_if y manejar la lógica en una devolución de llamada dentro del objeto primario. Debería verificar los valores en blanco en tour_dates_attributes y destruir el modelo anidado. Pero pise con cuidado ...

5

He logrado hacer algo como esto hoy. Al igual que @shuriu, su mejor opción es eliminar la opción reject_if y manejar la destrucción usted mismo. mark_for_destruction viene muy bien:

class Artist < ActiveRecord::Base 
    accepts_nested_attributes_for :tour_dates 

    before_validation :mark_tour_dates_for_destruction 

    def mark_tour_dates_for_destruction 
    tour_dates.each do |tour_date| 
     if tour_date.when.blank? or tour_date.where.blank? 
     tour_date.mark_for_destruction 
     end 
    end 
    end 
end 
+0

¿Por qué 'tour_date.when.blank?' ¿dos veces? gracias –

+0

@maxkaplan: Debería haber sido 'cuándo' y' dónde'. Lo arreglé en la respuesta. ¡Gracias! – Sunny

61

Me gustaría mantener el: reject_if bloque, pero inserte: _destroy => 1 en los atributos el hash si se cumplen sus condiciones. (Esto es útil en los casos donde no es conveniente agregar _destroy al código del formulario.)

Tiene que hacer una comprobación adicional para ver si el registro existe para devolver el valor correcto, pero parece que funciona lo siguiente en todos los casos para mí.

accepts_nested_attributes_for :tour_dates, :reject_if => :reject_tour, :allow_destroy => true 

def reject_tour(attributes) 
    exists = attributes['id'].present? 
    empty = attributes.slice(:when, :where).values.all?(&:blank?) 
    attributes.merge!({:_destroy => 1}) if exists and empty # destroy empty tour 
    return (!exists and empty) # reject empty attributes 
end 

Se podría aplicar cuando todos los atributos están en blanco con sólo cambiar el cálculo empty a:

empty = attributes.except(:id).values.all?(&:blank?) 
+0

esta es una solución brillante; sin embargo, debe asegurarse de que la validación esté desactivada para el modelo asociado o de que falle en los registros nuevos. – JoshL

+3

No debería fallar en los registros nuevos, ya que el nuevo registro no debería tener un atributo 'id', y el registro anidado se rechazaría y no se cargaría. ¡Esta solución es genial! Lo modifiqué ligeramente haciendo 'empty = attributes.reject {| k, v | k == 'id'}. values.all? (&: blank?) 'para verificar todos los atributos vacíos. – DGM

+2

Te amo tanto ahora –

2

Similar a la respuesta de Steve Kenworthy, no hay variables locales.

accepts_nested_attributes_for :tour_dates, :reject_if => :reject_tour, :allow_destroy => true 

def reject_tour(attributes) 
    if attributes[:when].blank? || attributes[:where].blank? 
     if attributes[:id].present? 
     attributes.merge!({:_destroy => 1}) && false 
     else 
     true 
     end 
    end 
    end 
+0

en palabras de Borat "es una muy buena" –