Nested forms in Ruby with checkboxes and many-to-many relationship - ruby-on-rails

I am new to ruby and having a trouble in following scenario. I am trying to build a relationship between bill and items. In my case, I want to generate a bill at run time like when user clicks on create new bill, he is directed to a route like http://localhost:3000/bills/new and then he has a list of items from which he has to choose by checking the checkboxes and adding the quantity. I have 3 tables, Items, Bills, BillItems. They have following fields in them:
create_table "bill_items", force: :cascade do |t|
t.integer "bill_id"
t.integer "item_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "quantity"
end
create_table "bills", force: :cascade do |t|
t.integer "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "items", force: :cascade do |t|
t.string "name"
t.float "price"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "category_id"
end
I have my models created like this:
Bill.rb
class Bill < ApplicationRecord
has_many :bill_items
has_many :items, through: :bill_items
accepts_nested_attributes_for :bill_items, :allow_destroy => true, :reject_if => :all_blank
end
Item.rb
class Item < ApplicationRecord
has_many :bill_items
has_many :bills, through: :bill_items
end
BillItem.rb
class BillItem < ApplicationRecord
belongs_to :bill
belongs_to :item
end
I have my form like:
<%= form_for #bill do |f| %>
<% if #allItems %>
<% #allItems.each_with_index do |item, index| %>
<tr class="table-success" scope="col-8">
<%= f.fields_for :bill_items do |s| %>
<td class="text-secondary"><%= item.category.name %></td>
<%= s.hidden_field :name, value: item.name %>
<td class="text-primary"><%= s.label item.name %></td>
<td><%= check_box_tag "item_ids[]", item.id, false, class: 'selectable' %> </td>
<td><%= s.number_field(:quantity, in: 1.0..100.0, step: 1) %></td>
<td><%= s.label :price, item.price %></td>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<div class="form-group row justify-content-center">
<%= f.submit "Create Order with Selected items", class: "btn btn-secondary" %>
</div>
<% end %>
Then I have my controller setup like this:
def new
#bill = Bill.new
#bill_items = #bill.bill_items.build
end
def create
byebug
#bill = Bill.new(bill_params)
#bill.save
redirect_to new_bill_path
end
private
def bill_params
params.require(:bill).permit(bill_items_attributes: [:quantity, :item_ids])
end
When I run my code and send data to form and check params via byebug it shows me following params, while i selected two items, of ids 1 and 4:
<ActionController::Parameters {"authenticity_token"=>"hVnrTkWxWwuXqS4tb01INVkNwRaFooVERKe2L8YkXykyPqImKCVRrvqjhK8sA0Q26nsOS+dSNdLvIOPTfis8nQ==", "bill"=>{"bill_items_attributes"=>{"0"=>{"name"=>"sheer", "quantity"=>"2"}, "1"=>{"name"=>"burger", "quantity"=>""}, "2"=>{"name"=>"custurs", "quantity"=>""}, "3"=>{"name"=>"sib", "quantity"=>"4"}}}, "item_ids"=>["1", "4"], "commit"=>"Create Order with Selected items", "controller"=>"bills", "action"=>"create"} permitted: false>
Then I click submit and it only saves bill in the db and gives me error
Unpermitted parameter: :name
Unpermitted parameter: :name
Unpermitted parameter: :name
Unpermitted parameter: :name`
I have tried many techniques and couldn't find a solution. It will be very helpful if someone can help me with this. Even if I need to redesign my logic then do help me with this. Thanks.

<td><%= check_box_tag "item_ids[]", item.id, false, class: 'selectable' %> </td>
has to be
<td><%= check_box_tag "bill[item_ids[]]", item.id, false, class: 'selectable' %> </td>

Problem: There is a mismatch in nested attributes naming
Bill model is accepting nested attributes for :items
On Controller you have specified :bill_items_attributes and
Form generating fields for bill_items -
f.fields_for :bill_items
Solution: make it consistent
On Bill model -
accepts_nested_attributes_for :items
On Bill Controller -
permit(items_attributes:
On form new.html.erb -
f.fields_for :items
However this will create another problem for items_id, which I am not sure what are trying to achieve there.

You were getting those errors because you had hidden_field :name in form which is not required while submitting form. I removed that. However there was an issue with strong parameters and checkbox naming. I corrected strong params and kept checkbox naming descriptive so that you can see how form will generates it.
Try this code. I was able to create records with this code.
bills_controller.rb
private
def bill_params
params.require(:bill).permit(bill_items_attributes: [:quantity, :item_id])
end
new.html.erb
<%= form_for #bill do |f| %>
<% if #allItems %>
<% #allItems.each_with_index do |item, index| %>
<%= f.fields_for :bill_items do |s| %>
<tr class="table-success" scope="col-8">
<td class="text-primary"><%= s.label item.name %></td>
<td><%= check_box_tag "bill[bill_items_attributes][#{index}][item_id]", item.id, false, class: 'selectable' %> </td>
<td><%= s.number_field(:quantity, in: 1.0..100.0, step: 1) %></td>
<td><%= s.label :price, item.price %></td>
</tr>
<% end %>
<% end %>
<% end %>
<div class="form-group row justify-content-center">
<%= f.submit "Create Order with Selected items", class: "btn btn-secondary" %>
</div>
<% end %>

Related

How to get data in has_many :through association?

I'm following this tutorial and this tutorial to learn more about has_many :through association in Rails. I created an app called school. And I have this inside my schema.rb file:
create_table "courses", force: :cascade do |t|
t.integer "teacher_id"
t.integer "student_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "quantity"
end
create_table "students", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "teachers", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
My teacher model:
class Teacher < ActiveRecord::Base
has_many :courses
has_many :students, :through => :courses
end
My student model:
class Student < ActiveRecord::Base
has_many :courses
has_many :teachers, :through => :courses
end
My course model:
class Course < ActiveRecord::Base
belongs_to :teacher
belongs_to :student
end
And my /courses view now looks something like this (I'm using scaffold):
When I go to /teachers/1, I want to display all students name and quantity associated with that teacher.
The current view of /teachers/1 is like this:
I try to make it by using this code but it's not working:
<% #course.each do |c| %>
<p><%= c.quantity %></p>
<% end %>
So, how to display all students name and quantity associated with that teacher?
<% #teacher.courses.each do |c| %>
<p><%= c.student.name %></p>
<p><%= c.quantity %></p>
<% end %>
You have to use the name of relation on variable with teacher object.
<% #teacher.courses.each do |c| %>
<%= c.quantity %>
<% end %>
<% #teacher.students.each do |s| %>
<%= s.name %>
<% end %>
Solved with this code:
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= #teacher.name %>
</p>
<table>
<tr>
<th>Student Name</th>
<th>Quantity</th>
</tr>
<% #teacher.courses.each do |c| %>
<tr>
<td><%= c.student.name %></td>
<td><%= c.quantity %></td>
</tr>
<% end %>
</table>
<%= link_to 'Edit', edit_teacher_path(#teacher) %> |
<%= link_to 'Back', teachers_path %>
Thanks to msergeant and Jan!

Setting Foreign key params Relationship in rails

I've searched a lot , found similar questions , but any seem to fit , or i did not understood right.
i have 3 models , i choose a bad name for one model, that is for Tv Shows , and you may confuse the Show model with show method and view .
Show.rb
class Show < ActiveRecord::Base
has_many :seasons
belongs_to :user
has_many :episodes , through: :seasons
accepts_nested_attributes_for :seasons
attr_accessible :name,:status,:duration
end
Season.rb
class Season < ActiveRecord::Base
belongs_to :show
has_many :episodes
accepts_nested_attributes_for :episodes
attr_accessible :order,:status
end
Episode.rb
class Episode < ActiveRecord::Base
belongs_to :season
attr_accessible :number
end
My forms for each model are separeted, what i wanna do is set the foreigns keys for each one automatically. So , in the view of my Show i wanna create a season that will be stored in that Show.
Shows/show.html.erb
p id="notice"><%= notice %></p>
<% #seasons = Season.all %>
<% #episodes= Episode.all%>
<p>
<strong>Name:</strong>
<%= #show.name %>
</p>
<p>
<strong>Status:</strong>
<%= #show.status %>
</p>
<p>
<strong>Episode Duration:</strong>
<%= #show.Episode_Duration %>
</p>
<% #seasons.where(:show_id => #show.id).each do |season| %>
<tr>
<td><%= season.order %></td>
<td><%= season.status %></td>
<h3>episodios</h3>
<hr>
<% #episodes.where(:season_id => season.id).each do |episode| %>
<td> <%= episode.number%></td>
<%end%>
<hr>
<td><%= link_to 'episode', episode_new_path(season.id)%></td>
</tr>
<br>
<% end %>
<h1><%=#show.id%></h1>
<h1><%=#show.episodes.count%></h1>
<h1></h1>
**<%= link_to 'create seasons', season_new_path(#show.id) %>**
<%= link_to 'Edit', edit_show_path(#show) %> |
<%= link_to 'Back', shows_path %>
In that lasts line, i'm passing #show.id for create a season, i want use that #show.id as my foreign key :show_id for that season. how i use that id in my params?
I saw some posts and i tried this , but didn't worked , heres the controller(only the part that i think is useful.)
seasons_controller.rb
def create
#season = Season.new(season_params)
#season.show_id = params[:id]
#season.save
end
respond_to do |format|
if #season.save
format.html { redirect_to #season, notice: 'Season was successfully created.' }
format.json { render :show, status: :created, location: #season }
else
format.html { render :new }
format.json { render json: #season.errors, status: :unprocessable_entity }
end
end
def season_params
params.require(:season).permit(:order, :status)
end
EDIT
My routes are:
Routes.rb
devise_for :users
resources :episodes
resources :seasons
resources :shows
root 'shows#index'
get 'seasons/create/:id' => 'seasons#new', as: :season_new
get 'episodes/create/:id' => 'episodes#new', as: :episode_new
put 'subscribe_show/:id' => 'shows#subscribe', as: :user_subscribe_show
put 'unsubscribe_show/:id' => 'shows#unsubscribe', as: :user_unsubscribe_show
get 'user/dashboard/' => 'users#index', as: :user_dashboard
And my forms are:
seasons/form.html.erb
<%= form_for(#season) do |f| %>
<% if #season.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#season.errors.count, "error") %> prohibited this season from being saved:</h2>
<ul>
<% #season.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :order %><br>
<%= f.number_field :order %>
</div>
<div class="field">
<%= f.label :status %><br>
<%= f.text_field :status %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Show/form.html.erb
<%= form_for(#show) do |f| %>
<% if #show.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#show.errors.count, "error") %> prohibited this show from being saved:</h2>
<ul>
<% #show.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :status %><br>
<%= f.text_field :status %>
</div>
<div class="field">
<%= f.label :Episode_Duration %><br>
<%= f.text_field :Episode_Duration %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
EDIT 2:
schema.rb
create_table "episodes", force: :cascade do |t|
t.integer "number"
t.integer "season_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "seasons", force: :cascade do |t|
t.integer "order"
t.string "status"
t.integer "show_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "watched"
end
create_table "shows", force: :cascade do |t|
t.string "name"
t.string "status"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "Episode_Duration"
t.integer "user_id"
end
Migrations
class AddUserIdtoShow < ActiveRecord::Migration
def change
add_index :shows , :user_id
end
end

Rails shows non existent database records

Rails shows that an instance of the Comment model exists, even though the database is empty. My code to show the Comment is
<% #post.comments.each do |c| %>
<%= c %>
<% end %>
And from that, I get #<Comment:0x007fe971c02800> also if i do c.attributes I get
{"id"=>nil, "body"=>nil, "author"=>nil, "post_id"=>37, "deleted"=>nil, "locked"=>nil, "created_at"=>nil, "updated_at"=>nil}, but the table has no records. If I change that code to
<% #post.comments.each do |c| %>
<%= c.body %>
<% end %>
I get nothing.
Maybe it has something to me using closure_tree.
If it helps, here is the schema for the table:
create_table "comment_hierarchies", id: false, force: true do |t|
t.integer "ancestor_id", null: false
t.integer "descendant_id", null: false
t.integer "generations", null: false
end
add_index "comment_hierarchies", ["ancestor_id", "descendant_id", "generations"], name: "comment_anc_desc_udx", unique: true
add_index "comment_hierarchies", ["descendant_id"], name: "comment_desc_idx"
create_table "comments", force: true do |t|
t.string "body"
t.string "author"
t.string "post_id"
t.boolean "deleted"
t.boolean "locked"
t.datetime "created_at"
t.datetime "updated_at"
end
EDIT: I fixed it by changing the form on the page to make a new comment from
<%= form_for([#post, #post.comments.build], :html => {:class => 'ui form'}) do |f| %>
<div class="field">
<%= f.text_area :body, placeholder: "Comment", style: "max-height: 100px;"%>
</div>
<p>
<%= f.submit "Add Comment", class: "blue ui button" %>
</p>
<% end %>
to
<%= form_for([#post, Comment.new], :html => {:class => 'ui form'}) do |f| %>
<div class="field">
<%= f.text_area :body, placeholder: "Comment", style: "max-height: 100px;"%>
</div>
<p>
<%= f.submit "Add Comment", class: "blue ui button" %>
</p>
<% end %>
Could you be calling #post.comments.new somewhere before this to render the comment form? This could be adding the new, non-persisted record in the association_cache.
To avoid this, you should be able to instantiate the comment as Comment.new(post_id: #post.id). This shouldn't add the comment to the association_cache. If it does, you really don't need the post_id on the new Comment. You probably have it in a hidden field anyway.
It seems like there is a comment persisted in your database for this post.
c.body may return nil, it alone doesn't define that there is no comment record in your database. Maybe you had no validation or manually skipped it on the console a while ago.
c.inspect will give you a more verbose breakdown of what data is in the object.
How are you confirming your database is empty? Does #post.comments.count equal 0?

How to set up a form to create several records on a has_many :through join table?

I'm trying to create a form that creates a new exercise with muscle_groups that are primary and secondary to the exercise.
Here's the relevant part of the schema:
create_table "exercised_muscle_groups", force: true do |t|
t.integer "muscle_group_id"
t.integer "exercise_id"
t.string "used_as"
end
add_index "exercised_muscle_groups", ["exercise_id"], name: "index_exercised_muscle_groups_on_exercise_id", using: :btree
add_index "exercised_muscle_groups", ["muscle_group_id"], name: "index_exercised_muscle_groups_on_muscle_group_id", using: :btree
create_table "exercises", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "muscle_groups", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
Here's what I have for the models:
class Exercise < ActiveRecord::Base
has_many :exercised_muscle_groups, dependent: :destroy
has_many :muscle_groups, through: :exercised_muscle_groups
accepts_nested_attributes_for :muscle_groups
accepts_nested_attributes_for :exercised_muscle_groups
end
class MuscleGroup < ActiveRecord::Base
has_many :exercised_muscle_groups
has_many :exercises, through: :exercised_muscle_groups
accepts_nested_attributes_for :exercises
accepts_nested_attributes_for :exercised_muscle_groups
end
class ExercisedMuscleGroup < ActiveRecord::Base
belongs_to :exercise
belongs_to :muscle_group
end
Here's what I have so far for the controller actions for new and create:
def new
#exercise = Exercise.new
#exercise.exercised_muscle_groups.build
end
def create
#exercise = Exercise.new(exercise_params)
#exercise.save
#then loop through all muscle groups marked as primary and then create a record for each one with value of "primary" for used_as column
#then loop through all muscle groups marked as secondary and then create a record for each one with value of "secondary" for used_as column
#if all saves are successful, then redirect to show page for that exercise
#if any save unsuccessful, then redirect to new page with error message
end
Here's what I have for the form in the view. I know it's wrong and it doesn't work.
<%=form_for(#exercise) do |exercise_form| %>
<%= exercise_form.label :name %>
<%= exercise_form.text_field :name, class: 'text_field' %><br>
<div class="col-xs-12">
<% exercise_form.fields_for :exercised_muscle_groups do |muscle_group_form| %>
<%= muscle_group_form.label :primary %>
</div>
<% MuscleGroup.all.each do |muscle_group| %>
<div class="col-xs-6">
<span class="col-xs-3">
<%= check_box_tag "exercise[muscle_group_ids][]", muscle_group.id %>
</span>
<span class="col-xs-3">
<%= muscle_group.name %>
</span>
</div>
<% end %>
<div class="col-xs-12">
<%= muscle_group_form.label :secondary %>
</div>
<% MuscleGroup.all.each do |muscle_group| %>
<div class="col-xs-6">
<span class="col-xs-3">
<%= check_box_tag "exercise[muscle_group_ids][]", muscle_group.id %>
</span>
<span class="col-xs-3">
<%= muscle_group.name %>
</span>
</div>
<% end %>
<% end %>
<%= exercise_form.submit "Submit", class: "btn btn-lg btn-primary" %>
<% end %>
How do I set up the form for the exercise and controller actions? Am I overcomplicating this? Is there an easier way?
Any help would be greatly appreciated. Thanks!

Can't get 'has_and_belongs_to_many' to work

Made a previous post about this but after a lot of testing and changing it's outdated.
I'm trying to tie users and activities together.(One user can have several activities. An activity can have several users). I'ts pretty much a multi-user agenda thing.
Whenever i create a new activity i can select users by checkbox which will be doing the activity.
I can't get it to work though; The users won't show up in my activity show. Printing #activity.users in my show results in #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_User:0x43d2158> but looping it, or checking my activities.js shows nothing(The activities.js shows "users":[] in the activity. So I'm pretty certain they're not associated properly.
Here's some code:
The activities.js index method
def index
#activities = Activity.all
respond_to do |format|
format.html
format.js {render_json #activities.to_json(:include => [:pictogram ,:users]) }
end
end
The activities' form(Loops all users)
<%= form_for(#activity) do |f| %>
<% if #activity.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#activity.errors.count, "error") %> prohibited this activity from being saved:</h2>
<ul>
<% #activity.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :start_date %><br>
<%= f.date_select :start_date %>
</div>
<div class="field">
<%= f.label :end_date %><br>
<%= f.date_select :end_date %>
</div>
<div class="users">
<% for user in User.all %>
<label class="activity">
<%= check_box_tag "activity[user_ids][]", user.id %>
<%= user.name %>
</label>
<% end %>
</div>
<div class="pictograms">
<% for p in Pictogram.all %>
<% #f.fields_for :pictograms do |x| %>
<%= p %>
<% #end %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The schema.rb
ActiveRecord::Schema.define(version: 20130911095113) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "activities", force: true do |t|
t.string "title"
t.date "start_date"
t.date "end_date"
t.integer "pictogram_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "activities", ["pictogram_id"], name: "index_activities_on_pictogram_id", using: :btree
create_table "activities_users", id: false, force: true do |t|
t.integer "activity_id"
t.integer "user_id"
end
add_index "activities_users", ["activity_id"], name: "index_activities_users_on_activity_id", using: :btree
add_index "activities_users", ["user_id"], name: "index_activities_users_on_user_id", using: :btree
create_table "pictograms", force: true do |t|
t.string "title"
t.string "url"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "name"
t.text "avatar"
t.date "birthdate"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Activity.rb
class Activity < ActiveRecord::Base
belongs_to :pictogram
has_and_belongs_to_many :users
accepts_nested_attributes_for :pictogram, allow_destroy: false
accepts_nested_attributes_for :users, allow_destroy: false
end
User.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :activities
validates_presence_of :name, :on => :create
validates_presence_of :birthdate, :on => :create
accepts_nested_attributes_for :activities, allow_destroy: false
end
And the activity params from my activities controller
def activity_params
params.require(:activity).permit(:title,
:start_date,
:end_date,
:users,
:pictogram)
end
Try sth like this:
class Activity < ActiveRecord::Base
belongs_to :pictogram
has_and_belongs_to_many :users
accepts_nested_attributes_for :pictogram, allow_destroy: false
def user_ids=(values)
self.users << User.find(values)
end
end
#controller
def activity_params
params.require(:activity).permit(:title,
:start_date,
:end_date,
{:user_ids =>[]},
:pictogram)
end
I suggest you try some minimalist debugging first,
User.create!(...) # create a valid user record
Activity.create!(...) # same as above
User.all.first.activities << Activity.all.first
User.all.first.activities.any?
See if this works, also keep an eye on your join_table and check that actual records are being persisted. Your schema looks good as well.
I think, none of them read about the strong_parameters with nested attributes :
You can easily define :users_attributes in permit parameters for nested_attributes.
activities controller :
def activity_params
params.require(:activity).permit(:title,
:start_date,
:end_date,
users_attributes: [:id])
end
This is the ideal way to do this. Thanks

Resources