Rails sort collection by association count - sql

I'm working in Rails 4, and have two relevant models:
Account Model
has_many :agent_recalls, primary_key: "id", :foreign_key => "pickup_agent_id", class_name: "Booking"
Hence, queries like Account.find(10).agent_recalls would work.
What I want to do is sort the entire Account collection by this agent_recalls association.
Ideally it'd look something like (but obviously not):
#agents = Account.where(agent: true).order(:agent_recalls)
Question: What's the correct query to output an ordered list, by this agent_recall count?

Well to accomplish what you are looking for you have 2 options:
first, only a query, but it will implied a join, so there will be lost the Accounts that doesn't have any agent_recalls, so i will discard this option
second, i think this one is more appropriate for what you are trying to do
Account.find(:all, :conditions => { :agent => true }, :include => :agent_recalls).sort_by {|a| a. agent_recalls.size}
As you can see is a mix between a query and ruby, hope it helps :)

Related

Limit the amount of results in associated queries with Rails 3.2

I have the following query:
#books = Books.includes(:author, :pages)
.find(:all,
:order => 'created_at DESC')
Let's assume my "Pages" table has fields "words, pictures". For blank pages, field "words" is NULL. There are many "Pages" records per book.
The problem with the above query, is that it retrieves ALL the pages for each book. I would like to retrieve only 1 page record for example with the condition "NOT NULL" on the "words" field. However, I don't want to exclude from the query results the Books that do not match the pages query (I have 10 books in my table and I want 10 books to be retrieved. The book.page association should be "nil" for the books where the condition does not match.)
I hope this makes sense.
Check this SO question:
Rails 3 - Eager loading with conditions
It looks like what you want
class Category
has_many :children, :class_name => "Category",
:foreign_key => "parent_id"
has_many :published_pages, :class_name => "Page",
:conditions => { :is_published => true }
end
If you only want a single blank page to be returned then you could add an association:
has_one :first_blank_page, -> {merge(Page.blanks).limit(1)}, :class_name => "Page"
... where in page.rb ...
def blanks
where(:words => nil)
end
Then you can:
#books = Books.includes(:author, :first_blank_page).order('created_at desc')
... and subsequently reading first_blank_page would be very efficient.
The limit will not be used if you eager load, though, as the SQL syntax for that sort of this would be very complex to execute as one query, so you'd want to consider whether you want to eager load all of the pages per book and then just use one per book. It's a tricky trade-off.

putting a condition on an includes

