Accessing error messages for nested attribute field - ruby-on-rails

I have a form created using the simple_form gem which populates 2 models using nested attributes. I want to check if there are any errors and display a new block. However, I'm not sure how to correctly access the error message for the location attribute of the Booking model.
class Booking < ActiveRecord::Base
belongs_to :customer
attr_accessible :date_wanted, :location
end
and
class Customer < ActiveRecord::Base
has_many :bookings
accepts_nested_attributes_for :bookings
attr_accessible :name, :phone, :bookings_attributes
validates_presence_of :name, :phone
end
Form view:
simple_form_for #customer, {:html => { :class => "form-horizontal" }} do |f|
= f.input :name
= f.input :phone
= f.simple_fields_for :bookings do |b|
= b.input :date
= b.input :location
- if #customer.errors[:appointments_attributes][:location]
# insert code if any validation errors for the date field were found
= f.button :submit

b is an instance of form builder, holding booking, so you can try:
# ...
if b.object.errors[:location]
# ...

Related

Rails - Set additional attribute on has_many through record

Using Rails 6.0.3.3 and ruby '2.6.0'.
I have these 3 models connected via a has_many through relation.
class Recipe < ApplicationRecord
has_many :layers
has_many :glazes, through: :layers
end
class Glaze < ApplicationRecord
has_many :layers
has_many :recipes, through: :layers
end
class Layer < ApplicationRecord
belongs_to :recipe
belongs_to :glaze
# there is a column named "coat_type" here that I would like to set
end
Everything is working great for creating a new Recipe and it automagically creating its related Layer record. But now I would like to also set a coat_type attribute on the Layer record when it's created, but I can't seem to figure out how I could do something like that.
The Form View Partial
= form_with model: recipe, local: true do |form|
%p
= form.label :name
%br
= form.text_field :name
%p
= form.label :description
%br
= form.text_area :description
.recipe-glaze
= form.collection_select(:glaze_id, Glaze.all, :id, :name, { prompt: "Select Glaze" }, { name: 'recipe[glaze_ids][]' })
.recipe-glaze
= form.collection_select(:glaze_id, Glaze.all, :id, :name, { prompt: "Select Glaze" }, { name: 'recipe[glaze_ids][]' })
%p
= form.submit
The Controller Create Action (and strong params acton)
def create
#recipe = Recipe.new(recipe_params)
if #recipe.save
redirect_to #recipe
else
render 'new'
end
end
private
def recipe_params
params.require(:recipe).permit(:name, :description, glaze_ids: [])
end
Ideally, I would be able to add another select box in the form and the user would use that to set the coat_type of the Layer record that will be created. But I can't figure out how I could pass that into the controller and have it know what to do with that value.
Is this something that is possible, or am I approaching this incorrectly?
So I actually ended up stumbling upon the "cocoon" gem thanks to this comment. By following the setup instructions for "cocoon", I was able to tweak my code to do what I needed.
My Recipe model changed to this ::
class Recipe < ApplicationRecord
has_many :layers, inverse_of: :recipe
has_many :glazes, through: :layers
accepts_nested_attributes_for :layers, reject_if: :all_blank, allow_destroy: true
end
My controller's strong params action changed to this ::
private
def recipe_params
params.require(:recipe).permit(:name, :description, layers_attributes: [:id, :glaze_id, :coat_type, :_destroy])
end
My form view partial changed to ::
- if recipe.errors.any?
= render partial: 'errors', locals: { recipe: recipe }
%p
= form.label :name
%br
= form.text_field :name
%p
= form.label :description
%br
= form.text_area :description
%h3 Layers
#layers
= form.fields_for :layers do |layer|
= render 'layer_fields', f: layer
.links
= link_to_add_association 'add layer', form, :layers
%p
= form.submit
and the "layer_fields" partial referenced in the form looks like this ::
.nested-fields
.field
= f.label :glaze_id
%br
= f.collection_select(:glaze_id, Glaze.all, :id, :name, { prompt: "Select Glaze" } )
.field
= f.label :coat_type
%br
= f.text_field :coat_type
= link_to_remove_association 'remove layer', f
Making those changes using the "Cocoon" gem, I was able to accomplish what I needed. Hopefully this helps someone else in the future.

