I am a Rails newbie trying to accept nested attributes for model Address through model Vault 'new' form, but I am getting an undefined method `build' for nil:NilClass ERROR
I have two Models, a Vault Model here:
class Vault < ActiveRecord::Base
has_one :address, dependent: :destroy
accepts_nested_attributes_for :address, allow_destroy: true
end
and I also have an Address Model here:
class Address < ActiveRecord::Base
belongs_to :vault
end
This is my Vault_controller 'new' methodd:
def new
#vault = Vault.new
#vault.address.build
end
This is part of my _form.html.erb Vault partial, where I am trying to capture the info for the Address model:
<%= f.fields_for :address do |builder| %>
<%= builder.label :stnumber, "St. Number" %></br>
<%= builder.text_field :stnumber %>
<% end %>
#vault.rb
class Vault < ActiveRecord::Base
has_one :address, dependent: :destroy
#Other codes goes here
end
#address.rb
class Address < ActiveRecord::Base
belongs_to :vault
#other code goes here.
end
Build Address(depending on association)
v = Vault.new
address = v.address.build
# this one will work only for has_many association.
address = v.build_address
#this one will work for your has_one association
Please check this link.
Related
I was wondering if someone could help me out with an application that has some ecommerce characteristics.
Context: Via the application a bike shop chain ('chains') can rent out
bikes ('bikes'),
by picking out a bike type such as mountainbike, city bike etc. ('bike_types) and
bike options, such as helmets etc. ('bike_options')
which are dependent on the individual bike store ('bike_stores')
this rental of the bikes & options will all be captured in an order ('orders')
the relationship between orders and bikes is many-to-many, therefore I created a table to bridge this ('order_bikes')
Final notes:
Before the rental process, the chain owner first created his/her (i) bike_stores, (ii) bike_types, (iii) bikes and (iv) bike_options, this part of the application is working. Therefore, he/she only needs to select bike_types/bikes/options out of the existing inventory previously created.
I limit the scope of the question by leaving out the bike_options, this was mainly to provide some context in order to understand the db schema build up.
Error message: Unpermitted parameter: :bike_id
Code:
models
class Order < ApplicationRecord
belongs_to :bike_store
has_many :bike_types, through: :bike_store
has_many :order_bikes, inverse_of: :order, dependent: :destroy
accepts_nested_attributes_for :order_bikes, allow_destroy: true
end
class OrderBike < ApplicationRecord
belongs_to :bike
belongs_to :order
accepts_nested_attributes_for :bike
end
class Bike < ApplicationRecord
belongs_to :bike_type
validates :name, presence: true
has_many :order_bikes
has_many :orders, through: :order_bikes
end
class BikeType < ApplicationRecord
belongs_to :bike_store
has_many :bikes, dependent: :destroy
accepts_nested_attributes_for :bikes, allow_destroy: true
has_many :bike_options, dependent: :destroy
accepts_nested_attributes_for :bike_options, allow_destroy: true
validates :name, :bike_count, presence: true
end
class BikeStore < ApplicationRecord
has_many :bike_types, dependent: :destroy
has_many :orders, dependent: :destroy
end
Order controller
class OrdersController < ApplicationController
def new
#bike_store = BikeStore.find(params[:bike_store_id])
#order = Order.new
#order.order_bikes.build
#bike_type_list = #bike_store.bike_types
end
def create
#order = Order.new(order_params)
#bike_store = BikeStore.find(params[:bike_store_id])
#order.bike_store = #bike_store
#order.save
redirect_to root_path
end
private
def order_params
params.require(:order).permit(:arrival, :departure,
order_bikes_attributes: [:id, :bike_quantity, :_destroy,
bikes_attributes: [:id, :name,
bike_types_attributes: [:id, :name]]])
end
end
view
<%= simple_form_for [#bike_store, #order] do |f|%>
<%= f.simple_fields_for :order_bikes do |order_bike| %>
<%= order_bike.input :bike_quantity %>
<%= order_bike.association :bike %>
<% end %>
<%= f.input :arrival %>
<%= f.input :departure %>
<%= f.submit %>
<% end %>
If you check coed from simple form here, you will see what actually method association does.
def association(association, options = {}, &block)
# ... simple form code here ...
attribute = build_association_attribute(reflection, association, options)
input(attribute, options.merge(reflection: reflection))
end
We are interested in build_association_attribute method call. here
def build_association_attribute(reflection, association, options)
case reflection.macro
when :belongs_to
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
# ... the rest of code ...
end
end
Your order bike model has belongs_to :bike association. So when you call order_bike.association :bike it builds :bike_id attribute in your form. If you check params hash that comes to your controller, I believe you'll see that attribute coming from your view.
I added bike_id to permitted parameters. I hope it will fix your problem..
def order_params
params.require(:order).permit(:arrival, :departure,
order_bikes_attributes: [:id, :bike_id, :bike_quantity, :_destroy,
bikes_attributes: [:id, :name,
bike_types_attributes: [:id, :name]]])
end
I have a common many to many relationship, these are the models:
class Employee < ApplicationRecord
has_many :related_professions
has_many :professions, through: :related_professions
accepts_nested_attributes_for :related_professions
end
class RelatedProfession < ApplicationRecord
belongs_to :employee
belongs_to :profession
accepts_nested_attributes_for :profession
end
class Profession < ApplicationRecord
has_many :related_professions
has_many :employees ,through: :related_professions
end
I also have a form for saving Employees. In this form I would like to render all the Professions in a multiple select for the user to choose as needed. I want that when the user submits the form, the IDs of all the selected professions be saved in the RelatedProfession pivot table (which just have three columns: id, employee_id, profession_id). This is the part of my form for the select:
<div class="field">
<%= form.label :professions %>
<%= form.fields_for :related_professions do |rp| %>
<%= rp.collection_select :profession_id, Profession.all, :id, :name, {}, {multiple: true} %>
<% end %>
</div>
And this is the part in my EmployeeController that allows the parameters:
# Never trust parameters from the scary internet, only allow the white list through.
def employee_params
params.require(:employee).permit(:name, related_professions_attributes: [:id, profession_id: [:id]])
end
The first problem is that the form does not load the Professions if the Employee does not have any assigned. I had to manually add one to the DB and then it would populate the select.
Second problem is that when I try to update the Employee (and also the RelatedProfession pivot table) by selecting a different Profession, it won't work, and I get this error:
Related professions profession must exist
I know there must be something wrong in the permit parameters and form that is not building the select correctly.
I appreciate the help. Thanks in advance.
You no need nested attributes to created has_many through relations,
You can just pass it as array of ids.
class Employee < ApplicationRecord
has_many :related_professions
has_many :professions, through: :related_professions
end
class RelatedProfession < ApplicationRecord
belongs_to :employee
belongs_to :profession
end
class Profession < ApplicationRecord
has_many :related_professions
has_many :employees ,through: :related_professions
end
In form also just select ids of Professions.
<div class="field">
<%= form.label :professions %>
<%= rp.collection_select :profession_ids, Profession.all, :id, :name, {}, {multiple: true} %>
</div>
change strong params to allow profession_ids as array.
def employee_params
params.require(:employee).permit(:name, profession_ids: [])
end
Hope this solves your problem.
I am having issue with saving a has_many through relation with nested attributes. Due to complexity and requirment in the application the relation is as follows
Table structure,
agreements:
id
agreement_rooms:
id
agreement_id
room_id
details:
id
agreement_rooms_id
For more clarification, agreement_rooms table is related to many other models which will be having agreement_rooms_id in them.
Rails Associations,
class Agreement < ActiveRecord::Base
has_many :details,:through => :agreement_rooms
accepts_nested_attributes_for :details
end
class AgreementRoom < ActiveRecord::Base
has_many :details
end
class Detail < ActiveRecord::Base
belongs_to :agreement_room
accepts_nested_attributes_for :agreement_room
end
When i try to create a agreements record with details hash in it, i get the following error,
Agreement.last.details.create()
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection: Cannot modify association 'agreement#details' because the source reflection class 'Detail' is associated to 'agreementRoom' via :has_many.
I am not sure how to get this nested attributed working with has_many through relation for the above example. Please help out to figure the issue.
Thanks in advance.
#app/models/aggreement.rb
class Agreement < ActiveRecord::Base
has_many :agreement_rooms
accepts_nested_attributes_for :agreement_rooms
end
#app/models/agreement_room.rb
class AgreementRoom < ActiveRecord::Base
belongs_to :agreement
belongs_to :room
has_many :details
accepts_nested_attributes_for :details
end
#app/models/room.rb
class Room < ActiveRecord::Base
has_many :agreement_rooms
has_many :agreements, through: :agreement_rooms
end
#app/models/detail.rb
class Detail < ActiveRecord::Base
belongs_to :agreement_room
end
--
#app/controllers/agreements_controller.rb
class AgreementsController < ApplicationController
def new
#agreement = Agreement.new
#agreement.agreement_rooms.build.details.build
end
def create
#agreement = Agreement.new agreement_params
#agreement.save
end
private
def agreement_params
params.require(:agreement).permit(:agreement, :param, agreement_rooms_attributes: [ details_attributes: [:x] ])
end
end
#app/views/agreements/new.html.erb
<%= form_for #agreement do |f| %>
<%= f.fields_for :agreement_rooms do |ar| %>
<%= ar.fields_for :details do |d| %>
<%= d.text_field :x %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
you need to define both associations:
class Agreement < ActiveRecord::Base
has_and_belongs_to_many :agreement_rooms # or has_many if you prefer
has_many :details,:through => :agreement_rooms
accepts_nested_attributes_for :details
end
check the docs
As i said before the model association design we has was not proper and due to poor maintenance it has to be in the same way, atleast for now. So i had to write a dirty patch to fix it.
Its simply skipping nested attributes for this specific model alone, so it can be saved separately by passing the master record id to this record.
As its a dirty solution i'm not marking it as the answer. Just added it hoping someone can have a solution if needed.
Thanks for the help
I have set a polymorphic association and added a nested form in the view. Im trying to create the main record and the association at the same time. The main record gets created but the association won't.
Here are the two models in question :
class UnRegistered < ActiveRecord::Base
has_one :vehicle, as: :detailable, dependent: :destroy
belongs_to :dealer
class Vehicle < ActiveRecord::Base
belongs_to :purchase_details, polymorphic: true
belongs_to :brand
belongs_to :model
belongs_to :color
belongs_to :customer
Here's the form definitions :
<%= form_for(#un_registered, url: panel_un_registereds_path, remote: true) do |f| %>
<%= f.fields_for :vehicle do |f_vehicle| %>
Here's a sample params set I get :
{"utf8"=>"✓", "un_registered"=>{"vehicle"=>{"brand_id"=>"", "model_id"=>"", "year"=>"", "engine_number"=>"gdfg", "chassis_number"=>"", "color"=>"", "options"=>""}, "original_price"=>"", "insurance"=>"", "freight"=>"", "tt"=>"", "tt_date"=>"", "duty"=>"", "clearance_fee"=>"", "other_expenses"=>"", "dealer_id"=>"", "landing_date"=>"", "loading_date"=>""}, "controller"=>"panel/un_registereds", "action"=>"create"}
Here's the controller actions :
def create
#un_registered = UnRegistered.create(un_registered_params)
end
def un_registered_params
params.require(:un_registered).permit(:original_price, :insurance, :freight, :tt, :tt_date, :duty, :clearance_fee, :other_expenses, :loading_date, :landing_date, :dealer_id, vehicle_attributes: [:id, :brand_id, :model_id, :engine_number, :chassis_number, :color_id, :year, :options, :selling_price, :customer_id, :purchase_date, :_destroy])
end
Full form code :
https://gist.github.com/THPubs/9665e0e5594e15fcc76a
New method :
def new
#un_registered = UnRegistered.new
end
Your form is fine. You just need to add below changes.
In your un_registered.rb model
class UnRegistered < ActiveRecord::Base
has_one :vehicle, as: :detailable, dependent: :destroy
belongs_to :dealer
accepts_nested_attributes_for :vehicle #this one
end
And in your controller,
def new
#un_registered = UnRegistered.new
#un_registered.build_vehicle #this one
end
I can't seem to get a has_one relationship with accepts_nested_attributes_for to work. This is my first foray into nested attributes, so it might be incorrect. Help!
Models:
class Case < ActiveRecord::Base
has_one :logged_event, class_name: 'Event', dependent: :destroy
accepts_nested_attributes_for :logged_event
end
class Event < ActiveRecord::Base
belongs_to :case
belongs_to :user
validates :case, presence: true
validates :user, presence: true
end
Controller:
class CasesController < ApplicationController
load_and_authorize_resource
def new
#case.build_logged_event(user: current_user)
end
def create
if #case.save
flash[:notice] = 'Case was successfully logged.'
redirect_to cases_path
else
render 'cases/new'
end
end
end
Form:
<h1>New Case</h1>
<%= error_messages_for :case %>
<%= form_for(#case) do |form| %>
<p>
<%= form.label(:study) %>:
<%= form.select(:study, Case.options_for(:study), include_blank: true) %>
</p>
<%= form.fields_for(:logged_event) do |logged_event_form| %>
<%= logged_event_form.label(:created_at, 'Date Case was opened:') %>
<%= logged_event_form.select(:created_at, [['Today', Date.today], ['Yesterday', Date.yesterday]]) %>
<% end %>
<%= form.submit('Log Case') %> or <%= link_to('cancel', cases_path) %>
<% end %>
It's saying that the logged event is not getting a user id or a case id, so both validations on the event class are failing. It looks like:
There were problems with the following fields:
Logged event case can't be blank
Logged event user can't be blank
I don't get that, because in the new action I have already built the object. I also tried form.fields_for(:logged_event, #case.logged_event) but that didn't work either.
It's because new is not being called when you submit the form. Only the create action is being ran. So #case is populated only with the for submission data; no user or case is assigned to it since it's not in the form.
You'd need to assign the user and case properties of #case before trying to save (and running validations)
Turns out you shouldn't validate on both sides. I was validating that a case had an event and an event had a case. By removing the validates :case, presence: true like from event, everything worked as it should have.
You don't need to remove your validation -- you just need to add inverse_of to the association declaration to tell Rails to manage the back reference for you.
Your models should look like this:
class Case < ActiveRecord::Base
has_one :logged_event, class_name: 'Event', dependent: :destroy, inverse_of: :case
accepts_nested_attributes_for :logged_event
end
class Event < ActiveRecord::Base
belongs_to :case, inverse_of: :logged_event
belongs_to :user, inverse_of: :logged_event # or inverse_of: :event -- you didn't provide your User model
validates :case, presence: true
validates :user, presence: true
end