Ordering records by the number of associated records - sql

Users can submit resources and post comments.
I want to show the users who are most 'active' by selecting the users who have submitted resources and comments and order the results from the user that has submitted the most resources and comments combined to the least.
**Resource**
has_many :users, :through => :kits
has_many :kits
belongs_to :submitter, class_name: "User"
**User**
has_many :resources, :through => :kits
has_many :kits
has_many :submitted_resources, class_name: "Resource", foreign_key: "submitter_id"
**Kits**
belongs_to :resource
belongs_to :user
**Comments**
belongs_to :user
I am new to this kind of sql in Rails. How can I get this record set?

First, you will need to add the comments association to the User model:
has_many :comments
With this, the simplest solution is to do this:
User.all.sort do |a,b|
(a.submitted_resources.count + a.comments.count) <=> (b.submitted_resources.count + b.comments.count)
end
This will get very slow, so if you want to do better you will want to add counter caches. In a migration:
def up
add_column :users, :submitted_resources_count, :integer
add_column :users, :comments_count, :integer
User.reset_column_information
User.find_each do |u|
u.update_attributes! \
:submitted_resources_count => u.submitted_resources.count,
:comments_count => u.comments.count
end
end
def down
add_column :users, :submitted_resources_count
add_column :users, :comments_count
end
Once you run this migration, you can change the original query to:
User.select('*, (submitted_resources_count + comments_count) AS activity_level').order('activity_level DESC')
This will very efficiently return all users in the proper order, and as a bonus each user will have a read-only attribute called activity_level that will give the exact submitted resources + comments count.

Related

Selecting Areas for Sales Reps