ActiveRecord::AssociationTypeMismatch in RoastsController#create

This is a new error to me, and struggling to resolve it. It also states: Roaster(#70130698993440) expected, got "1" which is an instance of String(#70130675908140)
It's highlighting my create method in my Roasts Controller:
def create
#roast = Roast.new(roast_params)
The scenario is that I'm trying to create a triple nested form. for three models Roasts Countries and Regions where roasts has many countries and countries has many regions.
I'm assuming there is something wrong with the roast params, but I can see what it is. I have added the associations there for the nested models
def roast_params
params.require(:roast).permit(:roaster, :name, :bestfor, :beans, :roast, :tastingnotes, :notes, :slug, :avatar, :countries_attributes => [:country_name, :regions_attributes => [:region_name]])
end
my form
<div class="form-group">
<%= form.fields_for :countries do |countries_form| %>
<%= countries_form.label :country %>
<%= countries_form.text_field :name, class: "form-control" %>
</div>
<div class="form-group">
<%= form.fields_for :regions do |regions_form| %>
<%= regions_form.label :region %>
<%= regions_form.text_field :region_name, class: "form-control" %>
<% end %>
<% end %>
</div>
Roast Controller
...
def new
#roast = Roast.new
#roast.countries.build.regions.build
end
...
roast model
class Roast < ApplicationRecord
has_many :tastings
has_many :countries
has_many :notes, through: :tastings
has_many :comments, as: :commentable
belongs_to :roaster
accepts_nested_attributes_for :countries
country model
class Country < ApplicationRecord
has_many :regions, inverse_of: :country
accepts_nested_attributes_for :regions
belongs_to :roasts
region model
class Region < ApplicationRecord
belongs_to :country
I've nested the regions params in the country params, is that correct? I also saw on SO other issues with suggestions for setting config.cache_classes to true in development.rb but that didn't help here.
Update
So looking at this further, I believe it's not related to the nested forms, but rather a collection_select I'm using.
<%= form.label :roaster, class: 'control-label' %>
<%= form.collection_select(:roaster, Roaster.order(:roaster_name).all, :id, :roaster_name, prompt: true, class: "form-control") %>
So this select is pulling the roaster_name from a model called Roaster.
My params now look like the below:
params.require(:roast).permit(:roaster_name, :roaster, :name, :bestfor, :beans, :roast, :tastingnotes, :notes, :slug, :avatar, :countries_attributes => [:country_id, :country_name, :regions_attributes => [:region_id, :region_name]])
And looking at the console when submitting the form, it seems that just the :id of Roaster is getting passed, rather than the value of :roaster_name.
{"utf8"=>"✓",
"authenticity_token"=>"EG+zty85IiVsgipm1pjSAEZ7M66ELWefLq8Znux+cf89sSnVXxielRr1IaSS9+cJvdQD8g1D4+v2KqtKEwh6gw==",
"roast"=>{"roaster"=>"1", "name"=>"Espress", "countries_attributes"=>{"0"=>{"country_name"=>"UK"}}, "regions"=>{"region_name"=>"Highlands"}, "bestfor"=>"", "roast"=>"", "tastingnotes"=>""},
"commit"=>"Create Roast"}
Can't work this out
ActiveRecord::AssociationTypeMismatch is raised when an association-setter (Roast#roaster= in this case) is called with a value that is not an instance of the expected class. Roaster was expected, got String.
The issue seems to be with passing roaster in as a param, which is "1" (String) in your example. I'm guessing this is actually an ID of a Roaster, the form code in the question does not show it.
Perhaps you meant to permit and pass a roaster_id param?
def roast_params
params.require(:roast).permit(:roaster_id, # ...
end

Nested Form has_many

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

another mass assignment issue with nested attributes

I have campaigns and messages. Campaigns have many messages and messages belongs to campaigns. I setup nested attributes for the models and in my view and i'm trying to create a message associated with the model. Here's the code:
class Campaign < ActiveRecord::Base
attr_accessible :message_id, :name, :group_id, :user_id, :messages_attributes
has_many :messages
belongs_to :group
belongs_to :user
accepts_nested_attributes_for :messages
end
class Message < ActiveRecord::Base
attr_accessible :body, :sent, :sent_at
belongs_to :user
belongs_to :campaign
has_many :responses
end
and the form:
= form_for #campaign, :html => {:class => 'form-horizontal'} do |f|
...removed error output code...
%legend
Enter the campaign information
.field
.control-group
%label.control-label
Campaign Name
.controls
= f.text_field :name
= f.collection_select(:group_id, current_user.groups.all, :id, :name, :include_blank => true)
= f.fields_for :message do |m|
= m.text_area :body, :rows => 3
.form-actions= f.submit "#{params[:action] == 'new' ? 'Create New Campaign' : 'Save Campaign'}", :class => 'btn btn-success'
I know it's probably something really simple but I keep getting a mass assignment issue with message. Here's the error:
ActiveModel::MassAssignmentSecurity::Error (Can't mass-assign protected attributes: message):
app/controllers/campaigns_controller.rb:18:in `update'
and finally the params that get created from the form:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"(removed)", "campaign"=>{"name"=>"Weekly Poker", "group_id"=>"1", "message"=>{"body"=>"s"}}, "commit"=>"Save Campaign", "id"=>"1"}
Because it's a has_many association it should be in plural form:
= f.fields_for :messages do |m|
Also, in the controller you will need:
def new
#campaign = Campaign.new
#campaign.messages.build
end

nested attributes with polymorphic has_one model

I am using accepts_nested_attributes_for with the has_one polymorphic model in rails 2.3.5
Following are the models and its associations:
class Address < ActiveRecord::Base
attr_accessible :city, :address1, :address2
belongs_to :addressable, :polymorphic => true
validates_presence_of :address1, :address2, :city
end
class Vendor < ActiveRecord::Base
attr_accessible :name, :address_attributes
has_one :address, :as => :addressable, :dependent => :destroy
accepts_nested_attributes_for :address
end
This is the view:
- form_for #vendor do |f|
= f.error_messages
%p
= f.label :name
%br
= f.text_field :name
- f.fields_for :address_attributes do |address|
= render "shared/address_fields", :f => address
%p
= f.submit "Create"
This is the partial shared/address_fields.html.haml
%p
= f.label :city
%br= f.text_field :city
%span City/Town name like Dharan, Butwal, Kathmandu, ..
%p
= f.label :address1
%br= f.text_field :address1
%span City Street name like Lazimpat, New Road, ..
%p
= f.label :address2
%br= f.text_field :address2
%span Tole, Marg, Chowk name like Pokhrel Tole, Shanti Marg, Pako, ..
And this is the controller:
class VendorsController < ApplicationController
def new
#vendor = Vendor.new
end
def create
#vendor = Vendor.new(params[:vendor])
if #vendor.save
flash[:notice] = "Vendor created successfully!"
redirect_to #vendor
else
render :action => 'new'
end
end
end
The problem is when I fill in all the fileds, the record gets save on both tables as expected.
But when I just the name and city or address1 filed, the validation works, error message shown, but the value I put in the city or address1, is not persisted or not displayed inside the address form fields?
This is the same case with edit action too.
Though the record is saved, the address doesn't show up on the edit form. Only the name of the Client model is shown.
Actually, when I look at the log, the address model SQL is not queried even at all.
Why f.fields_for :address_attributes?
Shouldn't it be:
- f.fields_for :address do |address_fields|
= render "shared/address_fields", :f => address_fields
It's not loading the values on edit and errors because you never load address_attributes with the values from #vendor.address.

Resources