14

Próbuję rozwiązać dość powszechne (jak myślałem) zadanie.Szyny has_many w formularzu z polami wyboru i dodatkowym polem w modelu łączenia

Są tam trzy modele:

class Product < ActiveRecord::Base 
    validates :name, presence: true 

    has_many :categorizations 
    has_many :categories, :through => :categorizations 

    accepts_nested_attributes_for :categorizations 
end 

class Categorization < ActiveRecord::Base 
    belongs_to :product 
    belongs_to :category 

    validates :description, presence: true # note the additional field here 
end 

class Category < ActiveRecord::Base 
    validates :name, presence: true 
end 

Moje problemy zaczynają się, gdy chodzi o nową formę produktu/edycji.

Podczas tworzenia produktu muszę sprawdzić kategorie (za pomocą pól wyboru), do których należy. Wiem, że można tego dokonać, tworząc pola wyboru o takiej nazwie, jak "product [category_ids] []". Ale muszę również podać opis dla każdej sprawdzonej relacji, która będzie przechowywana w modelu łączenia (kategoryzacja).

Widziałem te piękne Railscasty na złożonych formularzach, polach przyzwyczajeń itp. Przeszukując StackOverflow prawie nie. Ale mi się nie udało.

Znalazłem jeden post, który opisuje prawie dokładnie ten sam problem, co mój. Ostatnia odpowiedź ma dla mnie sens (wygląda na to, że jest to właściwa droga). Ale tak naprawdę nie działa dobrze (tj. Jeśli sprawdzanie poprawności nie powiedzie się). Chcę, aby kategorie były wyświetlane zawsze w tej samej kolejności (w nowych/edytowanych formularzach, przed/po sprawdzaniu poprawności) i pola wyboru pozostawały tam, gdzie były, jeśli walidacja się nie powiedzie, itp.

Dowolny doceniany. Jestem nowy w Railsach (przełączanie z CakePHP), więc proszę o cierpliwość i napisz jak najdokładniej. Wskaż mnie we właściwy sposób!

Dziękuję. :)

Odpowiedz

26

Wygląda na to, że zorientowaliśmy się! Oto co mam:

Moje modele:

class Product < ActiveRecord::Base 
    has_many :categorizations, dependent: :destroy 
    has_many :categories, through: :categorizations 

    accepts_nested_attributes_for :categorizations, allow_destroy: true 

    validates :name, presence: true 

    def initialized_categorizations # this is the key method 
    [].tap do |o| 
     Category.all.each do |category| 
     if c = categorizations.find { |c| c.category_id == category.id } 
      o << c.tap { |c| c.enable ||= true } 
     else 
      o << Categorization.new(category: category) 
     end 
     end 
    end 
    end 

end 

class Category < ActiveRecord::Base 
    has_many :categorizations, dependent: :destroy 
    has_many :products, through: :categorizations 

    validates :name, presence: true 
end 

class Categorization < ActiveRecord::Base 
    belongs_to :product 
    belongs_to :category 

    validates :description, presence: true 

    attr_accessor :enable # nice little thingy here 
end 

postaci:

<%= form_for(@product) do |f| %> 
    ... 
    <div class="field"> 
    <%= f.label :name %><br /> 
    <%= f.text_field :name %> 
    </div> 

    <%= f.fields_for :categorizations, @product.initialized_categorizations do |builder| %> 
    <% category = builder.object.category %> 
    <%= builder.hidden_field :category_id %> 

    <div class="field"> 
     <%= builder.label :enable, category.name %> 
     <%= builder.check_box :enable %> 
    </div> 

    <div class="field"> 
     <%= builder.label :description %><br /> 
     <%= builder.text_field :description %> 
    </div> 
    <% end %> 

    <div class="actions"> 
    <%= f.submit %> 
    </div> 
<% end %> 

i kontroler:

class ProductsController < ApplicationController 

    before_filter :process_categorizations_attrs, only: [:create, :update] 

    def process_categorizations_attrs 
    params[:product][:categorizations_attributes].values.each do |cat_attr| 
     cat_attr[:_destroy] = true if cat_attr[:enable] != '1' 
    end 
    end 

    ... 

    # all the rest is a standard scaffolded code 

end 

Z pierwszy rzut oka działa dobrze. Mam nadzieję, że to się nie złamie .. :)

Dzięki wszystkim. Specjalne podziękowania dla Sandipa Ransinga za udział w dyskusji. Mam nadzieję, że będzie to przydatne dla kogoś takiego jak ja.

+1

ładnie wykonane. Czuję, że może być łatwiejszy sposób. – courtsimas

+0

Dziękuję bardzo za dzielenie się, musiałem również uzupełnić http://stackoverflow.com/a/15920542/148421, ponieważ moje wartości, gdzie nie są zapisywane i brakowało mi sposobu zezwalania na zagnieżdżone atrybuty – Andrea

1

użycie accepts_nested_attributes_for wstawić do intermediate table tj categorizations Wygląd postaci będzie wyglądać -

# make sure to build product categorizations at controller level if not already 
class ProductsController < ApplicationController 
    before_filter :build_product, :only => [:new] 
    before_filter :load_product, :only => [:edit] 
    before_filter :build_or_load_categorization, :only => [:new, :edit] 

    def create 
    @product.attributes = params[:product] 
    if @product.save 
     flash[:success] = I18n.t('product.create.success') 
     redirect_to :action => :index 
    else 
     render_with_categorization(:new) 
    end 
    end 

    def update 
    @product.attributes = params[:product] 
    if @product.save 
     flash[:success] = I18n.t('product.update.success') 
     redirect_to :action => :index 
    else 
     render_with_categorization(:edit) 
    end 
    end 

    private 
    def build_product 
    @product = Product.new 
    end 

    def load_product 
    @product = Product.find_by_id(params[:id]) 
    @product || invalid_url 
    end 

    def build_or_load_categorization 
    Category.where('id not in (?)', @product.categories).each do |c| 
     @product.categorizations.new(:category => c) 
    end 
    end 

    def render_with_categorization(template) 
    build_or_load_categorization 
    render :action => template 
    end 
end 

Wnętrze

= form_for @product do |f| 
    = f.fields_for :categorizations do |c| 
    %label= c.object.category.name 
    = c.check_box :category_id, {}, c.object.category_id, nil 
    %label Description 
    = c.text_field :description 
+0

Dziękuję! Wygląda jednak na to, że twoje rozwiązanie nie działa dobrze w przypadku sprawdzania poprawności i innych rzeczy. Pola wyboru są zawsze sprawdzane, a nawet jeśli je odznaczę, zostaną one zignorowane (wrócą po nieudanej walidacji lub jeśli pomyślnie przejdą poprawne relacje nie zostaną usunięte). Chcę również wyświetlać kategorie zawsze w tej samej kolejności (w twoim przykładzie dodajesz nieprzydzielone kategorie po tych, które są już powiązane z produktem). Jakieś pomysły? – ok32

+0

możesz zrobić zamawianie w kolekcji '@ product.categoryizations' –

+0

czy możesz wklejać paramery przed przekazaniem? –

1

Właśnie wykonałem następujące czynności. To zadziałało dla mnie ..

<%= f.label :category, "Category" %> 
<%= f.select :category_ids, Category.order('name ASC').all.collect {|c| [c.name, c.id]}, {} %> 
+0

To nie pozwala na dodatkowe dołączenie pole modelu, o które prosił – dft

Powiązane problemy