2016-03-11 45 views
6

Pozwolę sobie zacząć od stwierdzenia, że ​​może to być również problem z modelowaniem i jestem otwarty na propozycje modeli.Szyny 4 Jak modelować formularz z kolekcją pól wyboru z innym polem tekstowym

Przypadek użycia: Mam formularz i muszę zezwolić użytkownikowi na zaznaczenie pola wyboru w kategorii jego wpisu. Jeśli nie ma kategorii, która pasuje do ich sprawdzania postów, druga kategoria pokaże pole tekstowe, aby użytkownik mógł dodać kategorię niestandardową. Powinno to byłoby do tworzenia i aktualizacji modułów zagnieżdżonych

DB Modeling

Models
class CreateCategories < ActiveRecord::Migration 
    def change 
    create_table :categories do |t| 
     t.string :name, null: false 
     t.timestamps null: false 
    end 

    reversible do |dir| 
     dir.up { 
     Category.create(name: 'Hats') 
     Category.create(name: 'Shirts') 
     Category.create(name: 'Pants') 
     Category.create(name: 'Shoes') 
     Category.create(name: 'Other') 
     } 
    end 

    create_table :categorizations, id: false do |t| 
     t.belongs_to :post, index: true, null: false 
     t.belongs_to :category, index: true, null: false 
     t.string :value 
    end 
    end 
end 

App

class Post < ActiveRecord::Base 
    has_many :categorizations 
    accepts_nested_attributes_for :categorizations, allow_destroy: true 
    has_many :categories, through: :categorizations 
    accepts_nested_attributes_for :categories 
end 

class Category < ActiveRecord::Base 
    has_many :posts 
end 

Kontroler:

def update 

    if @post.update(post_params) 
     flash.now[:success] = 'success' 
    else 
     flash.now[:alert] = @post.errors.full_messages.to_sentence 
    end 

    render :edit 
    end 

    private 

    def set_post 
    @post = Post.find(params[:id]) 
    (Category.all - @post.categories).each do |category| 
     @post.categorizations.build(category: category) 
    end 
    @post.categorizations.to_a.sort_by! {|x| x.category.id } 
    end 

    def post_params 
    params.require(:post).permit(:name, :description, 
           categorizations_attributes: [ :category_id, :value, :_destroy], 
           ) 
    end 

Widok:

= f.fields_for :categorizations do |ff| 
    = ff.check_box :_destroy, { checked: ff.object.persisted? }, '0', '1' 
    = ff.label :_destroy, ff.object.category.name 
    = ff.hidden_field :category_id 
    = ff.text_field :value if ff.object.category.other? 

Jednak przy powyższym rozwiązaniu kontynuuję uruchamianie, aby zduplikować błędy zapisu podczas zapisywania. Nie wiesz, dlaczego tak się dzieje? Czy jest lepszy sposób to zrobić?

Odpowiedz

2

Nie przechowuj drugiego w swoim modelu ani imienia! Jeśli używasz form_for dla swojego, po prostu dodaj niepowiązane pole.

ex: f.text_field :other_name do text_field_tag :other_name

ręcznie dodać opcję Other do kolekcji rozwijanej.

Możesz dodać JS, aby ukryć i wyświetlić ukryte pole tekstowe, jeśli wybrano inne.

W swojej posts_controller zrobić:

def create 
    ... 
    if params[:other_name] 
    post.categories.create(name: param[:other_name]) 
    end 
    ... 
end 
0

Zamiast użytkownikowi wybrać „Inne” kategorii, a następnie przechowywania pole tekstowe gdzieś indziej, należy utworzyć nową instancję Kategoria zamiast. Jesteś na dobrej drodze dzięki accepts_nested_attributes_for.

Następnym krokiem byłoby:

# app/controllers/posts_controller.rb 

def new 
    @post = Post.new 
    @post.build_category 
end 

private 
    # don't forget strong parameters! 
    def post_params 
    params.require(:post).permit(
     ... 
     category_attributes: [:name] 
     ... 
    ) 
    end 

widoki (wykorzystujące simple_form i nested_form perełki)

# app/views/new.html.haml 
= f.simple_nested_form_for @job do |f| 
    = f.simple_fields_for :category do |g| 
    = g.input :name 

