Say I have a Restaurant and a Reservation model. I want to find the reservations for each restaurant according to restaurant id, something like this:
#reservations = Reservation.find( # current user )
#restaurants = []
#reservations.each do |res|
#restaurants += Restaurant.where('id like ?', res.rest_id)
end
When trying this, this constructs an array, which I've tried to convert to an Active Record object un-successfully. Am I missing something, or is there a more obvious way to do this ?
restaurant.rb
has_many :reservations
reservation
belongs_to :restaurant, class_name: 'Restaurant', foreign_key: 'rest_id'
You can find restaurant record for this reservation as
#reservation = Reservation.joins(:restaurant).where(id: reservation_id)
#load all reservations matching your condition, and eager-load all associated restaurants
#reservations = Reservation.where(<some condition>).includes(:restaurants)
#get all associated restaurants for all objects in the #reservations collection
#which have been eager loaded, so this won't hit the db again),
#flatten them to a single array,
#then call `uniq` on this array to get rid of duplicates
#restaurants = #reservations.map(&:restaurants).flatten.uniq
EDIT - added flatten, added explanation in comments
Related
I would like to display #amount_spent and #amount_received by each user. I've already finished the first part with #amount_spent = Reservation.where(user_id: #user.id).sum(:total)
Now I would also like to display the #amount_received, since I use Stripe Connect.
I've tried something similar to:
#user = User.find(params[:id])
#products = #user.products
#amount_received = #products.reservations.sum(:total)
But when I try to use reservations with #products I get an #<Product::ActiveRecord_Associations_CollectionProxy:0x00007f50b8709fa8> error.
Is there a better way or another way, probably with scopes?
User Model:
has_many :products
has_many :reservations
Product Model:
has_many :reservations
Reservation Model:
belongs_to :user
belongs_to :product
You can't call #products.reservations to get all the reservations, because #products is a relationship (a collection) and you can call has_many methods on a single object. Instead you need to use SQL JOIN (implemented in ActiveRecord joins) to get all the relevant reservations and the to sum their total:
#products.joins(:reservations).sum('reservations.total')
See more:
https://guides.rubyonrails.org/active_record_querying.html#joins
https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-joins
http://www.sql-join.com/sql-join-types
The reason you get the exception is because .reservations can only be called upon a single product. Not on a collection of products. The following should work:
#amount_received = Reservation.where(product_id: #products).sum(:total)
Normally you would specify the column to select, but this defaults to the id. Meaning .where(product_id: #products) produces the same result as .where(product_id: #products.select(:id)).
I have two tables called
Product (prodID: integer, prodName: string, userID: FK)
and
User(userID:integer,userName:string).
The user can have many products. I want to write a query that gets me all the products for userID=10. I don't however understand which model I should put this in- the user or the product model or does it not matter? Presumably the output of the model will be fed to the controller it relies on so I should put it in the model that relates to the view I want to show it in? Is this correct?
You can directly use association method, no need of writing model method for fetching user's products.
In user.rb:
has_many :products
In product.rb
belongs_to :user
and from controller
User.where('id = ?', params[:id]).first.try(:products)
So, above query will fetch products if user with given id is found.
In your controller:
#user = User.find(params[:id])
#products = User.of_products(params[:id])
If you don't want to use #user in your action then you can avoid calculating #user.
In user.rb:
has_many :products
def self.of_products(user_id)
User.includes(:products).where(id: user_id)
end
This will give you all products of #user
I have two models has_and_belong_to_many to each other:
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags, :join_table => :tags_posts
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts, :join_table => :tags_posts
end
And then I have a query:
#tags = Tag.where(name: ["tag1","tag2","tag3"])
I want to get all the unique posts that have these tags, so I write this ugly code:
#posts = []
#tags.each do |tag|
#posts += tag.posts
end
#posts.uniq!
I guess it's inefficient, and since #posts is an array rather than a "ActiveRecord::Relation", I can't use all the class methods on it directly.
Is there a better way to achieve this?
#tags = Tag.where(name: %w(tag1 tag2 tag3)).includes(:posts)
should do the trick (%w() creates an array of strings, so no difference). It loads all posts with the tags in one or two queries, whatever is more efficient in AR's view. And then you can do
#posts = #tags.map(&:posts).flatten.uniq
to get the posts. map calls posts method on every tag and returns an array with those posts, but since you have an array of arrays, you want to flatten it.
You could join the two tables using includes and query on the Post model rather than Tag so that you get unique posts.
Post.includes(:tags).where(:tags => { name: %w(tag1 tag2 tag3) })
I have 3 models with "1 to n" associations, like this
Client --1 to n--> Category --1 to n--> Item
In one page, I need to display a list of Items along with their Categories. This page is subject to 3 level of filtering:
Client filtering: I know the client id (I'll use 'id=2' in this example)
Category name: dynamic filter set by the user
Item name: dynamic filter set by the user
And I'm getting more and more confused with ActiveRecord Associations stuff
In my ItemsController#index, I tried this:
categories = Client.find(2).categories
.where('name LIKE ?', "%#{params[:filter_categories]}%")
#items = categories.items
.where('name LIKE ?', "%#{params[:filter_items]}%")
The second line raises a NoMethodError undefined method 'items' for ActiveRecord::Relation. I understand that the first line returns a Relation object, but I cannot find a way to continue from here and get the list of Items linked to this list of Categories.
I also started to extract the list of categories ids returned by the first line, in order to use them in the where clause of the second line, but while writing the code I found it inelegant and thought there may be a better way to do it. Any help would be very appreciated. Thanks
models/client.rb
class Client < ActiveRecord::Base
has_many :categories
has_many :items, through: :categories
...
end
models/category.rb
class Category < ActiveRecord::Base
belongs_to :client
has_many :items
...
end
model/item.rb
class Item < ActiveRecord::Base
belongs_to :category
has_one :client, through: :category
...
end
You can only call .items on a category object, not on a collection. This would work:
#items = categories.first.items
.where('name LIKE ?', "%#{params[:filter_items]}%")
To get what you want, you can do the following:
#items = Item
.where('category_id IN (?) AND name LIKE ?', categories, "%#{params[:filter_items]}%")
Assuming that in the end you are only interested in what is in #items, it would be even better to do it in one query instead of two, using joins:
#items = Item.joins(:category)
.where('items.name LIKE ? AND categories.name = ? AND categories.client_id = 2', "%#{params[:filter_items]}%", "%#{params[:filter_categories]}%")
You can try smth like this:
item_ids = Client.find(2).categories.inject([]) { |ids, cat| ids |= cat.item_ids; ids }
items = Item.find(item_ids)
This how you can get a list of nested objects that associated through another table.
Lets say Users have BankAccounts which have a balance value.
So, how do I generate an array that Users and the total value of all their BankAccounts' values added together?
I'm not sure if this is quite what you want, but you can do something like:
ActiveRecord::Base.connection.select_all("select user_id, sum(balance) from accounts group by user_id;")
This will give you an array of user_ids and balances from the accounts table. The advantage of doing it this way is that it comes down to only one SQL query.
You'll want to do something like this. I don't believe it's possible to use #sum via an association.
class User
def net_worth
BankAccount.sum(:balance, :conditions => ["user_id = ?", self.id])
end
end
Edit
I see a reference to a #sum in AssociationCollection, so try this:
class User
def net_worth
self.bank_accounts.sum(:balance)
end
end
(I haven't tested this code)
First you need to find the users you want so I'll just assume you want all users.
#users = User.all
Then you need to take the array and collect it with only the elements you want.
#users.collect! {|u| [u.name, u.bank_account_total_value]}
For this kinda attribute I would set it in the model assuming you have has_many :transactions as an association
Class User
has_many :transactions
def bank_account_total_value
total = 0
self.transactions.each do |t|
total += t.amount
end
end
end