Hi I want to achieve something like this :
feeds = []
following_companies.each do |follow|
feeds = feeds + Activities.where(:company_id => follow.company_id).order("created_at DESC").limit(5)
end
feeds = feeds.order("created_at DESC")
By the point I use feeds = feeds.order("created_at DESC"), feeds its an Array and throws an exception saying undefined method order for #<Array:0x007f78032e2458>. As long as I know the where clause returns a hash ( or an ActiveRecord list, please correct me if I am wrong). I think the problem is the way I am initializing feeds. Any advise?
When you throw the limit on the end of the where clause, it returns an array instead of Arel. You could sort the array instead of using the arel method order.
feeds = feeds.sort{|f| f.created_at }
Update
If the following companies is still a query you can do
company_ids = following_companies.pluck(:company_id)
If it is an array, you can do this to get all the company_ids
company_ids = following_companies.map {|u| u.location_id }
You can do one query, instead of looping through, for all the feeds which will be
Activities.where(company_id: company_ids).order("created_at DESC")
I'm not sure how you would limit it to 5 for each id though.
feeds.sort_by(&:created_at).reverse will do this directly on feeds after you're done building it with your loop, but you need to do this more efficiently in the first place.
What you're doing above is going to generate a ton of queries if you have any considerable number of followed companies. I'm not sure what your models looks like, but what I can glean from above is something like this:
class User < ActiveRecord::Base
has_many :companies
end
class Company < ActiveRecord::Base
has_many :activities
end
class Activity < ActiveRecord::Base
belongs_to :company
end
You should be able to add a relationship on User to allow you to query all activities at once. Your user model would like something like this:
class User < ActiveRecord::Base
has_many :companies
has_many :activities, through: :companies
end
Then you'd be able to gather your activities by doing user.activities, which would use a join to capture all of the activities, only generating one query. Obviously, your data model may not look exactly like this, but this is the general gist of it.
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 need to get all children from a parent as an ActiveRecord::Relation. Thing is, this children are stored in a polymorphic relation. In my case I need it to paginate some search results obtained with pg_search gem.
I've tried the following:
results = PgSearch.multisearch('query').map(&:searchable)
# Horrible solution, N + 1 and returns an array
docs = PgSearch.multisearch('query').includes(:searchable)
results = docs.map(&:searchable)
# Still getting an array
Also thought of things like select or pluck, but they are not intended for retrieving objects, only column data. I could try to search ids for each children type like so
Post.where(id: PgSearch.multisearch('query').where(searchable_type: "Post").select(:searchable_id)
Profile.where(id: PgSearch.multisearch('query').where(searchable_type: "Profile").select(:searchable_id)
But it doesn't scale, since I would need to do this for every object I want to obtain from a search result.
Any help would be appreciated.
EDIT
Here's some basic pseudocode demonstrating the issue:
class Profile < ApplicationRecord
has_one :search_document, :as => :searchable
end
class Post < ApplicationRecord
has_one :search_document, :as => :searchable
end
class Profile < ApplicationRecord
has_one :search_document, :as => :searchable
end
class SearchDocument < ApplicationRecord
belongs_to :searchable, plymorphic: true
end
I want to obtain all the searchable items as an ActiveRecord::Relation, so that I can dynamically filter them, in this specific case, using limit(x).offset(y)
SearchDocument.all.joins(:searchable).limit(10).offset(10)
Generates an error: cannot eagerly load searchable cause of polymorphic relation
SearchDocument.all.includes(:searchable).limit(10).offset(10)
This one does load the searchable items into memory, but does not return them in the query, instead it applies the filters to the SearchDocument items, as expected. This might be a temporary solution, to filter the search documents and then get the searchable items from them, but collides with pagination on the views.
The question here is: Is there a way I can get all searchable items as ActiveRecord::Relation to further filter them?
I'm unfamiliar with this library. However, looking at your code, I'd guess that any attempt to take a CollectionProxy and map some function over it will trigger evaluating the CollectionProxy and return an array.
Having had a quick look at the library GitHub docs, perhaps something like this might work:
post_docs = PgSearch.multisearch('query').where(searchable_type: "Post")
posts = Post.where(pg_search_document: post_docs)
I have two models: User and Message that are connected by a has_many relationship. I want to get a list of users sorted by the timestamp on their last message.
class User < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
When I do this:
#users = User.includes(:messages).order('messages.created_at DESC').limit(5)
It seems to order the messages, grab the newest 5 messages, and then return the users associated with those. So the number of users can be less than 5. I want to make sure I get the 5 users.
I want the query to get the newest message for each request, order the last messages, and then return the users with the newest messages. So I want something like:
#users = User.includes(:messages).order( <messages.last.created_at DESC> )
Ideally, the solution would do the sorting on the database, because I want to use paginate. If it's relevant I'm using Postgres.
I would probably be preferential to the solution mentioned by phoet of adding an attribute to User such as last_message_posted_at and using touch: true to update that on message creation. That simplifies the query that has to be performed when you need to pull your list of users. This also allows a much more readable (IMO) chain for your application:
#users = User.all.order(:last_message_posted_at)
=> "SELECT \"users\".* FROM \"users\" ORDER BY \"users\".\"last_message_posted_at\" ASC"
This also allows you to add a nice and simple scope to your User model
scope: :by_recent_message, ->{ order(:last_message_posted_at) }
User.by_recent_message.limit(5)
It also depends when and how often this #users scope is being used. Adding a few ms to the message post time is preferable, to me, than a complicated SQL query each time the list of users is pulled.
-- Edit for those who aren't familiar with the touch syntax --
Documentation for touch: http://apidock.com/rails/v4.2.1/ActiveRecord/Persistence/touch
class User < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user, touch: true
end
And then make my query (to the user with most recent last message):
#user = User.includes(:messages).order(updated_at: :desc )
You can try something along the lines of
Message.group(:user_id).joins(:users).order('max(messages.created_at) desc')
you can use left join instead of includes
#users = User.joins("LEFT JOIN messages on messages.user_id = users.id").order('messages.created_at').limit(5)
I'm trying to list all the user's products with a probable association where a flag 'notification' is set to zero.
user.probable_associations.where(:notified => 0).collect{|a| Product.where(:id => a.product_id).collect{|p| p.name}}.to_sentence
It seems like using a where and collect method twice within the statement isn't very good. Is there a better way to go about this?
Also, the result is something like
"[\"Product A\"] and [\"Product B\"]"
which is pretty ugly...and I still need to remove the extra punctuation "[\" \"]
instead of something clean like
"Product A and Product B"
EDIT based on Rich's Answer, still have issues because notified is a field in associations NOT product:
has_many :probable_associations, -> { where "associations.category = 3"}, class_name: 'Association', before_add: :set_probable_category
has_many :probable_products, class_name: 'Product', through: :probable_associations, source: :product do
def not_notified
select(:name).where(notified: 0)
end
end
I'd use an ActiveRecord Association extension:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :products do
def not_notified
select(:name).where(notified: 0)
end
end
end
#-> #user.products.not_notified
That's my contribution, but you could then use #spickermann & #tompave's controbutions and use .flatten.to_sentence
Without knowing what probable_associations does would I rewrite the code to something like this:
product_ids = user.probable_associations.where(:notified => 0).map(&:product_id)
Product.where(:id => product_ids).map(&:name).to_sentence
Assuming that probable_associations is just an ActiveRecord has_many association, and that you want to end up with a list of titles for Product records, you can use this:
ids = user.probable_associations
.where(notified: 0)
.pluck(:product_id)
result = Product.where(id: ids).pluck(:name).to_sentence
It's similar to #spikermann's answer, but pluck(:column_name) is faster than using a block and only extracts the required column from the DB.
Also, the reason your code produces that string is that, by the time you call to_sentence, you have an Array of sub-arrays. Each sub-array contains a single element: a product name.
That's because the second collect is sent to an ActiveRecord::Relation containing just one record.
You could have solved that problem with flatten, but the whole operation could just be refactored.
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.