Esto funciona muy bien si tiene una relación has_one o belongs_to. Pero se quedó corto con un has_many o has_many through.
Tengo un sistema de etiquetado que utiliza una relación has_many: through. Ninguna de las soluciones aquí me llevó a donde tenía que ir, así que se me ocurrió una solución que puede ayudar a otros. Esto ha sido probado en Rails 3.2.
Configuración
Éstos son una versión básica de mis modelos:
Ubicación del objeto:
class Location < ActiveRecord::Base
has_many :city_taggables, :as => :city_taggable, :dependent => :destroy
has_many :city_tags, :through => :city_taggables
accepts_nested_attributes_for :city_tags, :reject_if => :all_blank, allow_destroy: true
end
Tag objetos
class CityTaggable < ActiveRecord::Base
belongs_to :city_tag
belongs_to :city_taggable, :polymorphic => true
end
class CityTag < ActiveRecord::Base
has_many :city_taggables, :dependent => :destroy
has_many :ads, :through => :city_taggables
end
Solución
I , efectivamente reemplazar el método autosave_associated_recored_for de la siguiente manera:
class Location < ActiveRecord::Base
private
def autosave_associated_records_for_city_tags
tags =[]
#For Each Tag
city_tags.each do |tag|
#Destroy Tag if set to _destroy
if tag._destroy
#remove tag from object don't destroy the tag
self.city_tags.delete(tag)
next
end
#Check if the tag we are saving is new (no ID passed)
if tag.new_record?
#Find existing tag or use new tag if not found
tag = CityTag.find_by_label(tag.label) || CityTag.create(label: tag.label)
else
#If tag being saved has an ID then it exists we want to see if the label has changed
#We find the record and compare explicitly, this saves us when we are removing tags.
existing = CityTag.find_by_id(tag.id)
if existing
#Tag labels are different so we want to find or create a new tag (rather than updating the exiting tag label)
if tag.label != existing.label
self.city_tags.delete(tag)
tag = CityTag.find_by_label(tag.label) || CityTag.create(label: tag.label)
end
else
#Looks like we are removing the tag and need to delete it from this object
self.city_tags.delete(tag)
next
end
end
tags << tag
end
#Iterate through tags and add to my Location unless they are already associated.
tags.each do |tag|
unless tag.in? self.city_tags
self.city_tags << tag
end
end
end
La implementación anterior guarda, borra y cambios toca al camino que necesitaba cuando se utiliza fields_for en una forma anidada. Estoy abierto a comentarios si hay formas de simplificar. Es importante señalar que estoy cambiando explícitamente las etiquetas cuando la etiqueta cambia en lugar de actualizar la etiqueta.
¿Puede usted por favor señalar esta funcionalidad en la documentación? – dombesz
También creo que el correcto es def autosave_associated_records_for_author. – dombesz
¿Este método funciona del otro lado de la relación? por ejemplo, ¿qué pasa si tenemos has_many: authors? – dombesz