I have an application that is for sales reps.
I'm trying to pull different institutions information based on 2 things
If they are a client
If they are in the state that the user (sales rep) is over.
So I want to show all the institutions that are clients in the current_user (sales reps) area. How can I do that?
Since I haven't done this before and I'm newer to rails I'm not sure how to do this.
Here are my models (I've shortened the code):
User(Sales Rep)
class User < ActiveRecord::Base
has_many :states, :through => :rep_areas
has_many :institutions, :through => :company_reps
has_many :rep_areas
has_many :company_reps
end
States
class State < ActiveRecord::Base
has_many :users, :through => :rep_areas
has_many :rep_areas
has_many :institutions
attr_accessible :code, :name
end
Institutions
class Institution < ActiveRecord::Base
attr_accessible :company, :phone, :clientdate, :street, :city, :state_id, :zip, :source, :source2, :demodate1, :demodate2, :demodate3, :client, :prospect, :notcontacted
belongs_to :state
has_many :users, :through => :company_reps
has_many :company_reps
end
I'd suggest to proceed in this way:
states = current_user.states.to_a
# the following are all the Institution record in all the user's areas
inst_in_states = Institution.where(state_id: states)
# it will take an array and make an "IN" query
# the following are all the user's own clients, additionally on the states
# the user is in.
clients_in_states = current_user.institutions.where(state_id: states)
# as above, but additionally use the :company_reps join

What's the rails way to include a field in a join model when listing an association?

So if I have the following relationship
class Item < ActiveRecord::Base
has_many :item_user_relationships
has_many :users, :through => :item_user_relationships
end
class User < ActiveRecord::Base
has_many :item_user_relationships
has_many :items, :through => :item_user_relationships
end
class ItemUserRelationship < ActiveRecord::Base
belongs_to :item
belongs_to :user
attr_accessible :role
end
What's the rails way to include the role attribute when listing all the Users of an Item?
#users = #item.users # I want to include the role as part of a user
Thanks!
UPDATE: I'm still having trouble with this. My goal is to get an array of User models that have their role included in the attributes.
I'm note sure if I understand you correctly, but maybe this is what you want?
#users = #item.users
#users.each do |user|
puts user.item_user_relationships.first.role
end

How to sort by created_at column of association in rails?

Here are my associations:
Class Post
belongs_to :user
has_many :favorites, :dependent => :destroy
has_many :favoriters, :through => :favorites, :source => :user
end
Class User
has_many :posts
has_many :favorites, :dependent => :destroy
has_many :favorited, :through => :favorites, :source => :post
end
Class Favorites
belongs_to :user, :post
end
I want to sort users' favorite posts by the created_at column of the Favorites association. However, this sorts by the Post created_at attribute, not the Favorites created_at attribute. How can I sort by the Favorites created_at attribute?
#posts=#user.favorited.order('created_at DESC')
You need to specify which table you want to use in the order by clause.
#posts = #user.favorited.order('posts.created_at DESC')
ought to do it.
One nice trick is to use the rails console when inspecting associations. Specifically, it helps to use the 'to_sql' method on Active Record queries you are performing.
For instance:
% bundle exec rails console
> u = User.last
> u.favorited.order('created_at DESC').to_sql
use this in your post model for set default order:
default_scope { order("created_at DESC") }

Rails includes nested relations

I need to query all posts from a specific user and include all comments and the user who belongs to the comment.
class User < ...
has_many :posts
has_many :comments
end
class Post < ...
belongs_to :user
has_many :comments
end
class Comment < ...
belongs_to :user
belongs_to :post
end
#posts = current_user.posts.include(:comments)
Is is possible to also get the comment user? I list a lot of posts and comments. I do not want to query each comment user.
Thx / Tobias
Try
#posts = current_user.posts.includes( :comments => :user)
Read more about it here
How about include at the relation definition statement?
:include
Specify second-order associations that should be eager loaded when this object is loaded.
class Post <
belongs_to :user
has_many :comments, :include => [:user], :limit => 5
end

Avoiding individual database calls for count

My models look like this:
class Movie < ActiveRecord::Base
attr_accessible :title, :year, :rotten_id, :audience_score,
:critics_score, :runtime, :synopsis, :link, :image
has_many :jobs, :dependent => :destroy
has_many :actors, :through => :jobs
end
class Actor < ActiveRecord::Base
attr_accessible :name
has_many :movies, :through => :jobs
has_many :jobs, :dependent => :destroy
end
class Job < ActiveRecord::Base
attr_accessible :movie_id, :actor_id
belongs_to :movie
belongs_to :actor
end
When I'm displaying my index of Actors, I'd like to show the number of movies each actor has starred in. I can do this with #actor.movies.count, however this generates an SQL query for each actor. With, say, 30 actors, this will result in 30 extra queries in addition to the initial.
Is there any way to include the count of movies each actor has participated in, in the initial Actor.all call? And thereby getting things done with only one call. Extra bonus if this was sorted by said count.
Update:
All answers provided has been helpful, and though it turned into some dirt-slinging-contest at some point, it worked out well. I did a mish-mash of all your suggestions. I added a movies_counter column to my Actor model. In my Job model I added belongs_to :actor, :counter_cache => :movies_counter. This works brilliantly, and is automatically updated when i create or destroy a movie, without me adding any further code.
As #Sam noticed, you should add new column to actors table movies_counter
rails g migration add_movies_counter_to_actor movies_counter:integer
Now you can edit your migration
class AddMoviesCounterToActor < ActiveRecord::Migration
def self.up
add_column :actors, :movies_counter, :integer, :default => 0
Actor.reset_column_information
Actor.all.each do |a|
a.update_attribute :movies_counter, a.movies.count
end
end
def self.down
remove_column :actors, :movies_counter
end
end
And run it
rake db:migrate
Then you should add two callbacks: after_save and after_destroy
class Movie < ActiveRecord::Base
attr_accessible :title, :year, :rotten_id, :audience_score,
:critics_score, :runtime, :synopsis, :link, :image
has_many :jobs, :dependent => :destroy
has_many :actors, :through => :jobs
after_save :update_movie_counter
after_destroy :update_movie_counter
private
def update_movie_counter
self.actors.each do |actor|
actor.update_attribute(:movie_count, actor.movies.count)
end
end
end
Then you can call some_actor.movies_counter
Add a column to your Actor table called 'movie_count'. Then add a call back in your Actor model that updates that column.
class Movie < ActiveRecord::Base
has_many :actors, :through => :jobs
before_save :update_movie_count
def update_movie_count
self.actor.update_attribute(:movie_count, self.movies.size)
end
end
That way your just have an integer that gets updated instead of calling all records.