Search code examples
ruby-on-railsruby-on-rails-5eager-loading

Eager-load an association but ONLY a single column


I have a self-referential association like this:

class User
  belongs_to :parent_user, class_name: "User"
  has_many :child_users, class_name: "User", foreign_key: :parent_user_id
end

When I get a list of users, I would like to be able to eager-load the child_users association, but I only need the id column from it. I need to be able to do something like this without causing n+1 queries, and preferably without having to load all of the other data in this model:

users = User.preload(:child_users)
users.map(&:child_user_ids)

The above works to limit the query to two queries instead of n+1, but I'm loading a lot of full objects into memory that I would prefer to avoid when I only care about the id itself on those child assocations.


Solution

  • You don't want eager loading which is for loading models. You want to select an aggregate. This isn't something the ActiveRecord query interface handles so you'll need to use SQL strings or Arel and use the correct function for your DB.

    For example:

    # This is for Postgres, use JSON_ARRAYAGG on MySQL
    User.left_joins(:child_users)
        .select(
          "users.*",
          'json_agg("child_users_users".id) AS child_ids'
        )
        .group(:id)
    

    child_users_users is the wonky alias that .left_joins(:child_users) creates.

    At least on Postgres the driver (the PG gem) will automatically cast the result to a an array but YMMV on the other dbs.