I am having trouble trying to update nested models in my form. I don't get any errors and but the attributes don't get updated.
I have the following model:
class Trip < ActiveRecord::Base
has_many :segments
accepts_nested_attributes_for :segments, allow_destroy: true
end
class Segment < ActiveRecord::Base
belongs_to :start_location, class_name: 'Location'
belongs_to :end_location, class_name: 'Location'
belongs_to :trip
validates_presence_of :date, :start_location, :end_location
end
class Location < ActiveRecord::Base
has_many :segments
end
And have this code in the _form.html.erb:
<%= form_for #trip do |f| %>
...
<%= f.fields_for :segments do |builder| %>
<%= render 'segment_fields', f: builder %>
<% end %>
...
<% end %>
And this in the partial _segment_fields.html.erb:
<%= f.collection_select :start_location_id, Location.order(:name), :id, :name %> -
<%= f.collection_select :end_location_id, Location.order(:name), :id, :name %> <br>
<%= f.date_field :date %>
In my controller I also permited the assigment of :segments_attributes
def trip_params
params.require(:trip).permit(:name, :start_date, :end_date, :segments_attributes)
end
Does anybody know what I am lacking or doing wrong?
When you are creating a new record you don't need its id to be permitted as it's not been created but when you want to update your record you need to pass id to the permitted attributes, else it will work with create but not when you want to update your record, so you need to do:
def trip_params
params.require(:trip).permit(:id, :name, :start_date, :end_date, segments_attributes: [:id,:start_location_id,:end_location_id, :date])
end
Related
i'm building a web application with Rails 5.2.0 about recipes and I have a doubt about the create method of the controller.
This are my models:
class Recipe < ApplicationRecord
belongs_to :user
has_many :quantities
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true
end
class Quantity < ApplicationRecord
belongs_to :recipe
belongs_to :ingredient
end
class Ingredient < ApplicationRecord
has_many :quantities
has_many :recipes, through: :quantities
end
And here the view to create new recipes:
<%= form_for(#recipe) do |f| %>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
<%= f.label :servings, "Servings" %>
<%= f.number_field :servings %>
<%= f.fields_for :quantities do |quantity| %>
<%= f.hidden_field :_destroy, class: "hidden-field-to-destroy" %>
<%= f.label :ingredient_id, "Ingredient Name" %>
<%= f.text_field :ingredient_id%>
<%= f.label :amount, "Amount" %>
<%= f.number_field :amount %>
<%= f.label :unit, "Unit" %>
<%= f.select(:unit, ["kg","g","l","ml"], {include_blank: true}) %>
<% end %>
<%= f.submit 'Add new recipe' %>
<% end %>
I can add new ingredients dynamically with jquery and also delete them in the same form.
The update method of the controller works perfectly but the create method does not work:
class RecipesController < ApplicationController
def create
#recipe = current_user.recipes.build(recipe_params)
if #recipe.save
flash[:success] = "New recipe created correctly."
redirect_to #recipe
else
render 'new'
end
end
def update
#recipe = Recipe.find(params[:id])
if #recipe.update_attributes(recipe_params)
flash[:success] = "The recipe has been updated correctly."
redirect_to #recipe
else
render 'edit'
end
end
private
def recipe_params
params.require(:recipe).permit( :name, :servings, quantities_attributes: [:ingredient_id, :amount, :unit,:_destroy, :id, :recipe_id])
end
end
I'm trying to do #recipe = current_user.recipes.build(recipe_params) but I get the following error in te view:
Quantities recipe can't be blank
I think this occurs because when trying to create the relation, it is necessary to indicate the recipe_id, but the recipe has not yet been created and the id can not be indicated.
Could you please tell me someone what would be the correct way to create the recipe first and then be able to add the ingredients through Quantity in the create method of the recipe controller?
As per the message shared the qunatity_recipes cannot be blank and you haven't specified any condition to manage this.
Current
class Recipe < ApplicationRecord
belongs_to :user
has_many :quantities
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true
end
Update the accepts nested attributes to allow_nil for Recipe class
class Recipe < ApplicationRecord
belongs_to :user
has_many :quantities
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true, allow_nil: true
end
I have a form on a model called "Patient", which has_one "Role" via polymorphic association. And the "Role" has_one "User". And I need to modify the "User" field "first_name" via a form...
class Patient < ApplicationRecord
has_one :role, :as => :roleable, dependent: :destroy
accepts_nested_attributes_for :role
end
class Role < ApplicationRecord
belongs_to :user
accepts_nested_attributes_for :user
belongs_to :roleable, polymorphic: true, optional: true
end
class User < ApplicationRecord
has_many :roles
end
class PatientsController < ApplicationController
def show
#patient = Patient.find(params[:id])
# This works just fine, so I know the data is there
puts(#patient.role.user.first_name)
end
end
How do I make a form that includes this field? This is what I have so far that isn't working:
<%= form_for(:patient, url: edit_patient_path(params[:id])) do |f| %>
<%= f.fields_for :role do |r| %>
<%= r.fields_for :user do |u| %>
<%= u.text_field :first_name, value: u.first_name, class: "value", disabled: false %>
<% end %>
<% end %>
<% end %>
But unfortunately this is not working:
undefined method `first_name' for #<ActionView::Helpers::FormBuilder:0x007f82d30c4ad8>
First change your form to <%= form_for(#patient) %> so that it wraps the #patient object.
Use .object to get the object wrapped by the form builder.
<%= u.text_field :first_name, value: u.object.first_name, class: "value", disabled: false %>
But you don't need to explicitly assign the value in the first place and the disabled attribute defaults to false:
<%= u.text_field :first_name, class: "value" %>
I have a rails4 app. At the moment my collection select only works if I select only one option. Below is my working code. I only have product form. Industry model is populated with seeds.rb. IndustryProduct is only use to connect the other 2 models.
I'd like to know what I have to change in the code to be able to choose more.
I saw some working examples with multiple: true option like (https://www.youtube.com/watch?v=ZNrNGTe2Zqk at 10:20) but in this case the UI is kinda ugly + couldn't pull it off with any of the sample codes. Is there an other solution like having more boxes with one option chosen instead of one box with multiple options?
models:
class Product < ActiveRecord::Base
belongs_to :user
has_many :industry_products
has_many :industries, through: :industry_products
has_many :product_features
accepts_nested_attributes_for :industry_products, allow_destroy: true
accepts_nested_attributes_for :product_features
validates_associated :industry_products
validates_associated :product_features
end
class Industry < ActiveRecord::Base
has_many :industry_products
has_many :products, through: :industry_products
accepts_nested_attributes_for :industry_products
end
class IndustryProduct < ActiveRecord::Base
belongs_to :product
belongs_to :industry
end
_form.html.erb
<%= form_for #product do |f| %>
<%= render 'layouts/error_messages', object: f.object %>
......
<%= f.fields_for :industry_products do |p| %>
<%= p.collection_select :industry_id, Industry.all, :id, :name %>
<% end %>
<%= f.fields_for :product_features do |p| %>
<%= p.text_field :feature, placeholder: "add a feature", class: "form-control" %>
<% end %>
<%= f.submit class: "btn btn-primary" %>
<% end %>
products controller
def new
#product = Product.new
#product.industry_products.build
#product.product_features.build
end
def create
#product = current_user.products.new(product_params)
if #product.save
redirect_to #product
else
render action: :new
end
end
......
def product_params
params.require(:product).permit(....., industry_products_attributes: [:id, :industry_id, :_destroy], industries_attributes: [:id, :name], product_features_attributes: [:feature])
end
Firstly, you could fix your first collection select by using it to set the industry_ids for the #product:
<%= form_for #product do |f| %>
<%= f.collection_select :industry_ids, Industry.all, :id, :name %>
<% end %>
This will allow you to set the collection_singular_ids method, which exists for all has_many associations.
You'd have to back it up in the params method:
#app/controllers/products_controller.rb
....
def product_params
params.require(:product).permit(.... industry_ids: [])
end
A lot more succinct than using nested attributes.
To get that "multiple" selection, you'll want to use the following:
<%= f.collection_select :industry_ids, Industry.all, :id, :name, {}, { multiple: true } %>
Tested & working
--
You may also want to look at collection_check_boxes:
<%= f.collection_check_boxes :industry_ids, Industry.all, :id, :name %>
I'm trying to get my User to save to a Rate. I was able to get the Location to be saved to the Rate by removing the presence validation but after it's created it doesn't have the current user. How would I do this for my nested form?
User.rb
attr_accessible :email, :password
has_many :locations
has_many :rates
Location.rb
attr_accessible :name, :rates_attributes
belongs_to :user
has_many :rates
accepts_nested_attributes_for :rates, :reject_if => :all_blank
# Not sure if :all_blank works anyways as it -
# still saves even when theirs no user_id, lol
Rate.rb
attr_accessible :amount, :location_id
belongs_to :location
belongs_to :user
validates_presence_of :amount
# Couldn't use these validations
# validates_presence_of :user_id
# validates_presence_of :location_id
LocationsController
def new
#location = Location.new
#location.rates.build
end
def create
#location = current_user.locations.build(params[:location])
if #location.save.....
end
locations/new.html.erb
<%= nested_form_for #location do |f| %>
<%= f.label :name, "Name *" %>
<%= f.text_field :name %>
<%= f.link_to_add "Add Rate", :rates %>
<%= f.fields_for :rates do |r| %>
<%= r.text_field :amount %>
<%= r.link_to_remove "Remove" %>
<% end %>
<%= f.submit "Add Location" %>
<% end %>
There is a great railscast on this topic; episodes 196 and 197. Even better, Ryan wrote a gem https://github.com/ryanb/nested_form.
The gem is super easy to implement. If set up correctly the nested form grabs the parent object id on create automatically.
I don't notice anything in the code you have posted that looks wrong...what does your nested form look like in the view?
I have User, which can be one of three types: Admin, Student, Teacher. Everyone has other attributes. I am trying polymorphic association one-to-one like this:
User
class User < ActiveRecord::Base
belongs_to :identity, :polymorphic => true
accepts_nested_attributes_for :identity, :allow_destroy => true
attr_accessible :email, :login, :remember_token,
:password_confirmation, :password, :role
end
Student
class Student < ActiveRecord::Base
attr_accessible :field
has_one :user, :as => :identity
end
Controller
def new
#user = User.new
end
def create
#user = User.new(params[:user]) # It fails here.
#user.identita.build
...
end
View
<%= form_for(#user) do |f| %>
<%= f.label :login %><br />
<%= f.text_field :login %>
<%= f.fields_for [:identity, Student.new] do |i| %>
<%= i.label :field %><br />
<%= i.textfield_select :field %>
<% end %>
<% end %>
When I submit this view (more complex, but this is the core), it sends hash like this:
{"utf8"=>"✓",
"authenticity_token"=>"...",
"user"=>{"login"=>"...",
"student"=> {"field"=>"..."}
}
So it fails on marked line in controller with:
ActiveModel::MassAssignmentSecurity::Error in UsersController#create
Can't mass-assign protected attributes: student
What am I doing wrong? Something like :as=>"student" or twisting the realationship?
Firstly, fix:
<%= f.fields_for [:identity, Student.new] do |i| %>
to:
<%= f.fields_for :identity, Student.new do |i| %>
Secondly, you are trying to use accepts_nested_attributes_for on a belongs_to relationship. This is not supported behavior AFAIK. Perhaps try moving that to the Student model:
class Student < ActiveRecord::Base
attr_accessible :field
has_one :user, :as => :identity
accepts_nested_attributes_for :user, :allow_destroy => true
end
and make the view like this:
<%= form_for(Student.new) do |i| %>
<%= i.fields_for :user, #user do |f| %>
<%= f.label :login %><br />
<%= f.text_field :login %>
<% end %>
<%= i.label :field %><br />
<%= i.textfield_select :field %>
<% end %>
From documentation of attr_accessible
attr_accessible will only set attributes in this list, to assign to
the rest of attributes you can use direct writer methods.
So once you've used attr_accessible, another attributes automatically will become a protected ones.