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
Related
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.
I would like to structure a music web-application's database to use the following structure.
Song(Attributes: name, year, etc(any other relevant attributes))
has many Performances
has many Performers (through Performances)
has many Artists (through Performers)
Performance(Foreign key:Song_id, Attributes: enum version: [:official, :remix, :cover])
belongs to Song
has many Performers
has many Artists (through Performers)
Performers (Foreign keys: Performance_id, Artist_id, Attributes: enum role: [:main_artist, :collaborating_artist, :featuring_artist, :versus ])
has one Song (through Performers)
belongs to Performances
belongs to Artist
Artist(Attributes: name, DOB, etc.(any other relevant attributes))
has many Performers
has many Performances (through Performers)
has many Songs (through Performances)
So that upon saving a Song, i can have all the rest of the other tables populated too, that is via the associations, however, so far i had the following implementations:
Song model:
class Song < ActiveRecord::Base
#Added to make the db more logical and ended up complicated
has_many :performances
has_many :performers, :through => :performances
has_many :artists, :through => :performers
accepts_nested_attributes_for :artists, allow_destroy: true
accepts_nested_attributes_for :performers, allow_destroy: true
accepts_nested_attributes_for :performances, allow_destroy: true
validates :name, :presence => true, uniqueness: true
validates :year, :presence => true
end
Performance model:
class Performance < ActiveRecord::Base
enum version: [:official, :remix, :cover]
belongs_to :song
has_many :performers
has_many :artists, :through => :performers
end
Performer model:
class Performer < ActiveRecord::Base
enum role: [:main_artist, :collaborating_artist, :featuring_artist, :versus]
belongs_to :artist
belongs_to :performance
has_one :song, :through => :performers #this upsets the back hairs!
end
Artist model:
class Artist < ActiveRecord::Base
#Added to make the db more logical and ended up complicated
has_many :performers
has_many :performances, :through => :performers
has_many :songs, :through => :performances
validates :name, :presence => true, uniqueness: true
end
Song controller
class SongsController < ApplicationController
before_action :find_song, only: [:show, :edit, :update, :destroy]
def index
...
end
def new
#song = Song.new
#song.performers.build
#song.performances.build
#genres = Genre.all #.map{|c| [ c.name, c.id ] }
#countries = Country.all.map{|c| [ c.name, c.id ] }
end
def create
#song = Song.new(song_params)
if #song.save
redirect_to #song, notice: 'Successfully added a song.'
else
render 'new'
end
end
def show
...
end
def update
if #song.update(song_params)
redirect_to #song, notice: 'Successfully updated the song.'
else
render 'edit', notice: 'Unable to save the changes'
end
end
def edit
...
end
def destroy
...
end
private
def song_params
params.require(:song).permit(:name, :year, artists_attributes: [ :id, :name, :DOB], performances_attributes: [:id, :version, :song_id], performers_attributes: [:id, :performance_id, :artist_id])
end
def find_song
#song = Song.find(params[:id])
end
end
new.html.erb
<div class="content">
<%= form_for #song, html: { multipart: true } do |f| %>
<% if #song.errors.any? %>
<div>
<%= #song.errors.count %>
Prevented this song from saving
<ul>
<% #song.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name, class: "input-field" %>
</div>
<div class="song-version">
<div class="field">
<%= f.fields_for :performances do |performances_for_form| %>
<%= render 'performances_fields', f: performances_for_form %>
<% end %>
</div>
</div>
<div class="performers-fields">
<h2>Artist(s)</h2>
<div class="field">
<%= f.fields_for :performers do |performers_for_form| %>
<%= render 'performers_fields', f: performers_for_form %>
<% end %>
</div>
</div>
<div class"btn">
<%= f.submit %>
</div>
<% end %>
</div>
Performance partial
<fieldset>
<div class="field">
<%= f.label :version %>
<%= f.select :version, Performance.versions.keys %>
</div>
</fieldset>
Performer partial
<fieldset>
<h2>Artists Details</h2>
<div class="field">
<%= f.fields_for :artists do |artists_for_form| %>
<%= render 'artist_fields', f: artists_for_form %>
<% end %>
</div>
<div class="field">
<%= f.label :artists_role %>
<%= f.select :role, Performer.roles.keys %>
</div>
</fieldset>
Artist partial
<fieldset>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :DOB %>
<%= f.datetime_select :DOB, :ampm => true, :minute_step => 15 %>
</div>
</fieldset>
When I fill out all the data on the form, upon saving, only the Song's attributes and Performances attributes are saved to the database, while the Performers & Artists are never saved, neither is the connection established, my questions are:
Is this structure logically correct? If not what is it that is being incorrectly done.
Is the new, create methods of songs_controller wrongly implemented and thus, the reason for not being able to save the data appropriately?
What right and or best practices could be employed developing this type of web-application with this kind of database structure?
Or is there any other better or succinct database structure that would better be followed rather than this?
I'm trying to implement a has_many :through many to many form, but I'm having an issue submitting to the database. I have no field validations, which tells me it's complaining about the structure of the parameters hash more than anything.
The error is:
ActiveRecord::RecordInvalid (Validation failed: Expense expense categories expense must exist):
The parameter hash looks like this:
Parameters: {"utf8"=>"✓", "expense"=>{"date"=>"2006/12/12", "amount"=>"234", "check_number"=>"234", "debit"=>"0", "notes"=>"234", "expense_expense_categories_attributes"=>{"1464029611137"=>{"amount"=>"234", "expense_category_id"=>"1", "_destroy"=>"false"}}}, "commit"=>"Create Expense"}
One thing I notice is that it's not adding the :expense_id value into the junction table. This should be done by the accepts_nested_attributes_for mechanism but it's not. I'm starting to think this an issue with Rails 5 because I've had similar relationships and forms structured like this that works fine. Do you guys see anything I'm missing?
here's my controller:
def create
#expense = Expense.new(expense_params)
respond_to do |format|
if #expense.save!
#expenses = Expense.all.paginate(:page => params[:page], :per_page => 9).order("created_at DESC")
format.html { redirect_to #expense, notice: 'Expense was successfully created.' }
format.js {}
format.json { render json: #expense, status: :created, location: #expense }
else
#expenses = Expense.all.paginate(:page => params[:page], :per_page => 9).order("created_at DESC")
format.html { render action: "index" }
format.js {}
format.json { render json: #expense.errors, status: :unprocessable_entity }
end
end
end
def expense_params
params.require(:expense).permit(:id, :date, :amount, :check_number, :debit, :notes, :amount, :expense_expense_categories_attributes => [:id, :amount, :expense_id , :expense_category_id, :_destroy])
end
Here are my models:
Expense
class Expense < ApplicationRecord
has_one :payee
monetize :amount_cents
has_many :expense_expense_categories
has_many :expense_categories, through: :expense_expense_categories, :dependent => :destroy
accepts_nested_attributes_for :expense_expense_categories,:allow_destroy => true
end
ExpenseCategory:
class ExpenseCategory < ApplicationRecord
has_many :expense_expense_categories
has_many :expenses, through: :expense_expense_categories
end
ExpenseExpenseCategory
class ExpenseExpenseCategory < ApplicationRecord
monetize :amount_cents
belongs_to :expense
belongs_to :expense_category
accepts_nested_attributes_for :expense_category
end
_form.html.erb:
<%= form_for #expense, html: { :class => "ui form segment" }, :remote => true do |f|%>
<div class="field">
<%= f.label :date%>
<div class="ui small input">
<%= f.date_field :date %>
</div>
</div>
<div class="field">
<%= f.label :amount %>
<div class="ui small input">
<%= f.text_field :amount %>
</div>
</div>
<div class="field">
<%= f.label :check_number %>
<div class="ui small input">
<%= f.text_field :check_number %>
</div>
</div>
<div class="field">
<%= f.label :debit %>
<div class="ui small input">
<%= f.check_box :debit %>
</div>
</div>
<div class="field">
<%= f.label :notes %>
<div class="ui small input">
<%= f.text_area :notes %>
</div>
</div>
<%= f.fields_for :expense_expense_categories do |builders| %>
<%= render 'expense_expense_category_fields', :f => builders %>
<% end %>
<%= link_to_add_fields "Add Category", f, :expense_expense_categories %>
<%= f.submit class: "ui blue button" %>
expense_expense_category_fields.htnl.erb
<div class="field">
<%=f.label :amount%>
<%= f.text_field :amount %>
</div>
<div class="field">
<%=f.label :category%>
<%= f.select :expense_category_id, ExpenseCategory.all.collect { |p| [p.category, p.id] } %>
</div>
<%= f.hidden_field :_destroy %>
<%= link_to "Remove option", "#", :class => "remove_expense_expense_categories" %>
Here is the form data from the browser being submitted:
utf8:✓
expense[date]:2016-05-12
expense[amount]:23
expense[check_number]:23
expense[debit]:0
expense[notes]:
expense[expense_expense_categories_attributes][1464030986149][amount]:23
expense[expense_expense_categories_attributes][1464030986149][expense_category_id]:1
expense[expense_expense_categories_attributes][1464030986149][_destroy]:false
expense[expense_expense_categories_attributes][1464030991099][amount]:43
expense[expense_expense_categories_attributes][1464030991099][expense_category_id]:10
expense[expense_expense_categories_attributes][1464030991099][_destroy]:false
commit:Create Expense
This is because Rails is attempting to create ExpenseExpenseCategory before the expense has been created. You must define; :inverse_of on the associations.
class Expense < ApplicationRecord
has_one :payee
monetize :amount_cents
has_many :expense_expense_categories, inverse_of: :expense
has_many :expense_categories, through: :expense_expense_categories, :dependent => :destroy
accepts_nested_attributes_for :expense_expense_categories,:allow_destroy => true
end
class ExpenseCategory < ApplicationRecord
has_many :expense_expense_categories, inverse_of: :expense_category
has_many :expenses, through: :expense_expense_categories
end
class ExpenseExpenseCategory < ApplicationRecord
monetize :amount_cents
belongs_to :expense, inverse_of: :expense_expense_categories
belongs_to :expense_category, inverse_of: :expense_expense_categories
accepts_nested_attributes_for :expense_category
end
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
Please check this link
https://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html
inverse_of didn't worked for me, I have to use optional: true in belongs_to association.
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.
I'm getting the following error when I attempt to submit my nested form.
Cannot modify association 'Appointment#addresses' because the source reflection class 'Address' is associated to 'User' via :has_many
I'm not entirely sure which part of my setup is wrong. To briefly explain, I have Users that have multiple Appointments and multiple Addresses. Each Appointment can happen at a different Address, which is why I'm doing a :has_many association through user (which is correct, right?). Why am I getting this error?
Here are my models:
class User < ActiveRecord::Base
has_many :addresses, dependent: :destroy
has_many :appointments, dependent: :destroy
end
class Appointment < ActiveRecord::Base
belongs_to :user
has_many :addresses, :through => :user
accepts_nested_attributes_for :addresses
end
class Address < ActiveRecord::Base
belongs_to :user
end
And this is the create method from my Appointments controller:
class AppointmentsController < ApplicationController
...
def create
#appointment = current_user.appointments.build(appointment_params)
#address = #appointment.addresses.build(appointment_params[:addresses_attributes]["0"])
respond_to do |format|
if #appointment.save
format.html { redirect_to current_user, notice: 'Appointment was successfully created.' }
format.json { render :show, status: :created, location: current_user }
else
format.html { render :new }
format.json { render json: #appointment.errors, status: :unprocessable_entity }
end
end
end
...
private
def appointment_params
params.require(:appointment).permit(:appointment_date, :appointment_start_time, :appointment_end_time, :comments, :phone_number, addresses_attributes: [:user_id, :street_address, :street_address_optional, :city, :state, :zip_code, :primary])
end
end
And finally, this is my form in my view:
<%= form_for(#appointment, :url => {:controller => "appointments", :action => "create"}, :html => {"data-abide" => ""}) do |f| %>
<label>
Appointment Date
</label>
<%= f.date_select :appointment_date %>
<label>
Appointment Timeframe Start
</label>
<%= f.time_select :appointment_start_time %>
<label>
Appointment Timeframe End
</label>
<%= f.time_select :appointment_end_time %>
<%= f.fields_for :addresses do |builder| %>
<%= builder.hidden_field :user_id, :value => current_user.id %>
<label>
Street Address
<%= builder.text_field :street_address %>
</label>
<label>
Street Address (Optional)
<%= builder.text_field :street_address_optional %>
</label>
<label>
City
<%= builder.text_field :city %>
</label>
<label>
State
<%= builder.text_field :state %>
</label>
<label>
Zip Code
<%= builder.number_field :zip_code %>
</label>
<%= builder.check_box :primary %><%= builder.label :primary %>
<% end %>
<label>
Special Instructions
<%= f.text_area :comments %>
</label>
<%= f.submit "Sign Up", :class => "button expand"%>
<% end %>
Thanks in advance for the help :)
A user can have many appointments, but each one is in one address. (unless he can multilocate himself).
So you should do:
class User
has_many :appointments
class Appointment
has_one :address
class Address
belongs_to :appointments
If you want to retrieve the addresses in which the user has appointments you have to do:
#addresses = current_user.appointments.map {|app| app.address}