Można również zrobić to czystsze formularz Przedmioty zamiast.

Edit: Jeśli trzeba oddzielić problemy z pozostałych kategorii z oryginalnego kategoriach, można użyć OO dziedziczenia, aby to zrobić. Sposób Railsów to: Single Table Inheritance.

# app/models/category.rb 
class Category < ActiveRecord::Base 
end 

# app/models/other_category.rb 
class OtherCategory < Category 
end 

# app/models/original_category.rb 
class OriginalCategory < Category 
end 
+0

Dzięki temu rozwiązaniu musiałbym śledzić typ kategorii w modelu, prawda? Wstawiłoby to również wiele kategorii, których nigdy bym nie pokazał. – Matt

+0

Nie jestem w 100% pewien, co masz na myśli, jeśli chcesz śledzić typ kategorii. Jeśli chcesz, aby każda z kategorii "Inne" była traktowana inaczej niż "oryginalne" kategorie, możesz je podklasować (dziedziczenie pojedynczej tabeli). Ale tak, utworzyłoby to wiele kategorii, które nie byłyby wyświetlane. Nie jestem pewien, czy to jest prawdziwy problem. –

4

wolałbym coś takiego:

modele

post.rb

class Post < ActiveRecord::Base 
    has_many :categorizations 
    has_many :categories, through: :categorizations 

    accepts_nested_attributes_for :categorizations, allow_destroy: true 
    accepts_nested_attributes_for :categories 
end 

category.rb

class Category < ActiveRecord::Base 
    has_many :categorizations 
    has_many :posts, through: :categorizations 
end 

Controller

... 
def update 
    if @post.update(post_params) 
    flash.now[:success] = 'success' 
    else 
    flash.now[:alert] = @post.errors.full_messages.to_sentence 
    end 
    render :edit 
end 

private 

def set_post 
    @post = Post.find(params[:id]) 
end 

def post_params 
    params.require(:post).permit(:name, :description, category_ids: []) 
end 
... 

Odwiedzin zawsze preferer zwykły .erb tak, z pomocą simple_form.

<%= simple_form_for(@post) do |f| %> 
    <%= f.error_notification %> 

    <div class="form-inputs"> 
    <%= f.input :content -%> 
    ... 
    </div> 

    <div class="form-inputs"> 
    <%= f.association :categories, as: :check_boxes -%> 
    </div> 

    <div class="form-actions"> 
    <%= f.button :submit %> 
    </div> 
<% end %> 

Możesz mieć zaznaczone/niezaznaczone stany i łatwo i niszcząc w ten sposób. Ponadto, można dodać

<%= f.simple_fields_for :category do |category_fields| %> 
    <%= category_fields.input :name -%> 
<% end %> 

dostać zagnieżdżone pola dla stowarzyszeń, ale nie zapomnij dodać pokrewnych params do strong_params kiedy to zrobić.

... 
def post_params 
    params.require(:post).permit(:name, :description, category_attributes: [:name]) 
end 
.... 
+0

Co z polem formularza dla kategoryzacji? Gdy wybierzesz inną kategorię, powinno pojawić się pole formularza, aby wpisać inną kategorię. – Matt

+0

W moim przykładzie nie trzeba ręcznie określać "kategoryzacji". Używając opcji 'accepts_nested_attributes_for' w' Post' i wyświetlając wszystkie pola 'category' jako pola wyboru, wybrane kategorie będą serializowane jako' category_ids', które będą wywoływane w metodzie Post_the_category_ids =, więc stowarzyszenie 'kategorie' będzie overriden z tym ostatnim stanem, stowarzyszenie "kategoryzacji" będzie obsługiwane w sposób automatyczny w tym scenerio. Czy to było twoje pytanie? A może chcesz bezpośrednio edytować "kategoryzację"? –

+0

Kategoria "Inne" wyróżnia się tym, że istnieje opcjonalne pole tekstowe, które pozwala użytkownikowi dodać szczegóły do ​​kategorii "Inne". Tak więc informacje są przechowywane w kategoryzacji. Więc post ma kategoryzację, która jest powiązana z kategorią o nazwie inne, dlatego też kategoryzacja zawiera także inne wartości. – Matt

Powiązane problemy