I cannot figure out what I am doing wrong. I have two models:
class Product < ActiveRecord::Base
has_one :review, dependent: :destroy
accepts_nested_attributes_for :review, allow_destroy: true
end
class Review < ActiveRecord::Base
belongs_to :product
end
They have a has_one relationship. The database has the product_id column in the reviews table.
My controller is straight forward on the new (#product = Product.new) and edit action has nothing. Here are my strong params:
def product_params
params.require(:product).permit(:name, ..., review_attributes: [:id, :rating, :text, :author, :name] )
end
My form is as follows:
<%= form_for(#product, :html => {multipart: true, :class => "form-horizontal"}) do |f| %>
...
<%= f.fields_for :review do |ff| %>
<%= ff.hidden_field :author, :value => 'Yes' %>
<%= ff.label :rating, "Enter a Rating" %>
<%= ff.number_field :rating, class: "form-control input-md", min: 0, max: 5, step: 0.5 %>
<%= ff.label :name, "Title of Review" %>
<%= ff.text_field :name, class: "form-control input-md" %>
<%= ff.label :text, "Review Description" %>
<%= ff.text_area :text, class: "form-control" %>
<% end %>
<%= f.submit "Create Product", :class => 'btn btn-default btn-lg' %>
<% end %>
I cannot figure out why the nested form does not appear when I have the accepts_nested_attributes in the model, whether or not I need that accepts_nested_attributes, and why I get an error saying "unpermitted parameters: review" when I do not have the accepts_nested_attributes and submit the form. Any help is greatly appreciated.
In the controller, try to build the review object in the method that is rendering that form...
def new
#product = Product.new
#product.build_review
end
Related
I slightly modified and extended the example for building nested forms to implement the second level of nesting. Everything is displayed perfectly on the form. Data for the person is displayed at both nesting levels correctly. The corresponding JS scripts work to add and remove nested forms. All 3 are generated using scaffold.
But when I click on update, only the main form and the first nesting level (addresses) are updated. The second nesting level (nested addresses) is not updated. Although I also get parameters from the second nesting level in the controller ("name"=>"UPDATE NAME OF NESTED ADDRESS").
{
"_method"=>"patch",
"authenticity_token"=>"VZ09CR-aO2D4Wv3AwEa5PHXo-mA_--c6QPUN6f0Gb_9SJJSL2gIwzCl4G4SbzRy2t3wxJHytBWiPwysNJMrWgg",
"person"=>{
"first_name"=>"Liz",
"last_name"=>"Smith",
"addresses_attributes"=>{
"0"=>{
"_destroy"=>"false",
"kind"=>"Some kind",
"street"=>"Some street",
"nested_addresses_attributes"=>{
"0"=>{
"_destroy"=>"false",
"name"=>"UPDATE NAME OF NESTED ADDRESS",
"id"=>"1"
}
},
"id"=>"10"}}},
"commit"=>"Update Person",
"controller"=>"people",
"action"=>"update",
"id"=>"3"}
I understand that even the first nesting level is magically handled behind the scenes, but I don't understand how? And how to handle the second level as well? In general, the Create Update, Delete methods do not work for the second nesting level.
Models
class Person < ApplicationRecord
has_many :addresses, inverse_of: :person, :dependent => :destroy
has_many :nested_addresses, through: :addresses, inverse_of: :person, :dependent => :destroy
accepts_nested_attributes_for :addresses, allow_destroy: true, reject_if: :all_blank
accepts_nested_attributes_for :nested_addresses, allow_destroy: true, reject_if: :all_blank
validates :first_name, presence: true
validates :last_name, presence: true
end
class NestedAddress < ApplicationRecord
belongs_to :address
validates :name, presence: true
end
class Address < ApplicationRecord
belongs_to :person, optional: true
has_many :nested_addresses, inverse_of: :address, :dependent => :destroy
accepts_nested_attributes_for :nested_addresses, allow_destroy: true, reject_if: :all_blank
validates :kind, presence: true
validates :street, presence: true
end
Controllers
def person_params
params.require(:person).permit(:first_name, :last_name, addresses_attributes: [:id, :kind, :street, :_destroy], nested_addresses_attributes: [:id, :name, :_destroy])
end
def address_params
params.require(:address).permit(:kind, :street, :person_id, nested_addresses_attributes: [:id, :name, :_destroy])
end
def nested_address_params
params.require(:nested_address).permit(:name, :address_id)
end
people/_form.html.erb
<%= form_with model: #person, local: true do |f| %>
<%= render "shared/validation-messages", object: #person %>
<%= f.label :first_name %>
<%= f.text_field :first_name, class: 'form-control' %>
<%= f.label :last_name %>
<%= f.text_field :last_name, class: 'form-control' %>
<br>
<fieldset>
<legend>Addresses:</legend>
<%= f.fields_for :addresses do |addresses_form| %>
<%= render "address_fields", f: addresses_form %>
<% end %>
<br>
<%= link_to_add_fields "Add Addresses", f, :addresses, 'btn btn-outline-secondary'%>
</fieldset>
<br>
<%= f.submit class: 'btn btn-success' %>
<% if params[:action] === "edit" && params[:controller] === "people" %>
<%= link_to "Delete Person", person_path(#person), method: :delete, data: { confirm: "Are You Sure?" }, class: 'btn btn-outline-danger' %>
<% end %>
<% end %>
people/_address_fields.html.erb
<div class="card nested-fields">
<div class="card-header">
<div><%= f.object.id %></div>
</div>
<div class="card-body">
<%= f.hidden_field :_destroy %>
<div>
<%= f.label :kind %>
<%= f.text_field :kind, class: 'form-control' %>
</div>
<div>
<%= f.label :street %>
<%= f.text_field :street, class: 'form-control' %>
</div>
<br>
<fieldset>
<legend>Nested addresses:</legend>
<%= f.fields_for :nested_addresses do |nested_addresses_form| %>
<%= render "nested_address_fields", f: nested_addresses_form %>
<% end %>
<br>
<%= link_to_add_fields "Add Nested Addresses", f, :nested_addresses, 'btn btn-outline-secondary btn-sm' %>
</fieldset>
<br>
<div>
<%= link_to "Remove address", '#', class: "remove_fields btn btn-outline-danger btn-sm" %>
</div>
</div>
</div>
people/_nested_address_fields.html.erb
<div class="card nested-fields">
<div class="card-body">
<%= f.hidden_field :_destroy %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<br>
<div>
<%= link_to "Remove nested address", '#', class: "remove_fields btn btn-outline-danger btn-sm" %>
</div>
</div>
<br>
</div>
helpers/application_helper.rb
def link_to_add_fields(name, f, association, cl)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: 'add_fields ' + cl, data: {id: id, fields: fields.gsub("\n", "")}, role: 'button')
end
Your strong parameters permit call should reflect the actual nesting structure of parameters:
def person_params
params.require(:person).permit(
:first_name, :last_name,
addresses_attributes: [
:id, :kind, :street, :_destroy,
{ nested_addresses_attributes: [:id, :name, :_destroy] } # <= note the hash
]
)
end
In my models I have
class Blog < ActiveRecord::Base
has_many :tags, :dependent => :destroy
accepts_nested_attributes_for :tags, :allow_destroy => true
end
class Tag < ActiveRecord::Base
belongs_to :blog
validates :blog, :name, presence: true
end
Blog Controller
def new
#blog = Blog.new
#blog.tags.build
end
_form.html.erb
<%= form_for #blog, html: { multipart: true } do |f| %>
<div class="form-group">
<%= f.text_field :title, placeholder: 'Title', class: ('form-control') %>
</div><br>
<%= f.fields_for :tags do |builder| %>
<div class="form-group">
<%= builder.text_field :name, placeholder: 'Tags' %>
</div><br>
<% end %>
<div class="actions text-center">
<%= f.submit 'Submit', class: 'btn btn-primary' %>
</div>
<% end %>
Blog Controller
def create
#blog = Blog.new(blog_params)
binding.pry
end
def blog_params
params.require(:blog).permit(:title, :author, :text, :avatar, :banner, :tags_attributes => [:id, :name])
end
At my binding, it says #blog's error message is that it can't be saved because the Tag object is missing a blog_id. I have looked everywhere and I have tried to replicate my code to match other solutions but to no success.
If it helps, in my params when I submit the form I get this
"tags_attributes"=>{"0"=>{"name"=>"dsfsf"}}
that's because your #blog is not persisted in the db yet, so you won't have the id.
In your Tag model, remove :id from validation.
You should be able to just do Blog.create(blog_params)
Rails should handle the rest for you.
I have three models -> locations, products and stocks.
Stocks is a join table of locations and products.
When creating a new product i used fields_for to show locations and even though i got it to work, for some reason now it does not seem to work anymore and it gives me the above error.
<div class="input-field">
<%= f.label :product_name %>
<%= f.text_field :name, autofocus: true %>
</div>
<div class="input-field">
<%= f.label :price %>
<%= f.text_field :price, autofocus: true %>
</div>
<% if !#edit %>
<%= f.fields_for :stocks do |ff| %>
<div class="input-field margin-top x-4">
<%= ff.collection_select :location_id, Location.all, :id, :structured_location , {:prompt => "Please Select Locations for Product"}, {multiple: true} %>
<%= ff.label :locations %>
</div>
<div class="input-field">
<%= ff.label :quantity %>
<%= ff.text_field :quantity %>
</div>
<div class="input-field">
<%= ff.label :threshold_quantity %>
<%= ff.text_field :threshold_quantity %>
</div>
<% end %>
<% else %>
<%= collection_select :product, :location_ids, Location.all, :id, :structured_location , {:prompt => "Please Select Locations for Product"}, {multiple: true} %>
<% end %>
<div class="row margin-top x-4">
<div class="col s12 center-align">
<%= f.submit "#{current_page?(new_product_path) ? "Create Product" : "Update Product"}", class: "btn wave-effect pink darken-1 btn-large" %>
</div>
</div>
controller
class ProductsController < ApplicationController
helper_method :sort_column, :sort_direction
def index
#products = Product.order(sort_column + " " + sort_direction)
end
def new
#product = Product.new
#product.stocks.build
end
def create
#product = Product.new(product_params)
if #product.save!
flash[:notice] = "Successfully saved..."
redirect_to products_path
else
flash[:alert] = "Something went wrong, please check the values you entered"
redirect_to :back
end
end
private
def product_params
params.require(:product).permit(:name,:price, location_ids: [], stocks_attributes: [:id, :quantity, :threshold_quantity, location_id: []])
end
end
product model
class Product < ApplicationRecord
has_many :stocks, dependent: :destroy
has_many :locations, :through => :stocks
accepts_nested_attributes_for :stocks
end
parameters in rails console
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"l1BFhrdyB2QMO5k3+60GNiPphFfF+DXDGPbUU3V2Op2aekObjgIe13k8uoedmDIEZgIeXPZUeS/0VxQXkKa1Uw==",
"product"=>{"name"=>"Soap", "price"=>"10", "location_ids"=>["", "1",
"2"], "stocks_attributes"=>{"0"=>{"quantity"=>"100",
"threshold_quantity"=>"100"}}}, "commit"=>"Create Product"}
After hours of searching i stumbled upon this post
BigBinary
it seems that Rails 5 made belongs_to relationship IDs required by default thats why i had validations failed and i couldn't find anything about it.
simply adding optional: true in my stock model worked!
class Stock < ApplicationRecord
belongs_to :location, optional: true
belongs_to :product, optional: true
accepts_nested_attributes_for :product, allow_destroy: true
accepts_nested_attributes_for :location, allow_destroy: true
end
Im having problems with my child objects (scores) not being saved when submitting a form for a new (Qa).
Here are the models:
QA
belongs_to :user
has_many :scores, :dependent => :destory
has_many :call_components, through: :scores
accepts_nested_attributes_for :scores
Score
belongs_to :qa
has_one :call_component
Call Component Is just a title and description for a score
belongs_to :score
User
has_many :qas
has_many :scores, through: :qas
For whatever reason when submitting the post the scores are not created, the QA is however.
Form
<%= form_for [#qa], role: "form" do |f| %>
<%= f.label :call_id %>
<%= f.number_field :call_id, :autofocus => true, class: "form-control monospaced-control", placeholder: "Call Id", required: "" %>
... more fields
<% CallComponent.all.each do |comp| %> <!-- Usually is an array of about 5 components, so there 5 scores -->
<h4><b><%= comp.title.to_s.capitalize %></b></h4>
<p><%= comp.description.to_s.capitalize %></p>
<%= f.fields_for :scores, #qa.scores do |builder| %>
<%= builder.label :score, "Score" %>
<%= builder.number_field :score, :autofocus => true, class: "form-control monospaced-control", placeholder: "Score", required: ""%>
<%= builder.label :comments, "Comments" %><br />
<%= builder.text_area :comments, :autofocus => true, class: "form-control monospaced-control", placeholder: "Score", required: ""%>
<%= builder.hidden_field :call_component_id, :value => comp.id %>
<% end %>
<% end %>
Here is the QA New method
def new
#qa = Qa.new
#qa.scores.build
# Tried it this way too
##score = Score.new
##score.build_qa # then in the view linking the form like #score.qa, this didnt work.
end
And here is the QA Create Method
def create
#qa = Qa.new(qa_params)
#qa.final_score = #qa.scores.sum(:score).to_i
#qa.user_id = current_user.id
if #qa.save
redirect_to qas_path, notice: "New qa published!"
else
flash[:alert] = "Qa not published!"
render :new
end
end
def qa_params
params.require(:qa).permit(:call_id,...,:scores_attributes)
end
Any thoughts on how to fix this would be awesome. Thanks for your time.
try this..
def qa_params
params.require(:qa).permit(:agent_user_id, :call_id, :call_date, :case_number, :completion_date, scores_attributes: [:score,:comments,:call_component_id] )
end
as you need to specify which attributes of the nested set are permitted.
Nested attributes of join model won't be saved. relation id's seems to be missing. The following error messages are added when the fields get validated:
* Assigned projects user can't be blank
* Assigned projects project can't be blank
The submitted params look like this ( <%= debug(params) %> )
--- !map:ActionController::Parameters
utf8: "\xE2\x9C\x93"
authenticity_token: HrF1NHrKNTdMMFwOvbYFjhJE1ltlKbuz2nsfBYYBswg=
project: !map:ActionController::Parameters
name: Peter Pan
assigned_projects_attributes: !map:ActiveSupport::HashWithIndifferentAccess
"0": !map:ActiveSupport::HashWithIndifferentAccess
position: Group Leader
currency: " Neverland Dollars"
commit: Add Project
action: create
controller: projects
I have 3 models, as followed:
class User < ActiveRecord::Base
has_many :assigned_projects
has_many :projects, :through => :assigned_projects
has_many :created_projects, :class_name => "Project", :foreign_key => :creator_id
end
class AssignedProject < ActiveRecord::Base
belongs_to :user, class_name: "User"
belongs_to :project, class_name: "Project"
attr_accessible :project_id, :user_id, :position, :project_attributes
accepts_nested_attributes_for :project
validates :user_id, presence: true
validates :project_id, presence: true
validates :position, presence: true
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :assigned_projects
has_many :users, :through => :assigned_projects
belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
attr_accessible :name, :creator_id, :currency :assigned_projects_attributes
accepts_nested_attributes_for :assigned_projects
validates :name, presence: true, length: { minimum: 3, maximum: 100 }
validates :currency, presence: true, length: { minimum: 1, maximum: 5 }
validates :creator_id, presence: true
end
So each User can create a Project. He can add any User to the Project through the join model.
Each Project belongs to a User resp. Creator and has_many user through assigned_projects
I want to give each user of a project a "position", which should be saved in the join model: assigned_project :position
the Project controller looks like that:
class ProjectsController < ApplicationController
def new
#project = Project.new
#project.assigned_projects.build(user_id: current_user)
end
def create
#project = current_user.assigned_projects.build.build_project(params[:project])
#project.creator = current_user
if #project.save
redirect_to current_user
else
render 'new'
end
end
end
and the project/new.html.erb form looks like that:
<%= form_for( #project ) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.label :currency %>
<%= f.text_field :currency %>
<%= f.submit "Add Project", class: "" %>
<% end %>
UPDATE: current controller & view
def create
#project = Project.new(params[:project])
if #project.save
redirect_to current_user
else
render 'new'
end
end
<%= form_for( #project ) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.hidden_field :creator_id, value: current_user.id %>
<%= f.fields_for :assigned_projects, #project.assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.label :currency %>
<%= f.text_field :currency %>
<%= f.submit "Add Project", class: "" %>
<% end %>
View:
I think you need to pass the objects collection #project.assigned_projects you built in the new action to the fields_for:
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :assigned_projects, #project.assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.label :currency %>
<%= f.text_field :currency %>
<%= f.submit "Add Project", class: "" %>
Controller:
If i understood the first line in the create action i think you try to re-build the project assigned_projects in-order to stamp the creator attribute !!
Instead you could remove this line and put a hidden field in the nested for, something like:
<%= ff.hidden_field :creator, current_user %>
So your controller looks pretty basic now:
def create
#project = Project.new(params[:prject])
if #project.save #nested objects will be saved automatically
redirect_to current_user
else
render 'new'
end
end
What does the build_project method do?
I think in your controller you should just have build, not build.build_project, so like this:
#project = current_user.assigned_projects.build(params[:project])
of if build_project is a method used to create the params then
#project = current_user.assigned_projects.build(project_params)
in the case of rails 4 you would need something like this:
def project_params
params.require(:project).permit(:your_params)
end
In the case of rails 3 I think you need to add
attr_accessible :param1, :param2
in the project model for the parameters you want to set.
Problem is solved by removing the validations for project_id and user_id in the join table "AssignedProject"
So the join Model looks like that:
# Join Model AssignedProject
class AssignedProject < ActiveRecord::Base
belongs_to :user#, class_name: "User"
belongs_to :project#, class_name: "Project"
attr_accessible :project_id, :user_id, :position, :project, :project_attributes
accepts_nested_attributes_for :project
validates :position, presence: { message: " can't be blank." }
end
The New and Create methods look like that:
# Projects Controller
class ProjectsController < ApplicationController
def new
#project = Project.new
#project.assigned_projects.build(user_id: current_user)
end
def create
#project = Project.new(params[:project])
if #project.save
#project.assigned_projects.create(user_id: current_user)
redirect_to current_user
else
render 'new'
end
end
end
And the form in the view for the new method looks like that:
<%= form_for( #project ) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.hidden_field :creator_id, value: current_user.id %>
<%= f.fields_for :assigned_projects, #project.assigned_projects do |ff| %>
<%= ff.hidden_field :project_id, value: #project %>
<%= ff.hidden_field :user_id, value: current_user.id %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.label :currency %>
<%= f.text_field :currency %>
<%= f.submit "Add Project", class: "" %>
<% end %>