How to get data in has_many :through association? - ruby-on-rails

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!

Related

Nested forms in Ruby with checkboxes and many-to-many relationship

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 %>

Nested Form with Many-to-Many relationship - Rails

I'm learning Rails and I'am pretty stuck building a nested form with many to many relationship.
I was able to get the many-to-many relationship working with has_many :through, but when it comes down to create the views and the controllers to get it working I'm getting stuck.
See models relationships below:
class Timesheet < ActiveRecord::Base
belongs_to :user
has_many :timesheet_payments
has_many :employees, :through => :timesheet_payments
accepts_nested_attributes_for :timesheet_payments,
:reject_if => :all_blank,
:allow_destroy => true
accepts_nested_attributes_for :employees
end
class Employee < ActiveRecord::Base
belongs_to :user
has_many :timesheet_payments
has_many :timesheets, :through => :timesheet_payments
end
class TimesheetPayment < ActiveRecord::Base
belongs_to :employee
belongs_to :timesheet
accepts_nested_attributes_for :employee,
:reject_if => :all_blank
end
See db schema below:
create_table "timesheet_payments", force: true do |t|
t.integer "employee_id"
t.integer "timesheet_id"
t.float "basic_hours"
t.float "sunday_bh_hours"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "timesheets", force: true do |t|
t.date "upload_date"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "employees", force: true do |t|
t.string "pps_no"
t.string "fname"
t.string "lname"
t.date "dob"
t.text "address"
t.string "ph_number"
t.float "basic_rop"
t.float "sunday_bh_rop"
t.string "email"
t.date "date_joined"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I want to create a form where I can create a new Timesheet, display all Employees and an field to add the basic_hours and sunday_bh_hours for each employee.
This would be similar to the idea of a relationship between Customer -> Order -> Products.
I hope this makes sense! Thanks in advance!
I tried this view form
<%= form_for(#payment, :html => {:multipart => true}) do |f| %>
<% if #payment.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#payment.errors.count, "error") %> prohibited this payment from being saved:</h2>
<% end %>
<!--creates a new timesheet -->
<%= f.fields_for :timesheet do |builder| %>
<%= builder.label "New timesheet" %>
<%= builder.text_field :upload_date %>
<p></p>
<% end %>
<!-- Add worked hours for each employee -->
<% #employee.each do |t| %>
<%= f.label t.fname %>
<br />
<%= f.label "Basic Hours" %>
<%= f.text_field :basic_hours %>
<%= f.label "Sunday/BH Hours" %>
<%= f.text_field :sunday_bh_hours %>
<br />
<% end %>
<%= f.submit 'Submit', :class => 'btn btn-primary' %>
<% end %>
It is loading fine but i really can't get my head around how I go about creating the "create" method on the controller.
I have the "new" method is following:
def new
#payment = TimesheetPayment.new
#timesheet = Timesheet.new
#employee = Employee.all
end

Show images grouped by their FK id

I have the following setup so far using Rails 4
class Tournament < ActiveRecord::Base
has_many :fixtures
has_one :gallery
has_many :gallery_images, :through => :gallery
end
class Gallery < ActiveRecord::Base
belongs_to :tournament
has_many :gallery_images, dependent: :destroy
accepts_nested_attributes_for :gallery_images, allow_destroy: :true
end
class GalleryImage < ActiveRecord::Base
belongs_to :gallery
end
With my db setup like so
create_table "galleries", force: true do |t|
t.integer "tournament_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "gallery_images", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.string "photo_file_name"
t.string "photo_content_type"
t.integer "photo_file_size"
t.datetime "photo_updated_at"
t.integer "gallery_id"
end
create_table "tournaments", force: true do |t|
t.string "name"
t.date "tourn_date"
t.string "tourn_location"
t.datetime "created_at"
t.datetime "updated_at"
end
I am trying to get a gallery and all its images into an object so that i can iterate through all the gallery_images by each tournament
<div id="verticalTab">
<ul class="resp-tabs-list">
<% #tournaments.each do |t| %>
<li><%= t.name %></li>
<% end %>
</ul>
<div class="resp-tabs-container">
<div>
<% #tournaments.each do |t| %>
<div class="clear"></div>
<% g.gallery_images.each do |i| %>
<ul class="team-gallery">
<li><%= image_tag(i.photo.url(:gallery_image)) %></li>
<% end %>
</ul>
<% end %>
Controller
def index
#tournaments = Tournament.all
end
Im a little unsure on how to group gallery_images by its tournament
Any pointers appreciated
try something like this for your tournament model
class Tournament < ActiveRecord::Base
has_many :fixtures
has_one :gallery
has_many :gallery_images, through: :gallery
end
then in your view you could do something like
<ul class="resp-tabs-list">
<% #tournaments.each do |t| %>
<li><%= t.name %></li>
<% t.gallery_images.each do |img| %>
<% end %>
<% end %>
</ul>
or alternatively you could do something like this as roman.brodetski commented which would not require you to modify your model
<ul class="resp-tabs-list">
<% #tournaments.each do |t| %>
<li><%= t.name %></li>
<% t.gallery.gallery_images.each do |img| %>
<% end %>
<% end %>
</ul>
(note that your controller variable name #tournaments didn't agree with the one you were referencing in the view and would have been nil as a result)
One other thing to note is that your models Tournament and Gallery are one to one and I think what you may want given your question is that Tournament and Gallery be a one to many relationship, in which case your Tournament model should look like
class Tournament < ActiveRecord::Base
has_many :fixtures
has_many :galleries
end
in which case you could have your view do something like
<ul class="resp-tabs-list">
<% #tournaments.each do |t| %>
<li><%= t.name %></li>
<% t.galleries.map{|a| a.gallery_images}.flatten.each do |img| %>
<% end %>
<% end %>
</ul>

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

How to refer to a symbols' active record relationship inside a fields_for

Inside a fields_for block how can i reference the value of a relationship field.
For instance:
app/models/cart.rb
class Cart < ActiveRecord::Base
attr_accessible :lineitems_attributes
has_many :lineitems, dependent: :destroy
accepts_nested_attributes_for :lineitems
def total_price
lineitems.to_a.sum { |item| item.total_price }
end
end
app/models/lineitem.rb
class Lineitem < ActiveRecord::Base
attr_accessible :cart_id, :quantity, :package_id, :part_id
belongs_to :cart
belongs_to :package
belongs_to :part
def total_price
if package_id?
return package.price * quantity
end
if part_id?
return part.price * quantity
end
end
end
app/models/package.rb
class Package < ActiveRecord::Base
attr_accessible :description, :img_src, :name, :price
has_many :lineitems
end
app/views/cart/_form.html.erb
<%= form_for #cart do |f| %>
<%= c.fields_for :lineitems do |i| %>
<%= render 'lineitem_fields', :f => i %>
<% end %>
<%= c.submit %>
<% end %>
app/views/cart/_lineitem_fields.html.erb
<%= f.text_field :quantity %>
<% if :package_id? %>
<%= f.text_field :package_id %>
<% else %>
<%= f.text_field :part_id %>
<% end %>
<%= link_to 'Remove',
lineitem_path(:id),
:method => :delete,
:confirm => t('.confirm', :default => t("helpers.links.confirm",
:default => 'Are you sure?')) %>
relative pieces of schema
create_table "carts", :force => true do |t|
t.integer "branch_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "lineitems", :force => true do |t|
t.integer "cart_id"
t.integer "part_id"
t.integer "package_id"
t.integer "quantity", :default => 1
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "parts", :force => true do |t|
t.string "description"
t.string "partNumber"
t.decimal "price"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "packages", :force => true do |t|
t.string "description"
t.string "name"
t.string "img_src"
t.decimal "price"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
The above form works but...
Question 1: How to show the package.name instead of :package_id
Question 2: How to display total_price of each lineitem in the form. It is a method how would that work?
Question 3: Is there a best practice to displaying a form in an invoice looking manner where maybe the quantity is a text field but the remainder of the columns are just text or labels?
The end game scenario is this form will be a last chance to edit the quantities of the cart (or remove lineitems) before submitting an order. Obviously in the real world you want to display the quantities, package name, description and price but I can't seem to figure out how to display those values inside the form since they are in another model by relationship and not specific to lineitems.
Thanks for the help.
You are looking for the ActionView::Helpers::ActiveModelInstanceTag#object method. This gives you access to ActiveRecord everywhere inside the form.
1
<% if f.object.package_id? %>
<%= text_field_tag :package_name, f.object.package.name %>
<%= f.hidden_field :package_id %>
<% else %>
2 <%= f.object.total_price %>
3 Perhaps try the :readonly => true option on all input tags except the quantity?

Resources