Order by count of has_many :through items in Rails 3 - ruby-on-rails-3

If I have 3 models:
Model Section
Model User
has_many :votes
Model Vote
belongs_to :user
and inside ModelSection
has_many :users
has_many :votes, :through => :users
How to get the Sections list ordered by votes quantity using the AR associations?

The most reasonable way to do this is to use a subquery written as raw SQL for ordering the result as follows...
Section.order(
'(select count(1) from votes inner join users on votes.user_id=users.id where users.section_id=sections.id)'
)

Section.joins(users: :votes).group(:id).order('COUNT(*)')

Related

Count records which has no association or association of association meets condition

Assume we have three classes:
class Person
belongs_to :group
end
class Group
has_many :people
has_many :tags
end
class Tag
belongs_to :group
end
It is possible, that Person may have group_id = NULL.
What I need, is to COUNT Person records which has group_id = NULL or his Group doesn't have any Tag record with attribute status IN ('active', 'finished')
One more thing, it has to be as fast as possible. We are querying over milions of records.
Ideas? Thanks.

Querying an indirect relationship

I have an app that has Users, Libraries and Books.
Users have many Libraries. Libraries have many Users and Books. But Users and Books do not have a direct relationship. Users can only borrow Books from Libraries.
I'm stuck trying to get the collection of books that belongs to the libraries that a user is a member of. In other words, I want to get all of the books in a library that a given user belongs to.
To be clear, I don't want to go through the borrowing association that currently exists between users and books, because I want all the books in that library, not just the ones currently borrowed by the user.
I've tried to come up with this query for hours using SQL and Squeel, without luck... thanks in advance for your help.
Table_User
user_id user_name library_id membership_id
Table_Library
library_id user_id book_id membership_id
Table_Books
books_id book_name library_id
Table_Membership
membership_id user_id library_id
select book_name from Table_Books
inner join Table_Library on Table_Books.library_id=Table_Library.library_id
inner join Table_User on Table_User.library_id=Table_Library.library_id
where Table_User.library_id = Table_Membership.library_id
and Table_User.user_id =Table_Membership.user_id
You can do something like this...
Book.where(:library_id => user.library_ids)
Or you can do this. Given this model setup...
class Book < ActiveRecord::Base
belongs_to :library
class Library < ActiveRecord::Base
has_many :books
has_many :memberships
has_many :users, :through => :memberships
class User < ActiveRecord::Base
has_many :memberships
has_many :libraries, :through => :memberships
has_many :books, :through => :libraries
With this, you can ask for #user.books, which in Rails will generate SQL like:
# 1 is #user.id
SELECT "books".* FROM "books" INNER JOIN "libraries" ON "books"."library_id" = "libraries"."id" INNER JOIN "memberships" ON "libraries"."id" = "memberships"."library_id" WHERE "memberships"."user_id" = 1

Accessing Join Attributes in Rails 4

I am trying to get all of a user's friends' posts and user info in one query.
I have tried to use includes(:users) but that doesn't seem to work and throws an error. I figured a manual join would be the best solution:
#friends = #user.friends
.joins('inner join users on users.id = friends.user_id left outer join posts on users.id = posts.user_id')
.select('posts.*, users.*, friends.*')
How do you access attributes from users and posts? doing a #friends.inspect only shows attributes from the friends table, and I can't do #friends.posts or #friends.users.
My model for friends and users looks like this:
class Friend < ActiveRecord::Base
belongs_to :users
end
class User < ActiveRecord::Base
has_many :friends
has_many :users, through: :friends
end
According to the SQL in the join clause, your posts are related to users, not to friends. Something like this will work:
friends.map{|f| f.user.posts}.flatten
if Friend had belongs_to :user (singular "user") which would be conventional Rails, and is reflected in the SQL you used, so has the overriding factor of being correct.

Adding eager loading to custom has_many relationships

Groups and users have has_many through relationships on the table user_roles, which specifies what role users have in each group (pending, member, admin, etc). For a given group, how might I return all users matching a particular role in that group such that the code is DRY and eager loads the appropriate associations?
class Group < ActiveRecord::Base
has_many :user_roles, dependent: :destroy
has_many :users, through: :user_roles
def members(role)
self.users.includes(:user_roles).where("user_role.role = ?", role)
# Returns following error message:
# PG::UndefinedTable: ERROR: missing FROM-clause entry for table "user_role"
end
Your table name probably 'user_roles' and not 'user_role'. Change the name of table in your where clause to 'user_roles'.
def members(role)
self.users.includes(:user_roles).where("user_roles.role = ?", role)
end

Active Record optimization: .includes() generates a very slow DISTINCT query, how can I avoid it?

I've got two relations as follows:
class CfThread < ActiveRecord::Base
self.primary_key = 'thread_id'
self.table_name = 'cforum.threads'
belongs_to :forum, class_name: 'CfForum', :foreign_key => :forum_id
has_many :messages, class_name: 'CfMessage', :foreign_key => :thread_id
attr_accessible :thread_id, :tid, :slug, :forum_id, :archived, :created_at, :updated_at
end
class CfMessage < ActiveRecord::Base
has_one :owner, class_name: 'CfUser', :foreign_key => :user_id
has_many :flags, class_name: 'CfFlag'
belongs_to :thread, class_name: 'CfThread', :foreign_key => :thread_id
attr_accessible :message_id, :mid, :thread_id, :subject, :content,
:author, :email, :homepage, :deleted, :user_id, :parent_id,
:updated_at, :created_at
end
I want to query the last 10 threads and its messages without producing a n+1 queries situation:
#threads = CfThread.includes(:messages).order('cforum.threads.created_at DESC').limit(10)
This produces a really slow query using DISTINCT:
SELECT DISTINCT "cforum"."threads".thread_id, cforum.threads.created_at AS alias_0 FROM "cforum"."threads" LEFT OUTER JOIN "cforum"."messages" ON "cforum"."messages"."thread_id" = "cforum"."threads"."thread_id" ORDER BY cforum.threads.created_at DESC LIMIT 10
The query planner want's to do a full table scan on threads AND messages.
With plain SQL it would be very easy to optimize this situation, but is there a way using AR, too? Can I avoid the DISTINCT without producing n+1 queries?
Greetings,
CK
EDIT: Indexes are given as follows:
Threads:
"threads_pkey" PRIMARY KEY, btree (thread_id)
"index_cforum.threads_on_slug" UNIQUE, btree (slug)
"index_cforum.threads_on_archived" btree (archived)
"index_cforum.threads_on_tid" btree (tid)
"threads_created_at_idx" btree (created_at, updated_at)
Messages:
"messages_pkey" PRIMARY KEY, btree (message_id)
"index_cforum.messages_on_mid" btree (mid)
"index_cforum.messages_on_thread_id" btree (thread_id)
I think your order is confusing active record slightly - I think active record thinks it needs the joined columns in order for the order to work. So it's using its join based include variant, which in turn requires a distinct query when you want to limit it.
Obviously the order depends only on the CfThread model, it's just the AR heuristics that are confused. One way would be to force the eager load mode used by doing
CfThread.preload(:messages).order('cforum.threads.created_at DESC').limit(10)