3

In my sidebar I display the freshly created user profiles. Profile belongs_to user and user has_one_profile. I realized that I only use 3 columns from the profile table so it would be better to use pluck. I also have a link_to user_path(profile.user) in the partial, so somehow I have to tell who the user is. At the moment I'm using includes, but I don't need the whole user table. So I use to many columns both from the user and the profile tables.

How can I optimize this with pluck? I tried a few versions, but always got some error (most of the time profile.user is not defined).

My current code:

def set_sidebar_users
  @profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3) if user_signed_in?
end

create_table "profiles", force: :cascade do |t|
  t.integer  "user_id",      null: false
  t.string   "first_name",   null: false
  t.string   "last_name",    null: false
  t.string   "company",      null: false
  t.string   "job_title",    null: false
  t.string   "phone_number"
  t.text     "description"
  t.datetime "created_at"
  t.datetime "updated_at"
  t.string   "avatar"
  t.string   "location"
end
Sean Magyar
  • 2,360
  • 1
  • 25
  • 57
  • 1
    Have you tried something like `Profile.order(created_at: :desc).limit(3).pluck(:'users.name')`? Another possible answer could be to do your first query (without the includes), and then do `@profiles_sidebar.users.pluck(:username)`. I also found two threads that might help you with this question: [this](http://stackoverflow.com/a/18131149/2929693) and [this](http://stackoverflow.com/a/26538974/2929693). – D. Visser Mar 21 '16 at 11:00
  • I think you don't need to use pluck here. If you wanna eager load the association + load selected columns only then there is a way to do it. Secondly if you need only selected clumns from both of the table then simply use joins instead of includes. lemme write an answer – Qaisar Nadeem Mar 21 '16 at 11:07

1 Answers1

7

Okay let's explain three different way to accomplish what you are looking for.

First of all there is a difference in includes and joins Includes just eager load the association with all of the specified columns for associations. It does not allow you to query or select multiple columns from both table. It what joins do . It allow you to query both tables and select columns of your choice.

 def set_sidebar_users
  @profiles_sidebar = Profile.select("profiles.first_name,profiles.last_name,profiles.id,users.email as user_email,user_id").joins(:user).order("profile.created_at desc").limit(3) if user_signed_in?
end

It will return you the Profiles relation which has all of the columns you provided in select clause. You can get them just like you do for profile object e-g

@profiles_sidebar.first.user_email will give you user email for this profile.

This approach is best if you want to query on multiple tables or wanna select multiple columns from both table.

2.Pluck

def set_sidebar_users
  @profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3).pluck("users.email,profiles.first_name") if user_signed_in?
end

Pluck is just used to get columns from multiple associations but it does not allow you to use the power of ActiveRecord. It simply returns you the array of selected columns in same order. like in the first example you can get the user for profile object with @profiles_sidebar.first.user But with pluck you cannot because it's just a plain array. So that's why your most of the solutions raise error profile.user is not defined

  1. Association with selected columns.

Now this is option three. In first solution you can get multiple columns on both tables and use the power of ActiveRecord but it does not eager load the associations. So it will still cost you N+1 queries if you loop through the association on returned result like @profiles_sidebar.map(&:user)

So if you wanna use includes but want to use selected columns then you should have new association with selected columns and call that association. e-g In profile.rb

belongs_to :user_with_selected_column,select: "users.email,users.id"

Now you can include it in above code

def set_sidebar_users
  @profiles_sidebar = Profile.order(created_at: :desc).includes(:user_with_selected_column).limit(3) if user_signed_in?
end

Now this will eager load users but will select only email and id of user. More information can be found on ActiveRecord includes. Specify included columns

UPDATE

As you asked about the pros for pluck so let's explain it. As you know pluck returns you the plain array. So it does not instantiate ActiveRecord object it simply returns you the data returned from database. So pluck is best to use where you don't need ActiveRecord Objects but just to show the returned data in tabular form. Select returns you the relations so you can further query on it or call the model methods on it's instances. So if we summaries it we can say pluck for model values, select for model objects

More informations can be found at http://gavinmiller.io/2013/getting-to-know-pluck-and-select/

Community
  • 1
  • 1
Qaisar Nadeem
  • 2,404
  • 13
  • 23
  • Nice explanation Qaisar. One more question: The main point of pluck is only return the necessary columns. I know that. But here `select` seems to be doing the same. So what is the competititve advantage of pluck over the rest? For instance: `Profile.select("profiles.first_name,profiles.last_name,profiles.id,users.email as user_email,user_id")` vs `Profile.pluck("profiles.first_name,profiles.last_name,profiles.id,users.email as user_email,user_id")` – Sean Magyar Mar 21 '16 at 11:32
  • 1
    I add the pros and cons in the answer for future reference – Qaisar Nadeem Mar 21 '16 at 12:24