Has_Many Through Associations and Nested Attributes - ruby-on-rails

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

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

Unpermitted params error while creating records of grand-child records in grand-parent forms

I have three models Book_Room, Invoice, and Bill. Invoice belongs to BookRoom, and has many bills. the error is unpermitted params error for :invoice is thrown when i try to create Book Room after ive clearly asked to accept nested params
class Invoice < ApplicationRecord
belongs_to :book_room
has_many :bills
accepts_nested_attributes_for :bills
end
And BookRoom is meant to have only one invoice, but I used has_many because the fields_for form doesn't render when I use has_one.
class BookRoom < ApplicationRecord
belongs_to :customer
has_and_belongs_to_many :rooms
has_many :invoices
accepts_nested_attributes_for :invoices
end
Here is my create action for invoice controller:
def create
#booking = BookRoom.find(params[:book_room_id])
#invoice = #booking.invoice.new(invoice_params)
if #invoice.save
redirect_to #invoice, notice: 'Invoice was successfully created.'
else
render :new
end
end
And here is my form, which is meant to create invoices and bills.
<%= f.fields_for :invoice do |i| %>
<%= i.fields_for :bill do |b| %>
<%= b.label :price %>
<%= b.number_field :price %>
<%= b.hidden_field :type, value: :deposit %>
<% end %>
<% end %>
And finally, my book_room controller:
def create
#book_room = #customer.book_rooms.new(book_room_params)
if #book_room.save
redirect_to #book_room, notice: 'Book room was successfully created.'
else
render :new
end
end
def book_room_params
params.require(:book_room).permit(:customer_id, :start_date, :end_date, :room_ids=>[], :invoices_attributes => [ :invoice_id, :bills_attributes => [:price, :type] ])
end
When I try to create a book room with bill records, it throws an error of unpermitted params invoice. If there is a way to get the form to render on has_one relationship, I will appreciate it.
Try switching to has_one :invoice association - fields_for can work with it, but you need to build association first with #book_room.build_invoice in BookRoomController#new controller action.
Then you can fix book_room_params - change invoices_attributes key to singular invoice_attributes.

Create 3rd level child in a single form - Rails 5

I'm building a simple top-to-bottom Workout Routine app on ROR. I'm able to create a Workout Day (parent) and an Exercise (child) on the same form. But I can't seem to save the Weighted Set (grandchild) when I submit the form. The interesting thing is that since the Exercise is saved, I can go to that exercise edit page, add a Weighted Set, and the Weighted Set will show up in the Workout Day show page. I think it has to do with the Weighted Set not being associated with the Exercise at the time of creation. How cam I tie wll three models together? I know I'm close!
I have the whole app on github. I the link isn't working, try this URL https://github.com/j-acosta/routine/tree/association
Models
class WorkoutDay < ApplicationRecord
has_many :exercises, dependent: :destroy
has_many :weighted_sets, through: :exercises
accepts_nested_attributes_for :exercises
accepts_nested_attributes_for :weighted_sets
end
class Exercise < ApplicationRecord
belongs_to :workout_day, optional: true
has_many :weighted_sets, dependent: :destroy
accepts_nested_attributes_for :weighted_sets
end
class WeightedSet < ApplicationRecord
belongs_to :exercise, optional: true
end
Workout Day Controller
class WorkoutDaysController < ApplicationController
before_action :set_workout_day, only: [:show, :edit, :update, :destroy]
...
# GET /workout_days/new
def new
#workout_day = WorkoutDay.new
# has_many association .build method => #parent.child.build
#workout_day.exercises.build
# has_many :through association .build method => #parent.through_child.build
# #workout_day.weighted_sets.build
#workout_day.weighted_sets.build
end
...
# POST /workout_days
# POST /workout_days.json
def create
#workout_day = WorkoutDay.new(workout_day_params)
respond_to do |format|
if #workout_day.save
format.html { redirect_to #workout_day, notice: 'Workout day was successfully created.' }
format.json { render :show, status: :created, location: #workout_day }
else
format.html { render :new }
format.json { render json: #workout_day.errors, status: :unprocessable_entity }
end
end
end
...
private
# Use callbacks to share common setup or constraints between actions.
def set_workout_day
#workout_day = WorkoutDay.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def workout_day_params
params.require(:workout_day).permit(:title, exercises_attributes: [:title, :_destroy, weighted_sets_attributes: [:id, :weight, :repetition]])
end
end
New Workout Day form
<%= form_for #workout_day do |workout_day_form| %>
<% if workout_day.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(workout_day.errors.count, "error") %> prohibited this workout_day from being saved:</h2>
<ul>
<% workout_day.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= workout_day_form.label :title, 'Workout Day Name' %>
<%= workout_day_form.text_field :title %>
</div>
exercise_field will go here
<div>
<%= workout_day_form.fields_for :exercises do |exercise_field| %>
<%= exercise_field.label :title, 'Exercise' %>
<%= exercise_field.text_field :title %>
<% end %>
</div>
weighted_set_fields will go here
<div>
<%= workout_day_form.fields_for :weighted_sets do |set| %>
<%= render 'exercises/weighted_set_fields', f: set %>
<% end %>
</div>
<div>
<%= workout_day_form.submit %>
</div>
<% end %>
The culprit is the workout_day_params. In the form you have the fields of weighted_sets nested under the workout_day. But in the workout_day_params, you have weighted_sets_attributes under exercises_attributes which is the reason for your problem. Changing it to below should solve the issue.
def workout_day_params
params.require(:workout_day).permit(:title, exercises_attributes: [:title, :_destroy], weighted_sets_attributes: [:id, :weight, :repetition])
end
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrMany‌​Reflection
This due to wrong associations. You should consider tweaking your associations like below
class WorkoutDay < ApplicationRecord
has_many :weighted_sets, dependent: :destroy
has_many :exercises, through: :weighted_sets
accepts_nested_attributes_for :exercises
accepts_nested_attributes_for :weighted_sets
end
class Exercise < ApplicationRecord
has_many :weighted_sets, dependent: :destroy
has_many :workout_days, through: :weighted_sets
end
class WeightedSet < ApplicationRecord
belongs_to :exercise, optional: true
belongs_to :workout_day, optional: true
end

Nested Attributes and Unpermitted parameters

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.

Building a nested form with a many-to-many relationship in Rails 4

I have a model (User) that has_many of another model (Profession) - and this is supposed to be represented by one (or multiple) select menu in a form.
I cannot get my head around why the select menu doesn't get rendered? Am I constructing the select helper in the wrong way? Or is something else wrong in the view or the controller? The name attribute of the User is showing up alright in the form.
The models:
class User < ActiveRecord::Base
has_many :occupations, dependent: :destroy
has_many :professions, through: :occupations
accepts_nested_attributes_for :occupations
end
class Profession < ActiveRecord::Base
has_many :occupations, dependent: :destroy
has_many :users, through: :occupations
end
class Occupation < ActiveRecord::Base
belongs_to :user
belongs_to :profession
end
The controller:
def edit
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user, notice: 'User was successfully created.'
else
render action: 'new'
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:name, :email, ocuppations_attributes: [:id, :user_id, :profession_id])
end
The view (compressed):
<%= form_for(#user) do |f| %>
<%= f.text_field :name %>
<%= f.fields_for :occupations do |builder| %>
<%= builder.select :profession_id, Profession.all.collect {|x| [x.title, x.id]} %>
<% end %>
<% end %>
Shouldn't that be a collection select?
<%= builder.collection_select(:profession_id, Profession.all, :id, :title) %>

Resources