Nested Attributes and Unpermitted parameters - ruby-on-rails

I have a form that is passing nested attributes it works for the 'Child' but the 'books' don't appear to be saving.
I have 3 models. Each Child can have 2 books:
class Child < ActiveRecord::Base
has_many :hires
has_many :books, through: :hires
end
class Hire < ActiveRecord::Base
belongs_to :book
belongs_to :child
accepts_nested_attributes_for :book
accepts_nested_attributes_for :child
end
class Book < ActiveRecord::Base
has_many :hires
has_many :children, through: :hires
belongs_to :genres
end
The controller looks like this:
class HiresController < ApplicationController
...
def new
#hire = Hire.new
2.times { #hire.build_book }
end
def create
#hire = Hire.new(hire_params)
respond_to do |format|
if #hire.save
format.html { redirect_to #hire, notice: 'Hire was successfully created.' }
format.json { render :show, status: :created, location: #hire }
else
format.html { render :new }
format.json { render json: #hire.errors, status: :unprocessable_entity }
end
end
end
...
private
# Use callbacks to share common setup or constraints between actions.
def set_hire
#hire = Hire.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def hire_params
params.require(:hire).permit(:child_id, books_attributes: [ :id, :book_id, :_destroy])
end
end
The hash that is being submitted looks like this:
Processing by HiresController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+2xxx==", "hire"=>{"child_id"=>"2", "books"=>{"book_id"=>"5"}}, "commit"=>"Create Hire"}
Unpermitted parameter: books
I can't work out why i'm getting the unpermitted params error? Any advice?
* EDIT - included the view as I now suspect this might play a part *
<%= form_for(#hire) do |f| %>
<%= f.select(:child_id, Child.all.collect {|a| [a.nickname, a.id]}) -%>
<%= f.label :child_id %><br>
<%= f.fields_for :books do |books_form| %>
<%= books_form.label :book_id %><br>
<%= books_form.select(:book_id, Book.all.collect {|a| [a.Title, a.id]}) -%>
<%= books_form.label :book_id %><br>
<%= books_form.select(:book_id, Book.all.collect {|a| [a.Title, a.id]}) -%>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

You need to use the following:
#app/controllers/hires_controller.rb
class HiresController < ApplicationController
def new
#hire = Hire.new
2.times do
#hire.build_book
end
end
end
The rest of it looks okay.
The problem you have is pretty typical; when you use f.fields_for, you have to build the associative objects, otherwise the fields_for won't create the correct field names.
The field names you're looking for will be [books_attributes][0][book_id] etc.
Since your form is passing all the attributes you'd expect (without the _attributes suffix), I can only surmise that your building of the associated objects is incorrect:
2.times { #hire.books.build }
... should be ...
2.times { #hire.build_book }
When you have singular associations, you have to build the associative object singularly: build_object, whilst with plural, you can use the plural.build call.

Related

Rails Unpermitted Parameter

I have a form that has some prebuilt tags that the user can select on a post. These tags are set up with a has_many through: relationship. Everything seems to be working but when I save (the post does save) there is an Unpermitted parameter: :tags from the controller's save method.
Tag model:
class Tag < ApplicationRecord
has_many :post_tags
has_many :posts, through: :post_tags
end
PostTag model:
class PostTag < ApplicationRecord
belongs_to :tag
belongs_to :post
end
Post model:
class Post < ApplicationRecord
...
has_many :post_tags
has_many :tags, through: :post_tags
Post controller methods:
def update
# saves tags
save_tags(#post, params[:post][:tags])
# updates params (not sure if this is required but I thought that updating the tags might be causing problems for the save.
params[:post][:tags] = #post.tags
if #post.update(post_params)
...
end
end
...
private
def post_params
params.require(:post).permit(:name, :notes, tags: [])
end
def save_tags(post, tags)
tags.each do |tag|
# tag.to_i, is because the form loads tags as just the tag id.
post.post_tags.build(tag: Tag.find_by(id: tag.to_i))
end
end
View (tags are checkboxes displayed as buttons):
<%= form.label :tags %>
<div class="box">
<% #tags.each do |tag| %>
<div class="check-btn">
<label>
<%= check_box_tag('dinner[tags][]', tag.id) %><span> <%= tag.name %></span>
</label>
</div>
<% end %>
</div>
Again this saves, and works fine, but I'd like to get rid of the Unpermitted parameter that is thrown in the console.
Your whole solution is creative but extremely redundant. Instead use the collection helpers:
<%= form_with(model: #post) |f| %>
<%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name %>
<% end %>
tags_ids= is a special setter created by has_many :tags, through: :post_tags (they are created for all has_many and HABTM assocations). It takes an array of ids and will automatically create/delete join rows for you.
All you have to do in your controller is whitelist post[tag_ids] as an array:
class PostsController < ApplicationController
# ...
def create
#post = Post.new(post_params)
if #post.save
redirect_to #post
else
render :new
end
end
def update
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
private
# ...
def post_params
params.require(:post)
.permit(:name, :notes, tag_ids: [])
end
end

Updating nested fields_for and collection_select in Rails

Really stumped here. I'm trying to get my form to update the categories on the edit form. Problem is, everything in my form updates when submitted except the categories. It ends up inserting the new category chosen like it's going through the create method instead of the update method, so when the edit form is shown again after submission, it keeps doubling the fields of categories. 1, then 2, then 4, then 8, etc. after each submission. Please please help anyone. Appreciate it.
views/blog_posts/edit.html.erb
<div class="col-md-6 col-md-offset-3 blog-submit">
<%= form_for #blog_post do |b| %>
<%= b.label :title %>
<%= b.text_field :title %><br>
<%= b.fields_for :categorizations do |cat| %>
<%= cat.label :category_name, "Category 1" %>
<%= cat.collection_select(:category_id, Category.all, :id, :category_name, {blank: "Select Category"}) %>
<%= link_to "Add Categories", new_category_path %>
<br>
<% end %>
<%= b.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
Blog_post controller:
class BlogPostsController < ApplicationController
protect_from_forgery
before_action :authenticate_admin!, only: [:new, :edit]
def index
#blog_posts = BlogPost.order(id: :desc)
end
def new
#blog_post = BlogPost.new
#blog_post.categorizations.build.build_category
#blog_post.categories.build
end
def edit
#blog_post = BlogPost.find(params[:id])
end
def create
#blog_post = BlogPost.new(blog_post_params)
respond_to do |format|
if #blog_post.save
format.html { redirect_to #blog_post, notice: 'Your blog was submitted successfully' }
format.json { render :show, status: :created, location: #blog_post }
else
format.html { render :new }
format.json { render json: #blog_post.errors, status: :unprocessable_entity }
end
end
puts #blog_post.errors.inspect
end
def update
#blog_post = BlogPost.find(params[:id])
if #blog_post.update_attributes(blog_post_params)
render 'show'
else
render 'edit'
end
end
def show
#blog_post = BlogPost.find(params[:id])
end
private
def blog_post_params
params.require(:blog_post).permit(:title, :content, :posted_by, :comments, :blog_pic, {categorizations_attributes: [:category_id, :category_name]})
end
end
models:
class BlogPost < ApplicationRecord
has_many :categorizations
has_many :categories, :through => :categorizations
accepts_nested_attributes_for :categorizations
has_many :comments
mount_uploader :blog_pic, BlogPicUploader
end
class Categorization < ApplicationRecord
belongs_to :blog_post
belongs_to :category
end
class Category < ApplicationRecord
has_many :categorizations
has_many :blog_posts, :through => :categorizations
end
Add id in blog_post_params as shown below. This will work for you.
def blog_post_params
params.require(:blog_post).permit(:title, :content, :posted_by, :comments, :blog_pic, {categorizations_attributes: [:id,:category_id, :category_name]})
end

Rails 4: checkbox and has_many through

This example has been taken from Rails 4 Form: has_many through: checkboxes
models:
#models/user.rb
class User < ActiveRecord::Base
has_many :animals, through: :animal_users
has_many :animal_users
end
#models/animal.rb
class Animal < ActiveRecord::Base
has_many :users, through: :animal_users
has_many :animal_users
end
#models/animal_user.rb
class AnimalUser < ActiveRecord::Base
belongs_to :animal
belongs_to :user
end
The user form:
#views/users/_form.html.erb
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
# Checkbox part of the form that now works!
<div>
<% Animal.all.each do |animal| %>
<%= check_box_tag "user[animal_ids][]", animal.id, f.object.animals.include?(animal) %>
<%= animal.animal_name %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Strong params within the users_controller.rb
def user_params
params.require(:user).permit(:name, animal_ids: [])
end
I followed this example and could not save the join table. I have two question here
What type should be animal_ids, string or integer?
How to save the form?
Currently I am saving it like this
def create
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'user was successfully created.' }
format.json { render json: #user, status: :created, location: #user}
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
And this only create user but not the join table. How can I do this?
#user.save is not passing in the nested attributes (animal_ids)
You'll need to pass the params like this:
#user = User.new(user_params)
And in your User model (user.rb) you need to add something like:
accepts_nested_attributes_for :animals
accepts_nested_attributes_for :animal_users

Has_Many Through Associations and Nested Attributes

I'm trying to create a view allowing me to create and edit values of my joining table directly. This model is called 'hires'. I need to be able to create multiple rows in my joining table for when a child hires up to 2 books. I'm having some trouble and I suspect it's down to my associations...
I have 3 models. Each Child can have 2 books:
class Child < ActiveRecord::Base
has_many :hires
has_many :books, through: :hires
end
class Hire < ActiveRecord::Base
belongs_to :book
belongs_to :child
accepts_nested_attributes_for :book
accepts_nested_attributes_for :child
end
class Book < ActiveRecord::Base
has_many :hires
has_many :children, through: :hires
belongs_to :genres
end
The controller looks like this:
class HiresController < ApplicationController
...
def new
#hire = Hire.new
2.times do
#hire.build_book
end
end
def create
#hire = Hire.new(hire_params)
respond_to do |format|
if #hire.save
format.html { redirect_to #hire, notice: 'Hire was successfully created.' }
format.json { render :show, status: :created, location: #hire }
else
format.html { render :new }
format.json { render json: #hire.errors, status: :unprocessable_entity }
end
end
end
...
private
# Use callbacks to share common setup or constraints between actions.
def set_hire
#hire = Hire.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def hire_params
params.require(:hire).permit(:child_id, book_attributes: [ :id, :book_id, :_destroy])
end
end
The view likes this:
<%= form_for(#hire) do |f| %>
<%= f.label :child_id %><br>
<%= f.select(:child_id, Child.all.collect {|a| [a.nickname, a.id]}) -%>
<%= f.fields_for :books do |books_form| %>
<%= books_form.label :book_id %><br>
<%= books_form.select(:book_id, Book.all.collect {|a| [a.Title, a.id]}) %>
<%# books_form.text_field :book_id #%>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The problem is, the hash is not submitting books_attributes as you'd expect, it's just submitting 'books':
Processing by HiresController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"xx", "hire"=>{"child_id"=>"1", "books"=>{"book_id"=>"1"}}, "commit"=>"Create Hire"}
Unpermitted parameter: books
I suspect this is because my associations for the Hire model are:
belongs_to :book
accepts_nested_attributes_for :book
which means I can't build the attributes correctly but I'm not sure how to solve this. Any help would be great, am I solving this problem badly?
Try changing books_attributes to book_attributes in strong paramters for hire_param.
def hire_params
params.require(:hire).permit(:child_id, book_attributes: [ :id, :book_id, :_destroy])
end

How to make a form for three models with many-to-many relations

What's the best way to create a form for models with many-to-many relations?
In detail:
I have 3 models: User, Task, TaskAssignment:
User Model
class User < ActiveRecord::Base
has_many :task_assignments
has_many :tasks, through: :task_assignments
end
Task Model
class Task < ActiveRecord::Base
has_many :task_assignments
has_many :users, through: :task_assignments
end
TaskAssignment Model (Join Table)
I can't use has_and_belongs_to_many, because I need additional fields in the TaskAssignment Model.
class TaskAssignment < ActiveRecord::Base
belongs_to :task
belongs_to :user
end
By creating a new task, there should be the possibility to assign multiple users to a task, so I made this form view:
Task Edit Form View
<%= form_for(#task) do |f| %>
<div class="field">
<%= f.label :note %><br>
<%= f.text_field :note %>
</div>
<select name="task[users]" size="5" multiple>
<% #users.each do |user| %>
<option value="<%= user.id %>"><%= user.email %></option>
<% end %>
</select>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Now, I wonder how to go on in my controller:
Task Controller
def create
#task = Task.new(task_params)
respond_to do |format|
if #task.save
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render action: 'show', status: :created, location: #task }
else
format.html { render action: 'new' }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
I think I have to do something like that:
#task = Task.new(task_params)
users = ???
#task.users << users
Is that the way n:m data should be saved or are there any other magic rails functions?
How do i get the data from the select-box? I tried to receive them by setting the name of the select-box to name="task[users]", but the variable task_params contains only the note-field
There's a helper called accepts_nested_attributes_for that allows a parent object to create and update its nested objects. In this case, you want Task to be able to create and update TaskAssignment
First, allow Task to accept attributes for its kids and to assign attributes to them.
class Task < ActiveRecord::Base
has_many :task_assignments
has_many :users, through: :task_assignments
accepts_nested_attributes_for :task_assignments
end
That should point you in the right direction. Creating the form will look something like explained in fields_for for one-to-many.
You don't need to save your object in any special way. Just remember to allow :user_ids => [] in your task_params.
Also, your life might be a bit easier with the form helper collection_select.
<%= f.collection_select :user_ids, #users, :id, :email, {}, { :multiple => true, :size => 5 } %>

Resources