Nested Form has_many - ruby-on-rails

I am not sure how to proper create a rails form with nested form. I have followed many tutorial but becoming more confused has in what should it be, singular plurials, controller... Here my models
model/event.rb
attr_accessible :description :title, :tag_ids, :locations_attributes
has_many :location
accepts_nested_attributes_for :location, :allow_destroy => true
model/location.rb
attr_accessible :address, :customer_id, :event_id, :latitude, :longitude
belongs_to :customer
belongs_to :event
controller.rb
def new
#event = Event.new
...
def create
#event = Event.new(params[:event])
...
view form.html.erb
<%= form_for(#event) do |f| %>
<%= f.fields_for :locations do |e| %>
<%= e.text_field :longitude %>
<%= e.text_field :latitude %>
<% end %>
...
<% end %>
error
Can't mass-assign protected attributes: locations
params send
"locations"=>{"longitude"=>"45.6666",
"latitude"=>"47.44444665"},
Either my relationship are wrong because fields_for doesn't support it, either my controller is not proper, or either rails is just not a great language, or i don't understand it anymore.

You.re nearly there...
event.rb - locations NOT location
attr_accessible :description :title, :tag_ids, :locations_attributes
has_many :locations
accepts_nested_attributes_for :locations, :allow_destroy => true
Should do it I think
edit
And as Valery Kvon says, you need to add
#event.locations.build
to your controller

Edward's answer +
def new
#event = Event.new
#event.locations.build
end

Related

Nested checkboxes in Rails

I'm trying to create an event app where each event has multiple tables and each table has multiple people sitting at a table the event has multiple tickets which map the people to the tables that they are sitting at -> in order to achieve this I have created a checkbox nested in the fields_for :tables (which is in turn in the event form) I presume something is wrong with either the strong parameters or the form itself but I have not been able to find any information that provides a solution to the problem.After checking the checkboxes in the form indicating which people are going to be sitting at this table and submitting the form and returning to the form I find that the checkboxes are no longer checked???
here are the contents of my model files
# models
class Event < ActiveRecord::Base
has_many :tables, dependent: :destroy
has_many :people , through: :tickets
has_many :tickets
accepts_nested_attributes_for :tickets, allow_destroy: true
accepts_nested_attributes_for :tables, allow_destroy: true
end
class Table < ActiveRecord::Base
belongs_to :event
has_many :tickets
has_many :people, through: :tickets
end
class Ticket < ActiveRecord::Base
belongs_to :table
belongs_to :person
end
class Person < ActiveRecord::Base
has_many :tickets
has_many :tables, through: :tickets
end
Here is the form with parts omitted for brevity.
<%= form_for(#event) do |f| %>
...
<%= f.fields_for :tables do |builder| %>
<%= render 'table_field', f: builder %>
<% end %>
<%= link_to_add_fields "Add Table", f, :tables %>
...
<% end %>
And here is the checkbox list I have implemented within the table_field.
<% Person.all.each do |person| %>
<div class="field">
<%= check_box_tag "table[people_ids][]", person.id, f.object.people.include?(person) %> <%= f.label [person.first_name, person.last_name].join(" ") %>
</div>
<% end %>
this is the event_params
def event_params
params.require(:event).permit(:name, :description, :start, :end, :latitude, :longitude, :address, :data, :people_ids => [], tables_attributes: [:id, :number, :size, :people_ids => []]).tap do |whitelisted|
whitelisted[:data] = params[:event][:data]
end
How do I get the checkboxes to be persistently checked in this form?
You can use http://apidock.com/rails/v4.0.2/ActionView/Helpers/FormOptionsHelper/collection_check_boxes
<%= f.collection_check_boxes(:people_ids, Person.all, :id, :name) do |person| %>
<%= person.label { person.check_box } %>
<% end %>
It will persist data as well.

Auto set value of nested attributes in Rails

I have a model location which has many messages. I am trying to let users who are browsing locations to send messages to location owners.
I have location.rb
has_many :messages
accepts_nested_attributes_for :messages
and message.rb
belongs_to :location
In locations_controller.rb
def location_params
params.require(:location).permit(:name,
:user_id,
:image,
:latitude,
:longitude,
location_images_attributes: [:id, :location_id, :location_image, :_destroy],
messages_attributes: [:id, :location_id, :from_email, :to_email, :content])
end
Currently i have in my view the following code:
<%= simple_form_for #location do |l| %>
<%= l.simple_fields_for :messages, #location.messages.build do |m| %>
<%= m.input :content %>
<%= m.input :to_email, :input_html => {:value => #location.user.email}, as: :hidden %>
<% end %>
<%= l.button :submit %>
<% end %>
I don't want to set the value of the email field via hidden field, but i want to pass value from controller. Or a model. Please advise.
Maybe try this approach in your Location model:
class Location < ActiveRecord::Base
has_many :messages, after_add: :set_email
accepts_nested_attributes_for :messages
def set_email(message)
message.to_email = user.email
end
end
Basically you have to register a method for Location, when new message is added to messages set.
In this example I named it set_email, which takes the message object as it's argument, and you can freely modify it. I'm just setting the to_email based on location's email.
Hope this solves your problem!
You could do something like this in the Message model:
before_create :set_to_email
def set_to_email
self.to_email = self.location.user.email
end
Downside is that on every create action a few additional database lookups are being performed. So on the grand scale this is not an ideal solution in terms of performance optimization.

Many to many relationship with nested forms in Rails 4 - Unpermitted parameters error

I am attempting to make the relationship in the Products controller through the Categorizations model to the ClothingSize model. I am getting a "Unpermitted parameters: clothing_size" error and the relationship is not ever made as a result. I think there is something wrong with the nested forms in the view as I cannot get the "Size" field to appear unless the symbol is singular shown below. I think that may be pointing to another problem.
<%= form_for(#product) do |f| %>
<%= f.fields_for :clothing_size do |cs| %>
Models
Products
class Product < ActiveRecord::Base
has_many :categorizations
has_many :clothing_sizes, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Categorizations
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :clothing_size
accepts_nested_attributes_for :clothing_size
end
ClothingSizes
class ClothingSize < ActiveRecord::Base
has_many :categorizations
has_many :products, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Controller for Products
def new
#product = Product.new
test = #product.categorizations.build
def product_params
params.require(:product).permit(:title, :description, :image_url, :image_url2, :price, :quantity, :color, :clothing_sizes => [:sizes, :clothing_size_id])
end
end
View
<%= form_for(#product) do |f| %>
<%= f.fields_for :clothing_size do |cs| %>
<div class="field">
<%= cs.label :sizes, "Sizes" %><br>
<%= cs.text_field :sizes %>
</div>
<% end %>
<% end %>
In your view you have :clothing_size (singular) but in your product_params method you have :clothing_sizes (plural). Because your Product model has_many :clothing_sizes, you want it to be plural in your view.
<%= form_for(#product) do |f| %>
<%= f.fields_for :clothing_sizes do |cs| %>
In addition, you'll want to build a clothing_size for your product in your controller, and permit :clothing_sizes_attributes rather than clothing sizes in your product_params method. (I'd separate your new and product_params methods, making product_params private, but that's just me.)
def new
#product = Product.new
#clothing_size = #product.clothing_sizes.build
end
private
def product_params
params.require(:product).permit(:title, :description, :image_url, :image_url2, :price, :quantity, :color, :clothing_sizes_attributes => [:sizes, :clothing_size_id])
end

Mass assignment error with polymorphic association

I'm getting a mass assignment error when submitting a nested form for a has_one polymorphic model. The form is trying to create Employee and Picture instances based on the polymorphic association Rails guide.
I would appreciate literally ANY functioning example of a nested creation form for a has_one polymorphic model! I know there are tons of questions on mass assignment errors but I've never seen a working example with polymorphic associations.
Models
class Picture < ActiveRecord::Base
belongs_to :illustrated, :polymorphic => true
attr_accessible :filename, :illustrated
end
class Employee < ActiveRecord::Base
has_one :picture, :as => :illustrated
accepts_nested_attributes_for :picture
attr_accessible :name, :illustrated_attribute
end
Migrations
create_table :pictures do |t|
t.string :filename
t.references :illustrated, polymorphic: true
end
create_table :employees do |t|
t.string :name
end
controllers/employees_controller.rb
...
def new
#employee = Employee.new
#employee.picture = Picture.new
end
def create
#employee = Employee.new(params[:employee])
#employee.save
end
...
Error
Can't mass-assign protected attributes: illustrated
app/controllers/employees_controller.rb:44:in `create'
{"utf8"=>"✓", "authenticity_token"=>"blah"
"employee"=>{"illustrated"=>{"filename"=>"johndoe.jpg"},
"name"=>"John Doe"},
"commit"=>"Create Employee"}
In the models, I've tried every permutation of :illustrated, :picture, :illustrated_attribute, :illustrated_attributes, :picture_attribute, :picture_attributes, etc. Any tips or examples?
EDIT:
_form.html.erb
<%= form_for(#employee) do |f| %>
<%= f.fields_for :illustrated do |form| %>
<%= form.text_field :filename %>
<% end %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
You need to specify the nested_attributes appropriately. accepts_nested_attributes_for takes the name of the association as parameter and then you need to add the same assoc_attributes to your attr_accessible. So change your Employee model to
class Employee < ActiveRecord::Base
has_one :picture, :as => :illustrated
accepts_nested_attributes_for :picture
attr_accessible :name, :picture_attribute
end
And change the field_for line in the view code to
<%= f.fields_for :picture do |form| %>

Rails has_many through form with checkboxes and extra field in the join model

I'm trying to solve a pretty common (as I thought) task.
There're three models:
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
My problems begin when it comes to Product new/edit form.
When creating a product I need to check categories (via checkboxes) which it belongs to. I know it can be done by creating checkboxes with name like 'product[category_ids][]'. But I also need to enter a description for each of checked relations which will be stored in the join model (Categorization).
I saw those beautiful Railscasts on complex forms, habtm checkboxes, etc. I've been searching StackOverflow hardly. But I haven't succeeded.
I found one post which describes almost exactly the same problem as mine. And the last answer makes some sense to me (looks like it is the right way to go). But it's not actually working well (i.e. if validation fails). I want categories to be displayed always in the same order (in new/edit forms; before/after validation) and checkboxes to stay where they were if validation fails, etc.
Any thougts appreciated.
I'm new to Rails (switching from CakePHP) so please be patient and write as detailed as possible. Please point me in the right way!
Thank you. : )
Looks like I figured it out! Here's what I got:
My models:
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
The form:
<%= 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 %>
And the controller:
class ProductsController < ApplicationController
# use `before_action` instead of `before_filter` if you are using rails 5+ and above, because `before_filter` has been deprecated/removed in those versions of rails.
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
From the first glance it works just fine. I hope it won't break somehow.. :)
Thanks all. Special thanks to Sandip Ransing for participating in the discussion. I hope it will be useful for somebody like me.
use accepts_nested_attributes_for to insert into intermediate table i.e. categorizations
view form will look like -
# 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
Inside view
= 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
I just did the following. It worked for me..
<%= f.label :category, "Category" %>
<%= f.select :category_ids, Category.order('name ASC').all.collect {|c| [c.name, c.id]}, {} %>

Resources