Rails 4 - Cannot modify association because of :has_many association - ruby-on-rails

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}

Related

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 5 ActiveRecord::RecordInvalid (Validation failed), but I have no validations

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.

Rails - Convert hours and minutes to seconds then assign value to key

I would like to combine both values :hours and :minutes and convert them to to_i in seconds. Next is to assign this value (which should be in seconds) to the :time_duration which is a column in the cars db before it creates a new service. The :time_duration is in a hidden_field because there's no reason to render this data in the view.
views
This is my _car_fields.html.erb which is a nested partial inside a view template called, _form.html.erb .
_car_fields.html.erb
<div class="nested-fields">
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<%= f.label :hours %>
<%= f.select :hours, '0'..'8' %>
<%= f.label :minutes %>
<%= f.select :minutes, options_for_select( (0..45).step(15), selected: f.object.minutes )%><br>
<%= f.label :price %><br>
<%= f.text_field :price, :value => (number_with_precision(f.object.price, :precision => 2) || 0) %> <br>
<%= f.label :details %><br>
<%= f.text_area :details %></div>
<%= link_to_remove_association "Remove Car", f, class: 'btn btn-default' %>
<%= f.hidden_field :time_duration, value: %>
<br>
<hr>
</div>
_form.html.erb
<%= simple_form_for #service do |f| %>
<div class="field">
<%= f.label "Select service category" %>
<br>
<%= collection_select(:service, :service_menu_id, ServiceMenu.all, :id, :name, {:prompt => true }) %>
<%= f.fields_for :cars do |task| %>
<%= render 'car_fields', :f => task %>
<% end %>
</div>
<div class="links">
<%= link_to_add_association 'Add New Car', f, :cars, class: 'btn btn-default' %>
</div><br>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
controller
services_controller
def new
#service = current_tech.services.build
end
def create
#service = current_tech.services.build(service_params)
respond_to do |format|
if #service.save
format.html { redirect_to #service, notice: 'Service was successfully created.' }
format.json { render :show, status: :created, location: #service }
else
format.html { render :new }
format.json { render json: #service.errors, status: :unprocessable_entity }
end
end
end
private
def service_params
params.require(:service).permit(:name, :service_menu_id, cars_attributes: [:tech_id, :name, :hours, :minutes, :price, :details, :_destroy])
end
models
service.rb
class Service < ActiveRecord::Base
belongs_to :tech
belongs_to :service_menu
has_many :cars, dependent: :destroy
accepts_nested_attributes_for :cars, :reject_if => :all_blank, :allow_destroy => true
end
car.rb
class Car< ActiveRecord::Base
belongs_to :service
belongs_to :tech
has_many :appointments
end
First, you can remove the hidden time_duration field from the form, since it is not needed.
Then, you'll create a before_save method for your car model:
car.rb
class Car < ActiveRecord::Base
belongs_to :service
belongs_to :tech
has_many :appointments
before_save :generate_time_duration
def generate_time_duration
self[:time_duration] = hours.hours.to_i + minutes.minutes.to_i
end
end
What this does: Before the car object is saved, it will run the generate_time_duration method. What this method does is it simply sums the hours.to_i and minutes.to_i and assigns it to the car's time_duration attribute.
Update old DB records
Since you're adding this functionality in your application AFTER records have already been created, here is a quick way to update all of your current records:
In your command line, open a rails console by running the command rails c (or rails console)
In the console, run this command: Car.all.each { |c| c.save! }
This is a quick, one-time fix that will loop through all Car records, save them, and subsequently update their time_duration fields.

How to populate join table for a HABTM relationship Rails 3, postgresql

So after searching long and hard for information that can help us, we've found it difficult to find an answer we can work with.
Our problem is that we have two tables joined through a HABTM relationship called Schools and Organizations. A school is created first, and then an Organization takes the list of schools, allows the user to select one, and then populates a third table OrganizationsSchools with both the school_id and the organization_id.
Models for the three are as follows:
Schools model:
class School < ActiveRecord::Base
has_and_belongs_to_many :organizations, :join_table => 'organizations_schools'
attr_accessible :name
validates :name, :presence => true
end
Organizations model:
class Organization < ActiveRecord::Base
has_many :materials
has_many :users
has_and_belongs_to_many :causes
has_and_belongs_to_many :schools, :join_table => 'organizations_schools'
attr_accessible :name, :unlogged_books_num, :id
validates :name, :presence => true
end
The form for Organizations:
<%= form_for(#organization) do |f| %>
<% if #organization.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#organization.errors.count, "error") %> prohibited this organization from being saved:</h2>
<ul>
<% #organization.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% #schools = School.all %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :unlogged_books_num %><br />
<%= f.number_field :unlogged_books_num %>
</div>
<div class="field">
<%= f.label 'School' %><br />
<% school_id = nil %>
<%= collection_select(nil, school_id, #schools, :id, :name) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And finally, the create function in our Organizations controller
class OrganizationsController < ApplicationController
.
.
.
def create
#organization = Organization.new(params[:organization])
org_id = #organization.id
school_id = #organization.school_id
#org_school = OrganizationsSchool.create(:organization_id => org_id, :school_id => school_id)
respond_to do |format|
if #organization.save
format.html { redirect_to #organization, notice: 'Organization was successfully created.' }
format.json { render json: #organization, status: :created, location: #organization }
else
format.html { render action: "new" }
format.json { render json: #organization.errors, status: :unprocessable_entity }
end
end
end
end
All other functions in our controller are restfully created.
Please also know that I'm not the greatest with databases, and though I'm familiar with rails, I'm not by any stretch of the word proficient at using it.
In a has_and_belongs_to_many, there wouldn't be a school_id field for Organization. It sounds like you actually want:
class Organization < ActiveRecord::Base
belongs_to :school
...
end
class School < ActiveRecord::Base
has_many :organizations
end
if you really want HABTM, then you could write:
#organization.schools << School.find school_id
EDIT:
Obviously your controller code would need additional changes if you swtiched to a has_many relationship

find_or_create_by_name with has_many :through

Artists have many Events. Events have many Artists. The join between these two models is called Performances.
I'm trying to associate Artists with Events on the Event add/edit page. I would like to be able to add an Artist only if it doesn't exist, and create the join (performance) regardless. An Artist should be associated with an Event only once.
It was suggested that I use find_or_create_by_name instead of accepts_nested_attributes_for.
I'm following the Railscasts #102 instructions for Auto-Complete which say to use virtual attributes. I haven't even gotten to the auto-complete part, just trying to get find_or_create_by_name working.
I'm getting "undefined method `artist_name' for #" on the Event edit and new pages. In the Railscast, Ryan gets an undefined method before he adds the methods to the model. But I have the method in the Model.
No idea what to do.
event.rb
validates_presence_of :name, :location
validates_uniqueness_of :name
validates_associated :performances
has_many :performances, :dependent => :delete_all
has_many :artists, :through => :performances
#accepts_nested_attributes_for :artists, :reject_if => proc {|a| a['name'].blank?}, :allow_destroy => true
def artist_name
artist.name if artist
end
def artist_name=(name)
self.artist = Artist.find_by_name(name) unless name.blank?
end
artist.rb
validates_presence_of :name
has_many :mixes
has_many :performances, :dependent => :delete_all
has_many :events, :through => :performances
perfomance.rb
belongs_to :artist
belongs_to :event
events_controller.rb
def create
#event = Event.new(params[:event])
respond_to do |format|
if #event.save
flash[:notice] = 'Event was successfully created.'
format.html { redirect_to(admin_events_url) }
format.xml { render :xml => #event, :status => :created, :location => #event }
else
format.html { render :action => "new" }
format.xml { render :xml => #event.errors, :status => :unprocessable_entity }
end
end
end
_form.html.erb
<% form_for([:admin,#event]) do |f| %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :location %><br/>
<%= f.text_field :location %>
</p>
<p>
<%= f.label :date %><br />
<%= f.date_select :date %>
</p>
<p>
<%= f.label :description %><br />
<%= f.text_area :description %>
</p>
<% f.fields_for :artists do |builder| %>
<%= render 'artist_fields', :f => builder %>
<% end %>
<p><%= link_to_add_fields "Add Artist", f, :artists %></p>
<p>
<%= f.submit 'Submit' %> <%= link_to 'Cancel', admin_events_path %>
</p>
<% end %>
_artist_fields.html.erb
<p class="fields">
<%= f.label :artist_name, "Artist"%><br/>
<%= f.text_field :artist_name %>
<%= link_to_remove_fields "remove", f %>
</p>
Personally I would go back to accepts_nested_attributes_for, ryan bates method there was in the days before nested attributes.
In your controller do something like:
def new
#event = Event.find params[:id]
#artist = #event.artists.build
def edit
#event = Event.find params[:event_id]
#artist = #event.artists.find params[:user_id]
While in the view
...
<% f.fields_for :artists, #artist do |builder| %>
...

Resources