Update'owanie nested_attributes

mam model Poster, wyglada tak:

class Poster < ActiveRecord::Base
  belongs_to :user
  belongs_to :category
  has_many :text_fields
  has_many :string_fields
  has_many :integer_fields
  has_many :float_fields
  has_many :date_fields
    
  accepts_nested_attributes_for :float_fields
  accepts_nested_attributes_for :integer_fields
  accepts_nested_attributes_for :text_fields
  accepts_nested_attributes_for :string_fields
  accepts_nested_attributes_for :date_fields

   def list_fields
     fields = []
     self.text_fields.each { |field| fields << field }
     self.string_fields.each { |field| fields << field }
     self.integer_fields.each { |field| fields << field }
     self.date_fields.each { |field| fields << field }
     self.float_fields.each { |field| fields << field }
     fields
  end  
end

dla tego zasobu, na podstawie kategorii tworze konkretne pola(float_fields, integer_fields itd).

class PosterCreator
  
  attr_accessor :poster
  
  def initialize(poster)
    @poster = poster
    build_fields_based_on_category
  end
  
  private
  
  def build_fields_based_on_category
    @poster.category.fields.each do |field|
      unless list_all_field_ids.include?(field.id)
        @custom_field = @poster.send("#{field.kind}_fields").build
        @custom_field.source = field
        #### source to metoda odwolujaca sie do field_id, kazdy field ma przypisane swoje zrodlo
      end
    end    
  end 
  
  def list_all_field_ids
    @poster.list_fields.map do |field|
      field.field_id
    end  
  end
end

Za pomoca tej klasy builduje fieldy. kazda kategoria ma field. field ma opis i rodzaj.
Tutaj jest kontroler:

 def edit
    PosterCreator.new(@poster)
  end  
  
  def update

    respond_to do |format|
      if @poster.update_attributes(poster_params)
        format.html { redirect_to @poster }
      else
        format.html { render action: "new" }
      end 
    end 
  end  

def poster_params
    params.require(:poster).permit(:content, :title, :category_id, string_fields_attributes: [:detail, :field_id], float_fields_attributes: [:float_number, :field_id], integer_fields_attributes: [:integer_number, :field_id], text_fields_attributes: [:description, :field_id], date_fields_attributes: [:date, :field_id])
  end  

Ale o co chodzi… chodzi o to, ze w efekcie zagniezdzone modele (czyli FloatFields, IntegerFields…) nie są przy updateowaniu faktycznie uaktualniane tylko dublowane, Stare modele zostają niezmienione, a tworzone są nowe (takie o ktore chodzi) i dodają się do modelu. w rezultacie po kazdej akcji update kazdego zagniezdzonego modelu mam dwa razy wiecej niz poprzednio. ustaliłem że całe to zło się dzieje przez metodę “update_attributes()” ale zupełnie nie wiem dlaczego i zupełnie nie wiem jak to naprawić. HaLP plX!

Jeszcze mogę dorzucić fomularz, tak jako bonus:

= simple_form_for @poster do |f|
	= f.input :category_id, collection: Category.all, prompt: "Choose category"
	= f.input :title
	= f.input :content
	= f.simple_fields_for :string_fields do |fsf|
		= fsf.input :detail
		= fsf.input :field_id, as: :hidden
	= f.simple_fields_for :text_fields do |ftf|
		= ftf.input :description
		= ftf.input :field_id, as: :hidden
	= f.simple_fields_for :integer_fields do |fif|
		= fif.input :integer_number	
		= fif.input :field_id, as: :hidden
	= f.simple_fields_for :float_fields do |fff|
		= fff.input :float_number
		= fff.input :field_id, as: :hidden
	= f.simple_fields_for	:date_fields do |fdf|
		= fdf.input :date
		= fdf.input :field_id, as: :hidden
	= f.submit

na pałę dodałem do strong params dla kazdego zagniezdzonego hasha :id, bo zauwazyłem że a kazdym razem sa przekazywane w paramsach i dziala. ciągle jednak tak naprawdę nie wiem dlaczego:(
teraz wygląda to tak:
params.require(:poster).permit(:content, :title, :category_id, string_fields_attributes: [:detail, :field_id, :id])

Jeśli id jest nil to ActiveRecord tworzy nowy rekord. Jeśli id jest ustawione to aktualizuje istniejący rekord (o podanym id). Ty akurat przekazywałeś id w formularzu (bo korzystasz z simple form) ale usuwałeś id z parametrów po stronie kontrolera linijką:

params.require(:poster).permit(:content, :title, :category_id, string_fields_attributes: [:detail, :field_id])

W momencie dodania id do zagnieżdżonych parametrów, AR wie który rekord ma zaaktualizować, a bez tego tworzy nowy.

offtopowo maly refaktor

def list_fields
 fields = []
 self.text_fields.each { |field| fields << field }
 self.string_fields.each { |field| fields << field }
 self.integer_fields.each { |field| fields << field }
 self.date_fields.each { |field| fields << field }
 self.float_fields.each { |field| fields << field }
 fields
end

def list_fields
 self.text_fields + 
 self.string_fields +
 self.integer_fields +
 self.date_fields +
 self.float_fields
end


def list_all_field_ids
  @poster.list_fields.map do |field|
    field.field_id
  end  
end

def list_all_field_ids
  @poster.list_fields.map(&:field_id)
end

@marr dzięki:P
w metodzie list_fields uzycie takiej składni działa jak arytmetyka, po prostu dodaje do siebie te rekordy i wio?

powinno dzialac :smile:
zakladajac, ze a, b, c, d, e to objekty type Array aby uworzyc jedna wielka tablice z elementami z powyzszych pieciu tablic mozna przykladowo

all_elements = a + b + c + d + e
all_elements = ([] << a << b << c << d << e).flatten
all_elements = [a, b, c, d, e].reduce(&:+)