I'm relatively new to Rails and in addition to displaying "All" items on my Index page, I also want to show a filtered list. Essentially a subset of records that have an association to another model.
My models are Issue and Split. A Split belongs to an issue and an issue has and belongs to many splits.
If a Split is associated with issue_id = 1, then I want it shown in the list of splits for that issue
Here is my controller code:
class SplitsController < ApplicationController
before_action :set_split, only: [:show, :edit, :update, :destroy]
# GET /splits
# GET /splits.json
def index
#splits = Split.all
#chosen_splits = Issue.find(1)
end
Here is the code I have in my index view:
<tbody>
<% #chosen_splits.each do |split| %>
<tr>
<td><input type="checkbox" value></td>
<td><%= link_to split.name, split_path(split) %></td>
<td class="text-right"><%= number_with_delimiter(split.quantity) %></td>
<td><%= link_to 'Edit', edit_split_path(split), :class => 'btn btn-xs btn-default' %></td>
<td><%= link_to 'Delete', split, method: :delete, data: { confirm: 'Are you sure?'}, :class => 'btn btn-xs btn-danger' %></td>
</tr>
<% end %>
</tbody>
Here is my Issue Model:
class Issue < ActiveRecord::Base
belongs_to :publication
has_and_belongs_to_many :splits
has_many :issue_split_geographies
belongs_to :medium
validates :name, :start_date, :status, presence: true
end
When I load the page I'm getting a "undefined method `each' for" error. But, the same basic logic is working for when I run the #splits variable.
All help is greatly appreciated.
You need
#chosen_splits = Issue.find(1).splits
Otherwise you will call .each method for your issue
Else please edit has_and_belongs_to_many :splits to just has_many :splits in your Issue model
Related
I'm struggling to display only the RELEVANT data in a table that is using has_many and through relationships. My models are the following:
Models
class TrainingResource
has_many :user_training_resources, dependent: :destroy
end
class UserTrainingResource
belongs_to :user
belongs_to :training_resource
end
class Users
has_many :user_training_resources, dependent: :destroy
has_many :training_resources, through: :user_training_resources
end
Ultimately I'm trying to build out a table on the Training Resource Show page of those users that have THAT Training Resource and their current status along with the ability for the user to delete said resource (User Training Resource).
So example while looking at Alpha Training Resource the user sees of the Training Resource: Name, Description, URL, and Total Subscriptions. Then below a list of the users that have Alpha Training along with their current status AND the option to delete it.
Controller
class Admin::TrainingResourcesController < Admin::ApplicationController
belongs_to_app :training_resources
add_breadcrumb 'Training Resources', :admin_training_resources_path
before_action :load_training_resource, only: [:show, :edit, :update, :destroy]
respond_to :html, :json
def show
#user_training_resources = UserTrainingResource.all
#users = User.all
respond_with(#training_resource)
end
private
def load_training_resource
#training_resource = TrainingResource.find_by!(id: params[:id])
end
end
My Training Resource Show template looks like:
<dl class="dl-horizontal">
<dt>Name:</dt>
<dd><%= #training_resource.name %></dd>
<dt>Description:</dt>
<dd><%= #training_resource.description %></dd>
<dt>Subscriptions Available:</dt>
<dd><%= #training_resource.subscriptions_available %></dd>
<dt>Total Subscriptions:</dt>
<dd><%= #training_resource.total_subscriptions %></dd>
<dt>Url:</dt>
<dd><%= #training_resource.url %></dd>
</dl>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>Training Resource Names</th>
<th>Status</th>
<th> </th>
</tr>
</thead>
<tbody>
<% #users.all.each do |user| %>
<tr>
<td><%= user.full_name %></td>
<td><%= user.training_resources.map(&:name).join(', ') %></td>
<td><% user.user_training_resources.each do |user_training_resource| %></td>
<td><%= user_training_resource.status %></td>
<% end %>
<td class="table-actions">
<%# <%= link_to 'Delete', admin_user_training_resource_path(#user_training_resource), :method => :delete, :data => {:confirm => 'Are you sure you want to delete this Training resource?'}, :class => 'btn btn-danger btn-sm' %> %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
So if I go to Training Resource and click on the show for Alpha I see it's name, description, total subscriptions, and url. However the table below will show me all users, all their training resources, and then their status. Not exactly what I want.
I've tried doing something like below in hopes of just going through Training Resource:
<% #training_resource.each do |training_res| %>
<%= training_res.name %>
<% end %>
This results in undefined method 'each'. So how do I display ONLY the data associated with the TrainingResource?
Add a new association for users in model TrainingResource, so that you can directly fetch all the users for a training_resource record.
class TrainingResource
has_many :user_training_resources, dependent: :destroy
has_many :users, through: :user_training_resources
end
Change your controller action show to look like below:
def show
respond_with #training_resource
end
And display the users inside <tbody> tag on training_resource show page in the following way:
<% #training_resource.users.each do |user| %>
<tr>
<td><%= user.full_name %></td>
<!-- If you want to display all the training_resources of the user and their statuses -->
<td><%= user.training_resources.map(&:name).join(', ') %></td>
<td><%= user.user_training_resources.map(&:status).join(', ')</td>
<td class="table-actions">
<!-- Assuming your path is something like '/admin/training_resources/:id/users/:user_id' -->
<%= link_to 'Delete', admin_user_training_resource_path(#training_resource, user), method: :delete, data: { confirm: 'Are you sure you want to delete this Training resource?' }, class: 'btn btn-danger btn-sm' %>
</td>
</tr>
<% end %>
This code is not tested. So, please let me know, if you face any errors.
Update
If you want to show the status of only the current training_resource for all the users, do:
<%- utr = user.user_training_resources.where(training_resource: #training_resource).first %>
<td><%= utr.status %></td>
i'll start how my models looks:
User -> has_one :customer, has_one :employee, has_many :appointments
Customer -> belongs_to :user
Employee -> belongs_to :user
Appointment -> belongs_to :user
And now in view:
<% #appointments.order(:appointment_date).each do |appointment| %>
<% if appointment.confirmation == wait_or_confirmed?(#status) %>
<tr>
<td><%= appointment.purpose %></td>
<td><%= appointment.appointment_date %></td>
<td><%= appointment.appointment_time %></td>
<td><%= Employee.find(appointment.employee_id).first_name %> <%= Employee.find(appointment.employee_id).last_name %></td>
<td><%= Customer.find(appointment.customer_id).first_name %> <%= Customer.find(appointment.customer_id).last_name %></td>
<td><%= link_to 'Show', appointment_path(appointment), class: "btn btn-primary" %></td>
</tr>
<% end %>
<% end %>
My brother told me i shouldnt pick up informtaions from database in views so i tried using includes:
#appointments = Appointment.all
#users = User.includes(:appointments)
And after combination in view it still doesn't work. Can someone help me?
Instead of this:
Employee.find(appointment.employee_id).first_name
Do this:
appointment.employee.first_name
For performance, in the controller, you can also replace this:
#appointments = Appointment.all
With this:
#appointments = Appointment.all.includes(:employee, :customer)
By doing so, you are eager-loading all associated employee and customer data from the database in a single query, rather than one at a time.
I want to set up sort order for active record collection proxy in table.
It should be sorted by number of available rooms (from highest to lowest).
The trick is that #rooms.reserved is a boolean and to calculate quantity of free/reserved rooms I have to use helper method to avoid record collection proxy errors. I get proper results, but I need to sort table by number of available rooms.
I have two models: Room and Hotel.
class Room < ApplicationRecord
belongs_to :hotel, optional: true # avoiding rails 5.2 belongs_to error
accepts_nested_attributes_for :hotel
end
and
class Hotel < ApplicationRecord
has_many :rooms, dependent: :destroy
accepts_nested_attributes_for :rooms
end
I have table:
<table>
<tr>
<th>Name</th>
<th>Rooms count</th>
<th>Rooms status: in reserve || free</th>
</tr>
<% #hotels.each do |hotel| %>
<tr>
<td><%= hotel.name %></td>
<td><%= hotel.rooms_count %></td>
<td><%= rooms_reservation_status(hotel.rooms) %></td> <!-- rooms_reservation_status helper method in application_helper.rb -->
<td ><%= link_to 'Show', hotel_path(hotel) %></td>
<td><%= link_to 'Destroy', hotel, method: :delete, data: { confirm: 'Are you sure?' } %>
</tr>
<% end %>
</table>
Helper method
# rooms_reservation_status iterates throught ActiveRecord::Associations::CollectionProxy
# and calculates the sum of free rooms aswell as a sum of reserved rooms
def rooms_reservation_status(rooms)
reserved = 0
free = 0
rooms.each do |r|
r.reserved == true ? reserved+=1 : free+=1
end
"#{reserved} || #{free}"
end
Active Record table for rooms:
class CreateRooms < ActiveRecord::Migration[5.1]
def change
create_table :rooms do |t|
t.boolean :reserved, :default => false
t.belongs_to :hotel, index: true
t.timestamps
end
end
end
I would add a class method on the Room model in order to return for a given collection the number of free rooms and reserved rooms:
class Room < ApplicationRecord
belongs_to :hotel, optional: true
accepts_nested_attributes_for :hotel
def self.reserved_count
where(reserved: true).count
end
def self.free_count
where(reserved: false).count
end
end
Once you have implemented, you can call it from the relationship declared in Hotel model:
class Hotel < ApplicationRecord
has_many :rooms, dependent: :destroy
accepts_nested_attributes_for :rooms
def reserved_rooms
rooms.reserved_count
end
def free_rooms
rooms.free_count
end
end
Your view will look finally like this:
<table>
<tr>
<th>Name</th>
<th>Rooms count</th>
<th>Rooms status: in reserved || free</th>
</tr>
<% #hotels.each do |hotel| %>
<tr>
<td><%= hotel.name %></td>
<td><%= hotel.rooms_count %></td>
<td><%= "#{hotel.reserved_rooms} || #{hotel.free_rooms}" %></td>
<td ><%= link_to 'Show', hotel_path(hotel) %></td>
<td><%= link_to 'Destroy', hotel, method: :delete, data: { confirm: 'Are you sure?' } %>
</tr>
<% end %>
</table>
Sorting the Hotels in your controller
In your controller make sure that you eager load Rooms for Hotel:
#hotels = Hotel.includes(:rooms).sort_by { |h| h.free_rooms.to_i }.reverse
You could eventually implement it as Hotel.includes(:rooms).sort_by(&:free_rooms).reverse.
In this way you won't need any join or helper.
Regarding your comment, free_rooms is implemented as an instance method (e.g. Hotel.first.free_rooms), so it will not be available for an ActiveRecord_Relation (e.g. Hotel.all.free_rooms)
Good day all,
Pardon me for my noob-ness in rails.
So here's my question.
So I've a category model and a itinerary model defined below
class Category < ActiveRecord::Base
has_many :categorizations, :dependent => :destroy
has_many :itineraries, :through => :categorizations
end
class Itinerary < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
end
So in my view, I am looping through categories to display itineraries in groups.
<% #categories.each do |category| %>
<table>
<thead>
<tr>
<th colspan="4"><%= category.name %></th>
</tr>
</thead>
<tbody>
<% category.itineraries.each do |itinerary| %>
<tr>
<td><%= itinerary.name %></td>
<td><%= link_to 'Show', itinerary %></td>
<td><%= link_to 'Edit', edit_itinerary_path(itinerary) %></td>
<td><%= link_to 'Destroy', itinerary, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
So I am wondering, how do we display itineraries that are not categorised yet?
I've searched around and found out that using scopes in the model will be the way to go.
scope :without_categories, -> { includes(:categorizations).where( :categorizations => { :itinerary_id => nil } )}
I find it not very "DRY" due to the fact that I've to write another table to iterate through itinerary.without_categories again.
Is there a way where we're able to code it in such a way where categories.all shows everything with uncategorized items in it?
Thank you.
Update #1
Decided to use this in my controller, which builds a new "Uncategorized" category on index action and it'll add to the array.
def index
uncategorized = Category.new
uncategorized.name = "Uncategorized";
uncategorized.itineraries = Itinerary.without_categories
#categories = Category.all << uncategorized
end
I know that in rails, controllers should be as skinny as possible.
But I can't think of a better way.
Anyone with a better answer, please feel free to share. Thanks! :)
You just have to find the itineraries that dont have a reference inside the Categorizations table. You can do a nested query for this.
SELECT * FROM itineraries where id NOT IN ( SELECT itinerary_id FROM categorizations')
just do a method in your Itinerary model like this:
def self.uncategorized
Itinerary.find_by_sql('SELECT * FROM itineraries where id NOT IN ( SELECT itinerary_id FROM categorizations)')
end
I have several models in nested attributes that I'm working with.
I have "teams" (has many constests), and "contests" (belongs to Team). But I also want contests to reference "categories" as a child object (a contest can only have one category, and a category can have may contests).
The way the logic works is that a team is created first, then a contest, and after that I want to be able to select from a list of categories (in a partial) and establish the association (set the category_id in contest to the id value in a category). It makes sense to me how this is done when creating a new contest as a child of team, but I am hitting my head agains the wall when it comes to creating the second relationship (existing contest to an existing parent category).
The controller that gives me the show view for a contest is:
def show
#team = Team.find(params[:team_id])
#contest = Contest.find(params[:id])
#categories = Category.all
respond_to do |format|
format.html # show.html.erb
format.json { render json: [#contest] }
end
end
In the show view I have this code:
<p><b>Name:</b><%= #contest.name %></p>
<%= link_to 'Edit', edit_team_contest_path(#team, #contest) %> |
<%= link_to 'Back', team_contests_path %>
<br />
<%= render 'categories/index'%>
And my partial _index for categories contains this code:
<table>
<% #categories.each do |category| %>
<tr>
<td><%= category.level1 %></td>
<td><%= category.level2 %></td>
<td><%= category.level3 %></td>
<td><%= category.level4 %></td>
<td><%= link_to 'Show', category %></td>
<td><%= link_to 'Edit', edit_category_path(category) %></td>
<td><%= link_to 'Destroy', category, confirm: 'Are you sure?', method: :delete %></td>
<%end%>
</table>
Where I am so flummoxed is where to place the code (in the Contest or Category controller?) for setting the category-contest parent-child relationship, as well as which view (the Contest show view, or the Category _index partial?). I am pretty certain that I am not understanding something fundamental about Rails here, so if anyone could point me to the docs that might clear up my befuddlement I'd very much appreciate it.
Okay, here's how I ended up solving my problem (in case anyone finds it later and uses the same search terms I tried):
Models:
team.rb
has_many :contests, :dependent => :destroy
category.rb
has_many :contests
contest.rb
belongs_to :team, :foreign_key => "team_id"
belongs_to :category, :class_name => 'Category', :foreign_key =>"category_id"
accepts_nested_attributes_for :category
Controller:
contests_controller
def update
#contest = Contest.find(params[:id])
#team = #contest.team
if !params[:category_id].nil?
#category = Category.find(params[:category_id])
#contest.update_attributes(:category_id => #category.id)
end
respond_to do |format|
if #contest.update_attributes(params[:contest])
blah
else
blah
end
end
end
Categories View (_index), a partial in the contests/show view, includes these three bits of code:
<table>
<% #categories.each do |category| %>
<tr>
<td><%= form_for [category, #contest] do |f| %>
<% f.submit "Select" %>
<% end %></td>
</tr>
<%end%>
</table>
And that is what it takes to associate a record that belong to another parent with another parent in a different model (after the first relationship has been created).