Nested Form Gem throws "undefined method" - ruby-on-rails

I'm new to Nested Forms. I'm building a form where Companies can create Promos as under a few Categories. I keep getting undefined method "model_id" for Class. My codes and exact error messages are as below :-
Error Message:-
undefined method `category_id' for #<Company:0x007fd43828b6f8>
<%= f.collection_select :category_id, Category.all, :id, :name, {include_blank: 'Choose Category'}, required: true %>
<%= render partial: 'promo', locals: {f: f}%>
Models :-
class Company < ActiveRecord::Base
has_many :users, :through => :company_customers
has_many :company_users
has_many :categories, :through => :category_companies
has_many :category_companies
accepts_nested_attributes_for :category_companies
accepts_nested_attributes_for :categories s
end
class CompanyCategory < ActiveRecord::Base
belongs_to :company
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :company_categories
has_many :companies, :through => :company_categories
has_many :promos
accepts_nested_attributes_for :company_categories
end
class Promo < ActiveRecord::Base
belongs_to :category
end
Controller :-
class CompaniesController < ApplicationController
def new
#company = Company.new
end
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
format.html { redirect_to #company, notice: 'Company was successfully created.' }
format.json { render :show, status: :created, location: #company }
else
format.html { render :new }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_company
#company = Company.find(params[:id])
end
def company_params
params.require(:company).permit(:company_name, :registration_no, :address, :phone_no, :outlet_location, company_categories_attributes: [:id, :company_id, :category_id, category_attributes:[:id, :name, promo_attribute:[:id, :name, :description]]])
end
end
View:-
<%= form_for(:company) do |f| %>
<div class="medium-6 columns">
<%= f.collection_select :category_id, Category.all, :id, :name, {include_blank: 'Choose Category'}, required: true %>
<%= render partial: 'promo', locals: {f: f}%>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
_promo.html.erb
<%= f.fields_for :promos do |promo| %>
<h4 class="text-center">Promotions to be offered</h4><br>
<div class="row">
<div class="medium-6 columns">
<%= f.text_field :name, placeholder: "Name", required: true %>
</div>
<div class="medium-6 columns">
<%= f.text_field :description, placeholder: "Description", required: true %>
</div>
</div>
<% end %>
<p><%= f.link_to_add "Add More Promos", :promos %> </p>
Appreciate the help. Many Thanks !

From the docs, the collection_select has the following use:
collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
They have this example:
class Post < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :posts
def name_with_initial
"#{first_name.first}. #{last_name}"
end
end
Which would be used like this:
collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true)
So, when you do:
f.collection_select :category_id, Category.all, :id, :name, {include_blank: 'Choose Category'}, required: true
I think maybe you're missing the object argument? Like, where they have :post.

Related

Nested association has_many;through doesn't update collection_check_boxes

Using check boxes to update the nested form I can't update the tables. I received following message:
Unpermitted parameter: :category
ActionController::Parameters
{"name"=>"Flux Capacitor", "price"=>"19.55"} permitted: true
I have tried different ways to fix this through the permitted params, including a :category parameter, like so:
def product_params
params.require(:product).permit(:id, :name, :price, :category, categories_attributes: [:id, :name, :category], categorizations_attributes: [:id, :product_id, :category_ids, :category])
end
My models
class Product < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categories, reject_if: proc {|attributes| attributes['name'].blank?}
accepts_nested_attributes_for :categorizations
end
class Categorization < ApplicationRecord
belongs_to :product, inverse_of: :categorizations
belongs_to :category, inverse_of: :categorizations
end
class Category < ApplicationRecord
has_many :categorizations
has_many :products, through: :categorizations, inverse_of: :category
end
class ProductsController < ApplicationController
def edit
#categories = Category.all
end
def new
#product = Product.new
end
def create
#product = Product.new(product_params)
if #product.save
flash[:notice] = 'Product succesfully created'
redirect_to products_path
else
flash[:notice] = 'Product was not created'
render 'edit'
end
end
def update
if #product.update(product_params)
flash[:notice] = "Product succesfully updated"
redirect_to products_path
else
flash[:notice] = 'Product was not updated'
render 'edit'
end
end
app/view/products/edit.html.erb
<%= simple_form_for(#product) do |f| %>
<%= f.input :name %>
<%= f.input :price %>
<%= f.simple_fields_for #product.categories do |cats| %>
<%= cats.collection_check_boxes :ids, Category.all, :id, :name, collection_wrapper_tag: :ul, item_wrapper_tag: :li %>
<% end %>
<%= f.button :submit %>
<% end %>
This seems like something that is common enough that rails and/or simple_form, should provide in a more built-in way to do this. Am I missing something obvious?
If I am understanding you correctly you should be able to do this without the use of accepts_nested_attributes_for or simple_fields_for. Try something like this:
<%= simple_form_for(#product) do |f| %>
<%= f.input :name %>
<%= f.input :price %>
<%= f.association :categories, as: :check_boxes %>
<%= f.button :submit %>
<% end %>
your strong params should look something like this:
def product_params
params.require(:product).permit(:id, :name, :price, { category_ids: [] }])
end

Rails nested form with cocoon. Attributes using model

I am trying to create a nested form which has options and suboptions, both from the same model called Option. Here is the content of the files:
Model:
class Option < ApplicationRecord
belongs_to :activity
has_many :option_students
has_many :students, through: :option_students
has_many :suboptions,
class_name: "Option",
foreign_key: "option_id"
belongs_to :parent,
class_name: "Option",
optional: true,
foreign_key: "option_id"
accepts_nested_attributes_for :suboptions,
reject_if: ->(attrs) { attrs['name'].blank? }
validates :name, presence: true
end
Controller:
class OptionsController < ApplicationController
include StrongParamsHolder
def index
#options = Option.where(option_id: nil)
end
def show
#option = Option.find(params[:id])
end
def new
#option = Option.new()
1.times { #option.suboptions.build}
end
def create
#option = Option.new(option_params)
if #option.save
redirect_to options_path
else
render :new
end
end
def edit
#option = Option.find(params[:id])
end
def update
#option = Option.find(params[:id])
if #option.update_attributes(option_params)
redirect_to options_path(#option.id)
else
render :edit
end
end
def destroy
#option = Option.find(params[:id])
#option.destroy
redirect_to options_path
end
end
_form.html.erb:
<%= form_for #option do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<%= f.label :activity %><br>
<%= select_tag "option[activity_id]", options_for_select(activity_array) %><br>
</p>
<div>
<div id="suboptions">
<%= f.fields_for :suboptions do |suboption| %>
<%= render 'suboption_fields', f: suboption %>
<% end %>
<div class="links">
<%= link_to_add_association 'add suboption', f, :suboptions %>
</div>
</div>
</div>
<p>
<%= f.submit "Send" %>
</p>
<% end %>
_suboption_fields.html.erb
<div class="nested-fields">
<%= f.label :suboption %><br>
<%= f.text_field :name %>
<%= link_to_remove_association "X", f %>
</div>
StrongParamsHolder:
def option_params
params.require(:option).permit(:name, :activity_id, :students_ids => [], suboptions_attributes: [:id, :name])
end
The view is created correctly, but it is not saving. It goes to "render :new" on create controller. I think it should be a problem with the params, but I am not sure what.
Probably not saving because of a failed validation. If you are using rails 5, the belongs_to is now more strict, and to be able to save nested-params you need to make the connection/relation between association explicit.
So imho it will work if you add the inverse_of to your relations as follows:
has_many :suboptions,
class_name: "Option",
foreign_key: "option_id",
inverse_of: :parent
belongs_to :parent,
class_name: "Option",
optional: true,
foreign_key: "option_id"
inverse_of: :suboptions
If another validation is failing, it could also help to list the errors in your form (e.g. something like #option.errors.full_messages.inspect would help :)
As an aside: I would rename the option_id field in the database to parent_id as this more clearly conveys its meaning.

Rails 4 nested form with has_many, through and multiple select

I have a problem with nested form and has_many relation. Bussiness case: there are laboratories and their suppliers. Suppliers can be shared between labs.
Models
class Lab < ActiveRecord::Base
has_many :lab_suppliers
has_many :suppliers, through: :lab_suppliers
accepts_nested_attributes_for :lab_suppliers
end
class Supplier < ActiveRecord::Base
has_many :lab_suppliers
has_many :labs, through: :lab_suppliers
accepts_nested_attributes_for :lab_suppliers
end
class LabSupplier < ActiveRecord::Base
belongs_to :lab
belongs_to :supplier
accepts_nested_attributes_for :lab
accepts_nested_attributes_for :supplier
end
Form
<%= form_for(#lab) do |f| %>
<div class="field">
<%= f.label :code %><br>
<%= f.text_field :code %>
</div>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class"field">
<%= fields_for :lab_suppliers do |ff| %>
<%= ff.label :supplier_id %><br>
<%= ff.collection_select :supplier_id, Supplier.all, :id, :name, {include_blank: true}, {:multiple => true, :class=>""} %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Controller
class LabsController < ApplicationController
before_action :set_lab, only: [:show, :edit, :update, :destroy]
# GET /labs/new
def new
#lab = Lab.new
#lab.lab_suppliers.build
end
# POST /labs
# POST /labs.json
def create
#raise params.inspect
#lab = Lab.new(lab_params)
#lab_supplier = #lab.lab_suppliers.new(params[:lab_suppliers])
#lab_supplier.save
#lab.save
private
def lab_params
params.require(:lab).permit(:code, :name, lab_suppliers_attributes: [])
end
end
Result of the inspect on params after submitting form:
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"...",
"lab"=>{"code"=>"L01",
"name"=>"xxx"},
"lab_suppliers"=>{"supplier_id"=>["",
"1",
"3"]},
"commit"=>"Create Lab"}
While submitting form I receive ActiveModel::ForbiddenAttributesError
on the line:
#lab_supplier = #lab.lab_suppliers.new(params[:lab_suppliers])
What am i missing to make it work as expected?
It seems like you need to explicitly tell lab_params which attributes from lab_suppliers you need to pass like:
params.require(:lab).permit(:code, :name, lab_suppliers_attributes: [:supplier_id])
Try it and let me know.
Link to other post that helped me to find the working solution:
[Rails nested form with multiple entries
Below I provide the working solution showing how to pass values from the multiple select as nested attributes and insert them to the db.
Models
class Lab < ActiveRecord::Base
has_many :lab_suppliers#, :foreign_key => 'lab_id', dependent: :destroy
has_many :suppliers, through: :lab_suppliers
accepts_nested_attributes_for :lab_suppliers, :allow_destroy => true
end
class Supplier < ActiveRecord::Base
has_many :lab_suppliers
has_many :labs, through: :lab_suppliers
end
class LabSupplier < ActiveRecord::Base
belongs_to :lab
belongs_to :supplier
end
Comment:
accepts_nested_attributes_for is put only on has_many/has_one side. No need to put it on belongs_to side
Form (Lab)
<%= form_for(#lab) do |f| %>
<div class="field">
<%= f.label :code %><br>
<%= f.text_field :code %>
</div>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class"field">
<%= f.fields_for :lab_suppliers do |ff| %>
<%= ff.label :supplier_id %><br>
<%= ff.collection_select :supplier_id, Supplier.all, :id, :name, {include_blank: true}, {:multiple => true, :class=>""} %>
<% end %>
<%= f.submit %><% end %>
Controller
Comment:
There is no need to permit any additional params in supplier or lab_suppliers controllers
class LabsController < ApplicationController
before_action :set_lab, only: [:show, :edit, :update, :destroy]
def new
#lab = Lab.new
#lab.lab_suppliers.build
end
def create
#lab = Lab.new(lab_params)
#startcount=1 #start counting from 1 because the first element in the array of nested params is always null
#lab.lab_suppliers.each do |m|
#raise lab_params[:lab_suppliers_attributes]["0"][:supplier_id][#startcount].inspect
m.supplier_id = lab_params[:lab_suppliers_attributes]["0"][:supplier_id][#startcount]
#startcount +=1
end
respond_to do |format|
if #lab.save
lab_params[:lab_suppliers_attributes]["0"][:supplier_id].drop(#startcount).each do |m|
#lab.lab_suppliers.build(:supplier_id => lab_params[:lab_suppliers_attributes]["0"][:supplier_id][#startcount]).save
#startcount += 1
end
format.html { redirect_to labs_path, notice: 'Lab was successfully created.' }
format.json { render :show, status: :created, location: #lab }
else
format.html { render :new }
format.json { render json: #lab.errors, status: :unprocessable_entity }
end
end
end
private
def lab_params
params.require(:lab).permit(:name, :code, lab_suppliers_attributes: [supplier_id: [] ])
end
end
Comment: supplier_id: [] in the lab_suppliers_attributes permitts array of values from the multiple dropdown to be passed

Rails 4.2 nested form attributes not saving

I cannot seem to get nested attributes to save to the database, though I can see the params in terminal. I am using Rails 4.2.
Here are my models:
class Device < ActiveRecord::Base
belongs_to :hub
has_many :accessories, dependent: :destroy
accepts_nested_attributes_for :accessories,
reject_if: proc { |attributes| attributes['material'].blank? },
allow_destroy: true
end
class Accessory < ActiveRecord::Base
belongs_to :device
end
Here is the controller. I have my device model nested under user and hub model.
class DevicesController < ApplicationController
def edit
#user = User.find_by(params[:user_id])
#hub = Hub.find_by_title(params[:hub_id])
#device = Device.find_by(id: params[:id])
end
def update
#user = User.find_by(params[:user_id])
#hub = Hub.find_by_title(params[:hub_id])
#device = Device.find_by(id: params[:id])
if #device.update_attributes(device_params)
flash[:success] = "update successfully"
redirect_to user_hub_device_path(#user, #hub, #device)
else
render 'edit'
end
end
private
def device_params
params.require(:device).permit(:model, :hub_id, :resolution, :materials, :startcost, :take_online, :delivery_time, :unitcost, :color, :accessories, :accessories_attributes => [:id, :name, :cost, :color, :device_id, :_destroy])
end
end
Finally is my form.
<%= form_for([#user, #hub, #device]) do |f| %>
<fieldset>
<div id="material">
<%= f.fields_for :accessories do |a| %>
<%= render 'devices/accessory', a: a %>
<% end %>
</div>
</fieldset>
The partial:
<div class="row">
<%= a.collection_select :name, Material.all, :material, :material %>
<%= a.text_field :cost, id: "right-label" %>
<%= a.text_field :color, id: "right-label" %>
<%= a.check_box :_destroy %>
</div>
You are whitelisting params[:device][:materials] but you are checking attributes['material'].blank? (note the the s on the end). Which causes the nested attributes to be rejected.

Rails 4 nested attributes multiple records when updating

I'm stuck and I don't know why it is not working right.
I have a model product which has many tags.
When I update the product rails update properly the products attributes but is creating another tag record instead of just updating it.
here is my code:
View form:
<%= form_for ([#product.user, #product]), id: 'edit_form' do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.fields_for :tags do |t| %>
<%= t.label :name %>
<%= t.text_field :name %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
product model:
class Product < ActiveRecord::Base
belongs_to :user, :foreign_key => "user_id"
has_many :tags, :dependent => :destroy
accepts_nested_attributes_for :tags, reject_if: :all_blank, allow_destroy: true, :update_only => true
end
tags model:
class Tag < ActiveRecord::Base
belongs_to :product, :foreign_key => "product_id"
# before_save { name.downcase! }
end
product controller:
def edit
user = User.find(params[:user_id])
#product = user.products.find(params[:id])
#tags = #product.tags.all
respond_to do |format|
format.html
format.js
end
end
def update
user = User.find(params[:user_id])
#product = user.products.find(params[:id])
#tags = #product.tags.all
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to([#product.user, #product], :notice => 'Product successfully updated.') }
else
format.html { render :action => "edit" }
end
end
end
def product_params
params.require(:product).permit(:name, :description, tags_attributes: :name)
end
You have to pass the tag id in the permit params in your controller
def product_params
params.require(:product).permit(:name, :description, tags_attributes: [:id,:name])
end

Resources