let me first start by saying this could also be a modeling problem and I am open to model suggestions.
Use Case: I have a form and I need to allow a user to select a checkbox on the category of their post. If there is no category that fits their post checking the other category will show a text field for the user to add a custom category. This should would for creating and updating nested modules
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 Models
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
View:
= 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?
However with the above solution i continue to run in to duplicate record errors when saving. Not sure why this is happening? Is there a better way to do this?
I would prefer something like this:
Models
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
...
Views
I always preferer plain .erb
so,
with help of 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 %>
You can have checked/unchecked states and destroy easily and cleanly by this way. In addition, you can add
<%= f.simple_fields_for :category do |category_fields| %>
<%= category_fields.input :name -%>
<% end %>
to get nested fields for associations, but don't forget to add related params to strong_params
when you do this.
...
def post_params
params.require(:post).permit(:name, :description, category_attributes: [:name])
end
....
Don't store the other in your model, nor it's name! If you're using form_for
for your posts
, simply add an unrelated field.
ex: f.text_field :other_name
to text_field_tag :other_name
Manually add your Other
option to the dropdown collection.
You can add JS to hide and display a hidden text field if other is selected.
In your posts_controller do:
def create
...
if params[:other_name]
post.categories.create(name: param[:other_name])
end
...
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With