I have the following relationships:
Category has_many :posts
Post has_many :comments
Post has_many :commenters, :through => :comments
I have the following eager load, giving me posts, comments and commenters (note that I need all 3, and hence the includes as opposed to joins)
category.posts.includes(:comments, :commenters)
However, I'd like to limit comments (and if possible commenters) to only those created in the past two weeks while still returning the same set of posts. Initially I thought I could specify a condition on the includes:
category.posts.includes(:comments, :commenters).where("comments.created_at > ?", 2.weeks.ago)
But found that this returns only the posts that meet the condition. I'm thinking that I may need to do something like performing a subquery on comments and then performing a join. Is there an easy way to do this with AR of would I be better off doing this with sql?
Finally managed to figure this out from reading this page:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
I simply needed to create an association in my Post model like:
Post has_many :recent_comments, :class_name = 'Comment', :conditions => ["created_at > ?", 2.weeks.ago]
Then I could do the following to get the desired ActiveRecord::Association object:
category.posts.includes(:recent_comments => :commenters)
There was also a suggestion of doing this by using a scope on a model. However, I read somewhere (I think it was on SO) that scopes are on their way out and that ARel has taken their place so I decided to do this without scopes.
Try :
category.posts.all(:includes => {:comments =>:commenters}, :conditions => ["comments.created_at = ? AND commenters.created_at = ?", 2.weeks.ago, 2.weeks.ago]

How to retrieve both "associated" records and "associated through" records in a performant way?

I am using Ruby on Rails 3.1 and I am trying to improve an SQL query in order to retrieve both "associated" records and "associated through" records (ActiveRecord::Associations) in a performant way so to avoid the "N + 1 query problem". That is, I have:
class Article < ActiveRecord::Base
has_many :category_relationships
has_many :categories,
:through => :category_relationships
end
class Category < ActiveRecord::Base
has_many :article_relationships
has_many :articles,
:through => :article_relationships
end
In a couple of SQL queries (that is, in a "performant way", maybe by using the Ruby on Rails includes() method) I would like to retrieve both categories and category_relationships, or both articles and article_relationships.
How can I make that?
P.S.: I am improve queries like the followings:
#category = Category.first
articles = #category.articles.where(:user_id => #current_user.id)
articles.each do |article|
# Note: In this example the 'inspect' method is just a method to "trigger" the
# "eager loading" functionalities
article.category_relationships.inspect
end
You can do
Article.includes(:category_relationships => :categories).find(1)
Which will reduce this to 3 queries (1 for each table). For performance, also make sure your foreign keys have an index.
But in general, I'm curious why the "category_relationships" entity exists at all, and why this isn't a has_and_belongs_to sort of situation?
Updated
As per your changed question, you can still do
Category.includes(:article_relationships => :articles).first
If you watch the console (or tail log/development) you'll see that when you call the associations, it'll hit the cached values and you're golden.
But I am still curious why you're not using a Has and Belongs To Many association.

rails association, single record only

I have an association like so
has_many :comments, :dependent => :delete_all, :order => "created_at ASC", :include => [:user]
this is returning only the first comment. while it should be returning all...
EDIT from comment :
a = Activity.find(1) a.comments a.comments has only one record entry, while Comment.find_all_by_activity_id(1) has 4
EDIT after trials...
It seems that using :include => [:user, :comments] is limiting the result to one comment for every activity.
any idea why? and more importantly how to fix it? I could remove it from include, but i'd like to avoid n+1 queries...
I'd look at the SQL that's being generated for each call, either in the console output or database log file.
Are the generated statements the same?

Ruby-on-Rails: How to pull out most recent entries from a limited subset of a database table

Imagine something like a model User who has many Friends, each of who has many Comments, where I'm trying to display to the user the latest 100 comments by his friends.
Is it possible to draw out the latest 100 in a single SQL query, or am I going to have to use Ruby application logic to parse a bigger list or make multiple queries?
I see two ways of going about this:
starting at User.find and use some complex combination of :join and :limit. This method seems promising, but unfortunately, would return me users and not comments, and once I get those back, I'd have lots of models taking up memory (for each Friend and the User), lots of unnecessary fields being transferred (everything for the User, and everything about the name row for the Friends), and I'd still have to step through somehow to collect and sort all the comments in application logic.
starting at the Comments and using some sort of find_by_sql, but I just can't seem to figure out what I'd need to put in. I don't know how you could have the necessary information to pass in with this to limit it to only looking at comments made by friends.
Edit: I'm having some difficult getting EmFi's solution to work, and would appreciate any insight anyone can provide.
Friends are a cyclic association through a join table.
has_many :friendships
has_many :friends,
:through => :friendships,
:conditions => "status = #{Friendship::FULL}"
This is the error I'm getting in relevant part:
ERROR: column users.user_id does not exist
: SELECT "comments".* FROM "comments" INNER JOIN "users" ON "comments".user_id = "users".id WHERE (("users".user_id = 1) AND ((status = 2)))
When I just enter user.friends, and it works, this is the query it executes:
: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users".id = "friendships".friend_id WHERE (("friendships".user_id = 1) AND ((status = 2)))
So it seems like it's mangling the :through to have two :through's in one query.
Given the following relationships:
class User < ActiveRecord::Base
has_many :friends
has_many :comments
has_many :friends_comments, :through => :friends, :source => :comments
end
This statement will execute a single SQL statement. Associations essentially create named scopes for you that aren't evaluated until the end of the chain.
#user.friends_comments.find(:limit => 100, :order => 'created_at DESC')
If this is a common query, the find can be simplified into its own scope.
class Comments < ActiveRecord::Base
belongs_to :user
#named_scope was renamed to scope in Rails 3.2. If you're working
#if you're working in a previous version uncomment the following line.
#named_scope :recent, :limit => 100, : order => 'created at DESC'
scope :recent, :limit => 100, :order => 'created_at DESC'
end
So now you can do:
#user.friends_comments.recent
N.B.: The friends association on user may be a cyclical one through a join table, but that's not important to this solution. As long as friends is a working association on User, the preceding will work.