2016-03-11 22 views
6

lasciatemi iniziare dicendo che questo potrebbe anche essere un problema di modellazione e sono aperto a suggerimenti di modelli.Rails 4 Come modellare un modulo con una raccolta di checkboxes con altro campo di testo

Caso di utilizzo: Ho un modulo e devo consentire a un utente di selezionare una casella di controllo sulla categoria del suo post. Se non ci sono categorie che si adattano al loro post, l'altra categoria mostrerà un campo di testo per l'utente per aggiungere una categoria personalizzata. Questo dovrebbe farebbe per la creazione e l'aggiornamento di moduli annidati

DB Modeling

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 modelle

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 

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]) 
    (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 

Vista:

= 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? 

Tuttavia con la soluzione di cui sopra continuo a correre per duplicare errori di registrazione durante il salvataggio. Non sono sicuro del perchè ciò stia succedendo? C'è un modo migliore per farlo?

risposta

2

Non memorizzare l'altro nel modello, né il nome! Se stai utilizzando per il tuo posts, aggiungi semplicemente un campo non correlato.

es: f.text_field :other_name-text_field_tag :other_name

aggiungere manualmente l'opzione Other alla raccolta discesa.

È possibile aggiungere JS per nascondere e visualizzare un campo di testo nascosto se altro è selezionato.

Nel vostro posts_controller fare:

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

Invece di dover all'utente di selezionare la categoria "altro" e quindi memorizzare il campo di testo da qualche altra parte, è necessario creare una nuova istanza di categoria, invece. Sei sulla strada giusta con lo accepts_nested_attributes_for.

Il passo successivo sarebbe:

# 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 

Vista (utilizzando simple_form e nested_form gemme)

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

È anche possibile fare più pulita tramite il modulo oggetti invece.

Edit: Se è necessario separare le preoccupazioni delle altre categorie dalle categorie originali, è possibile utilizzare l'ereditarietà OO per farlo. Il modo Rails di fare ciò è 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

Con questa soluzione dovrei tenere traccia del tipo di categoria nel modello, corretto? Inseriresti anche molte categorie che non mostrerei mai. – Matt

+0

Non sono sicuro al 100% di cosa intenda dover tenere traccia del tipo di categoria. Se desideri che ciascuna delle categorie "Altro" sia trattata in modo diverso rispetto alle categorie "originali", puoi creare una sottoclasse (ereditarietà di una tabella singola). Ma sì, creerebbe molte categorie che non mostrereste. Non sono sicuro se questo è un vero problema però. –

4

io preferirei qualcosa di simile:

modelle

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 
... 

Visualizzazioni ho sempre preferer pianura .erb così, con l'aiuto di 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 %> 

È possibile controllare/deselezionare gli stati e distruggere facilmente e in modo pulito in questo modo. Inoltre, è possibile aggiungere

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

prelevare i campi nidificati per le associazioni, ma non dimenticare di aggiungere params relativi a strong_params quando si esegue questa operazione.

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

E il campo modulo per la categorizzazione? Quindi, quando selezioni l'altra categoria, dovrebbe apparire un campo modulo per poter digitare in un'altra categoria. – Matt

+0

Nel mio esempio, non è necessario specificare 'categorizzazione 'manualmente. Usando 'accept_nested_attributes_for' in' Post' e rendendo ogni 'category' come una casella di controllo, le categorie selezionate verranno serializzate come' category_ids', che verranno chiamate nel metodo 'Post # category_ids =', quindi l'associazione 'categories' sarà overriden con quest'ultimo stato, l'associazione 'categorizzazioni' sarà gestita automagicamente, in questo scenerio. Era questa la tua domanda? O vuoi modificare la categorizzazione direttamente? –

+0

La categoria "Altro" è speciale in quanto esiste un campo di testo opzionale che consente all'utente di aggiungere dettagli alla categoria "Altro". In modo che le informazioni siano memorizzate nella categorizzazione. Quindi un post ha una categorizzazione che è associata alla categoria con il nome altro quindi la categorizzazione tiene anche il valore altri dettagli. – Matt

Problemi correlati