Calling attributes of an associated model in the view - ruby-on-rails

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

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)

Eager-loading with many-to-many relationship

I have 3 models:
class Thing < ActiveRecord::Base
has_many :products
has_many :shops, through: :products
end
class Product < ActiveRecord::Base
belongs_to :thing
belongs_to :shop
end
class Shop < ActiveRecord::Base
has_many :products
has_many :things, through: :products
end
Shop sales many things. Every shop has its own page with the list of its things. Products table has shop_id, thing_id, things quantity and thing price.
Here is controller:
def show
#shop = Shop.find(params[:id])
end
And view:
<% #shop.things.each do |thing| %>
<%= link_to thing.name, shop_thing_path(id: thing.id, shop_id: #shop.id) %><br>
<%= thing.products.find_by(shop_id: #shop.id).price %><br>
<%= thing.products.find_by(shop_id: #shop.id).quantity %>
<% end %>
I can't understand how to eager load this right. Now i get N * 2 queries (N = things count):
SELECT "products".* FROM "products" WHERE "products"."thing_id" = ? AND "products"."shop_id" = 1 LIMIT 1
def show
#shop = Shop.includes(:things).find(params[:id])
end
Here is the documentation related to eager loading.
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
I tried to go from another point and use #products instead of #shop.things:
Controller:
def show
#shop = Shop.find(params[:id])
#products = #shop.products.includes(:thing).joins(:thing)
end
View
<% #products.each do |product| %>
<tr>
<td><%= link_to product.thing.name, shop_thing_path(id: product.thing_id, shop_id: product.shop_id) %></td>
<td><%= product.price %></td>
<td><%= product.quantity %></td>
</tr>
<% end %>
Now that works. But i still can't understand why
def show
#shop = Shop.includes(:things).find(params[:id])
end
doesn't work.

Getting uncategorized objects in has_many :through

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

Need data from a many:many join in a Rails view

Its maybe not the best solution in most cases, but i want a table with data form 3 tables.
class Media < ActiveRecord::Base
belongs_to :user
belongs_to :type
has_many :ratings
end
class User < ActiveRecord::Base
has_many :medias
has_many :ratings
end
class Rating < ActiveRecord::Base
belongs_to :user
belongs_to :media
end
Thats the view I want
<table>
<tr>
<th>Name</th>
<th>Comment</th>
<th>Creator</th>
<th>Type</th>
<% for user in #users %>
<th><%=h user.login %></th>
<% end %>
</tr>
<% for media in #medias %>
<tr>
<td><%=h media.name %></td>
<td><%=h media.comment %></td>
<td><%=h media.user.login %></td>
<td><%=h media.type.name %></td>
<% for user in #users %>
<td><%=h GET_RATING (media, user) %></td>
<% end %>%>
</tr>
<% end %>
</table>
Basicly i want one new row for each users ratings for each media
What I want is a Table that looks like that:
media.name media.comment ... rating(media, user).rating
I think it would be better to use a join in the Controller with the Media find methods but I dont know how exactly, enougher possible solution could be helper method that takes media and user as parameters.
What do you think is the best solution for this?
This kind of association belongs in your model, a has many through relationship is perfect for this.
class User < ActiveRecord::Base
has_many :ratings
has_many :media, :through => :ratings
end
class Media < ActiveRecord::Base
has_many :ratings
has_many :users, :through => ratings
end
class Rating < ActiveRecord::Base
belongs_to :user
belongs_to :media
end
Then you can access
media.name media.comment
Then could also access
user.ratings
or:
<% media.users.each do |user| %>
## Do stuff with user.ratings array
<% end %>
You can also:
media.ratings.each do |rating|
rating.your_attribute
rating.user.your_attribute
end

Resources