Getting uncategorized objects in has_many :through - ruby-on-rails

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

Related

Rails association loop display same primary key in row

I have a loop using associations. I'm looking to group by treatment and display in a row.
Each loop has three records for each treatment. The code below this is what i'm producing.
VIEW
<table>
<tr>
<td>Treatment</td>
<td>Date</td>
<td>Count</td>
</tr>
<% #trial.establishmentMethods.order(:treatment_selection_id).each do |data| %>
<tr>
<td><%= data.treatmentSelection.treatment.name %></td> This is reference by treatment_selection_id.
<td><%= data.date %></td>
<td><%= data.count %></td>
</tr>
<% end %>
</table>
This is what i'm hoping to produce. Display the treatment once, then loop the related treatment_selection_id's on the same row.
Here are my models and associations.
class Trial < ApplicationRecord
has_many :assessments, primary_key: 'trial_id'
has_many :establishmentMethods, through: :assessments
end
class EstablishmentMethod < ApplicationRecord
belongs_to :treatmentSelection, primary_key: 'treatment_selection_id', foreign_key: 'treatment_selection_id'
has_many :treatments, through: :treatmentSelection
end
class TreatmentSelection < ApplicationRecord
belongs_to :treatment, primary_key: 'treatment_id'
end
It seems like TreatmentSelection has_many establishmentMethods, so you should add that to the TreatmentSelection model. Then you can do something like:
<% treatment_selections.each do |treatment_selection| %>
<tr>
<td><%= treatment_selection.treatment.name %></td>
<% treatment_selection.establishmentMethods.each do |em| %>
<td><%= em.date %></td>
<td><%= em.count %></td>
<% end %>
</tr>
<% end %>
By the way, it's convention to use snake_case in ruby and it will make using associations easier.

Sort order for ActiveRecord::Associations::CollectionProxy in table rails

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)

Can't link to correct route using link_to

I am trying to link to the existing route /clients/:client_id/invoices/:id
from my /clients/:client_id show page and cant work out how to do so.
I have a has_many through: relationship and here are my models
class Client < ActiveRecord::Base
has_many :invoices
has_many :items, through: :invoices
class Invoice < ActiveRecord::Base
belongs_to :user
belongs_to :client
has_many :items, :dependent => :destroy
accepts_nested_attributes_for :items, :reject_if => :all_blank, :allow_destroy => true
class Item < ActiveRecord::Base
belongs_to :invoice
belongs_to :client
My routes
resources :clients do
resources :invoices
end
resources :invoices
my client controllers show action
def show
#client = Client.find(params[:id])
#invoices = #client.invoices.build
end
And my clients show.html.erb
<div class="panel-body">
<table class="table table-hover">
<thead>
<tr>
<th>Sender</th>
<th>Reciever</th>
<th>Amount</th>
<th>Currency</th>
<th>Date</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #client.invoices.each do |invoice| %>
<tr>
<td><%= invoice.sender %></td>
<td><%= invoice.reciever %></td>
<td><%= invoice.amount %></td>
<td><%= invoice.currency %></td>
<td><%= invoice.date %></td>
<td><%= link_to 'Show', invoices_path(#clients, #invoice) %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
everytime I click the link_to show it routes me to /invoices
I have tried a bunch of different link_to formats but I haven't been able to figure it out.
You are using wrong url_helper with wrong parameters. You should have:
<td><%= link_to 'Show', client_invoice_path(#client, invoice) %></td>
or
<td><%= link_to 'Show', invoice_path(invoice) %></td>
invoices_path is an url_helper generated by resources :invoices (the most outside one) and will route you to the index path for your InvoicesController (/invoices). If you pass an argument, it will be used for the format (/invoices.10 - quite common issue).
All the routes generated by nested resources have a name consisting of both resources, like new_user_profile_path or client_invoice_type_path (triple nested).
Note that your current route structure (same resources with two different paths) might make your controller logic more complex. It is usually sufficient to have a single route, pick one.

Calling attributes of an associated model in the view

I have three relevant models: Vendor, Item, InventoryItem. I'm having difficulty understanding how to tap into associations to return associated attributes.
class Item < ActiveRecord::Base
has_many :inventory_items
has_many :vendors, through: :inventory_items
accepts_nested_attributes_for :inventory_items, :vendors
class InventoryItem < ActiveRecord::Base
belongs_to :item
belongs_to :vendor
class Vendor < ActiveRecord::Base
has_many :inventory_items
has_many :items, through: :inventory_items
I'm trying to return the vendors who sell an item, and the price they sell it for. Here's my SearchResults index view:
<table>
<tr class="search-table">
<td>Product</td>
<td>Details</td>
<td>Brand</td>
<td>Code</td>
<td>Vendors</td>
<td>Price</td>
</tr>
<% #items.each do |item| %>
<tr class="search-table">
<td><%= item.product %></td>
<td><%= item.details %></td>
<td><%= item.brand %></td>
<td><%= item.code %></td>
<td><%= #how to return vendors? %></td>
<td><%= #how to return price? %></td>
</tr>
<% end %>
</table>
Here is my SearchResultsController:
class SearchResultsController < ApplicationController
def index
#search = Item.solr_search do
fulltext params[:search]
end
#items = #search.results
end
end
I'm newish at RoR so any input is welcome. Thanks in advance!
EDIT
Here is what is returned from rails console when given Item.first.vendors
Item Load (0.7ms) SELECT "items".* FROM "items" LIMIT 1
Vendor Load (0.9ms) SELECT "vendors".* FROM "vendors" INNER JOIN "inventory_items" ON "vendors"."id" = "inventory_items"."vendor_id" WHERE "inventory_items"."item_id" = 1
=> []
SOLUTION EDIT
I had some fundamental errors in my model associations that wouldn't allow me to utilize those relationships. I cleaned up those associations by getting rid of duplicate fields (in this case :item_id and :product_code) and the answer below worked perfectly.
In order to list vendors of a specific item, just replace :
<td><%= #how to return vendors? %></td>
With :
<% item.vendors.each do |vendor| %>
<%= vendor.name %><br/>
<% end %>

Associate Pre-existing Record with Pre-Existing Parent (2 parent objects)

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).

Resources