Rails 4: checkbox and has_many through - ruby-on-rails

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

Related

what is causing this AssociationTypeMismatch in my controllers?

I have two controllers - ItemsController and TradesController. I'm building a #trade inside the ItemsController #show action, which is sent to the TradesController #create action with a form.
class ItemsController < ApplicationController
def show
#item = Item.friendly.find(params[:id])
#trade = current_user.requested_trades.build
#approved_trades = #item.trades
respond_to do |format|
format.html
format.json { render :json => #items.to_json(:methods => [:image_url]) }
end
end
class TradesController < ApplicationController
def create
#trade = current_user.requested_trades.build(trade_params)
respond_to do |format|
if #trade.save
format.html { redirect_to #trade, notice: "Your request for trade has been submitted. You will be notified once it is approved or denied." }
format.json { render :index, status: :created, location: #trade }
else
format.html { redirect_to #trade, notice: "Pick another amount" }
end
end
end
private
def trade_params
params.require(:trade).permit(:trade_requester, :trade_recipient, :wanted_item, :collateral_item, :shares)
end
end
And then here's my Trade model
class Trade < ActiveRecord::Base
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item"
belongs_to :collateral_item, class_name: "Item"
end
Here's the form in my Item's show view:
<%= form_for(#trade) do |f| %>
<%= f.hidden_field :wanted_item, value: #item.id %>
<div class="field">
<%= f.text_field :shares, placeholder: "Pick a number between 1 and #{#item.shares}" %>
<%= f.submit "Trade", class: "button minty-button wide-button" %>
</div>
<% end %>
The above code for the ItemsController posts to the TradesController create action, but I'm getting an error that says ActiveRecord::AssociationTypeMismatch in TradesController#createItem(#70095717466760) expected, got String(#70095657672800)
Why is that expecting an Item? It seems that if #trade creation results in error, then it should redirect to #trade.
The quick solution is to change your hidden field from :wanted_item to :wanted_item_id:
<%= form_for(#trade) do |f| %>
<%= f.hidden_field :wanted_item_id, value: #item.id %>
<div class="field">
<%= f.text_field :shares, placeholder: "Pick a number between 1 and #{#item.shares}" %>
<%= f.submit "Trade", class: "button minty-button wide-button" %>
</div>
<% end %>
Also, make sure your trade_params method permits wanted_item_id:
def trade_params
params.require(:trade).permit(:trade_requester, :trade_recipient, :wanted_item_id, :collateral_item_id, :shares)
end
You may have a similar issue with :collateral_item in another form.

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

Nested form attributes wont save

I am required to use nested forms on an assignment I am working on and I got stuck because my nested form attributes wont submit to database.
Here is what my controller looks like
def new
#booking = Booking.new
params[:no_of_passengers].to_i.times { #booking.passengers.build }
end
def create
#booking = Booking.new(booking_params)
respond_to do |format|
if #booking.save
format.html { redirect_to '/booking_confirmed', notice: 'Booking was successfully created.' }
format.json { render :show, status: :created, location: #booking }
else
format.html { render :new }
format.json { render json: #booking.errors, status: :unprocessable_entity }
end
end
end
private
def booking_params
params.permit(
:airline, :origin, :destination, :departure_date, :departure_time, :arrival_date,
:arrival_time, :flight_id, :price, :no_of_passengers, :user_id, :booking,
passenger_attributes: [
:id,:booking_id, :name, :email,:done,:_destroy
]
)
end
Here is the association between the models
class Booking < ActiveRecord::Base
has_many :passengers
accepts_nested_attributes_for :passengers, reject_if: lambda { |attributes| attributes['name'].blank? }
end
class Passenger < ActiveRecord::Base
belongs_to :bookings
end
And here is the form
<%= form_for #booking do |b| %>
<%= b.fields_for :passengers do |p| %>
<%= p.text_field :name, placeholder: "Passenger Name" %>
<%= p.text_field :email, placeholder: "Passenger Email" %>
<% end %>
<% end %>
I checked the passenger table using Passenger.all in rails console and it returns nothing.
What am I doing wrong?
After a pairing session with sunnyk, I was able to see the errors.
The first error was that my class Passenger has belongs_to :bookings instead of belongs_to :booking. This is a common error though. The Associations between these classes now looks like:
class Booking < ActiveRecord::Base
belongs_to :flight
has_many :passengers
accepts_nested_attributes_for :passengers, reject_if:
lambda {|attributes| attributes['name'].blank?}, :allow_destroy => true
end
class Passenger < ActiveRecord::Base
belongs_to :booking
end
class Flight < ActiveRecord::Base
has_many :bookings
has_many :passengers, through: :bookings
accepts_nested_attributes_for :passengers
accepts_nested_attributes_for :bookings
end
Next:
Instead of using the default value of no_of_passengers for building my nested form, I used the cocoon gem, which makes nested forms building and management easier. I also crated a new params method, in which I made the flight_id permitted, and then passed it as an argument for my booking instance in my new method alongside my current user. So now my new method looks like this.
def new
#booking = Booking.new(new_booking_params)
#booking.user = current_user if current_user
end
def new_booking_params
params.permit(:flight_id)
end
After that, I had to make another params method for my create method, so as to allow the parameters I want in the bookings table, this include the passengers_attributes. Now my create method looks like this.
def create
#booking = Booking.new(another_booking_params)
respond_to do |format|
if #booking.save
format.html { redirect_to '/booking_confirmed', notice: 'Booking was successfully created.' }
format.json { render :show, status: :created, location: #booking }
else
format.html { render :new }
format.json { render json: #booking.errors, status: :unprocessable_entity }
end
end
end
def another_booking_params
params.require(:booking).permit(:flight_id, :user_id, :no_of_passengers,
passengers_attributes:[:name, :email])
end
Lastly, I had to adjust my form to look like this.
<%= form_for(#booking, url: bookings_path) do |f| %>
<%= f.hidden_field(:flight_id)%>
<%= f.hidden_field(:user_id) %>
<%= f.hidden_field(:no_of_passengers)%>
<%= f.fields_for :passengers do |passenger| %>
<%= render 'passenger_fields', :f => passenger %>
<% end %>
<%= link_to_add_association 'Add Another passenger',f, :passengers, :class => 'btn btn-primary add' %>
<%= submit_tag "Book Now", class: "btn btn-primary book" %>
<% end %>
and passenger_fields partial looks like.
<div class="nested-fields form-inline">
<div class="form-group">
<%= f.text_field :name, :class => "form-control", placeholder: "Passenger Name" %>
</div>
<div class="form-group">
<label>-</label>
<%= f.text_field :email, :class => "form-control", placeholder: "Passenger Email" %>
</div>
<div class="links pull-right">
<%= link_to_remove_association "Delete", f, class: "btn btn-danger" %>
</div>
<hr>
</div>
All that did the trick. I hope this will help others to understand nested forms better

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.

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