PeterB
PeterB

Reputation: 13

Rails Eager loading has_many associations for an existing object

I am fairly new to rails & I am having this performance issue that I would appreciate any help with.

I have a User model & each user has_many UserScores associated. I am preparing a dashboard showing different user stats including counts of user_scores based on certain conditions. Here is a snippet of the code:

def dashboard
@users = Array.new
users = User.order('created_at ASC')
users.each do |u|
    user = {}
    user[:id] = u.id
    user[:name] = u.nickname
    user[:email] = u.email
    user[:matches] = u.user_scores.count
    user[:jokers_used] = u.user_scores.where(:joker => true).length
    user[:jokers] = u.joker
    user[:bonus] = u.user_scores.where(:bonus => 1).length
  user[:joined] = u.created_at.strftime("%y/%m/%d")
    if user[:matches] > 0
        user[:last_activity] = u.user_scores.order('updated_at DESC').first.updated_at.strftime("%y/%m/%d")
    else
        user[:last_activity] = u.updated_at.strftime("%y/%m/%d")
    end
    @users << user
end
@user_count = @users.count

end

The issue I am seeing is repeated UserScore db queries for each user to get the different counts.

Is there a way to avoid those multiple queries??

N.B. I'm not sure if my approach for preparing data for the view is the optimal way, so any advice or tips regarding that will be greatly appreciated as well.

Thanks

Upvotes: 1

Views: 1530

Answers (3)

Slava.K
Slava.K

Reputation: 3080

Firstly, reference user_scores association in your query:

users = User.includes(:user_scores).order('created_at ASC')

Follow rails documentation associations eager loading: http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

Also note that where makes new query to the database even if your association is already preloaded. Therefore, instead of

u.user_scores.where(:joker => true).length
u.user_scores.where(:bonus => 1).length

try:

u.user_scores.count { |us| us.joker }
u.user_scores.count { |us| us.bonus == 1 }

You will probably have to rewrite .user_scores.order('updated_at DESC').first.updated_at.strftime("%y/%m/%d") somehow as well

Upvotes: 0

Parth Modi
Parth Modi

Reputation: 1813

You need to eager load users_scores to reduce multiple queries. @Slava.K provided good explanation on how to eliminate that. Add includes(:user_scores) for querying users, and use ruby's methods to work with collections once data is fetched from DB through query.

See code below to understand that:

users = User.includes(:user_scores).order('created_at ASC')

users.each do |u|
  ....

  user[:matches] = u.user_scores.length
  user[:jokers_used] = u.user_scopes.select{ |score|  score.joker == true }.length
  user[:jokers] = u.joker
  user[:bonus] = u.user_scores.select{ |score|  score.bonus == 1 }.length
  ....
end

Also, The way you are preparing response is not clean and flexible. Instead you should override as_json method to prepare json which can consumed by views properly. as_json method is defined for models by default. You can read more about it from official documentation http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html or visit article on preparing clean json response where I explained about overriding as_json properly in-depth.

Upvotes: 2

Aniket Rao
Aniket Rao

Reputation: 778

Use includes method for eager loading your has many associations. You can understand this concept here: https://www.youtube.com/watch?v=s2EPVMqOsTQ

Upvotes: 1

Related Questions