Chet
Chet

Reputation: 144

Active Model Serializer object not returning all association records (has many: through relationship)

So I have User&Role models which is linked to UserRole model using has_many :through association. And im using a serializer for User. Note: The user has many roles. But i'm not able to get all the roles in the serializer for some reason. This is the query,

unless params['roles'].blank?
  render json: User.includes(:roles)
                    .references(:user_roles)
                    .where('roles.name IN (?)', params['roles'])
else
  render json: User.all
end

And in my User serializer I have

attributes :id, :name, :email, :roles

def roles
  object.roles.pluck(:name)
end

This thing is, I have a user with both "admin","author" roles. When I pass "admin" as params, Output json object just has roles: ["admin"] for that user. Upon debugging, for this object, object.roles.count is 2 BUT when I do object.roles, it shows only 1 record. What's happening?

When I do User.find(object.id).roles.pluck(:name), this works. But this runs queries in the serializer loop..

The output response is perfectly fine for User.all withOUT params. (getting roles: ["author", "admin"]). The problem is when I pass a param. Perhaps something wrong with my query?

Upvotes: 0

Views: 456

Answers (1)

max
max

Reputation: 102036

Since the where clause limits the loaded records you may need to use a subquery:

User.includes(:roles).where(
  id: User.joins(:roles).where(roles: { name: params[:roles] })
)

If you look at the SQL generated you can see that the where clause here only applies to the subquery - thus all the associated roles are loaded and not just the ones that match WHERE "roles"."name" = $1.

  User Load (3.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" IN (SELECT "users"."id" FROM "users" INNER JOIN "user_roles" ON "user_roles"."user_id" = "users"."id" INNER JOIN "roles" ON "roles"."id" = "user_roles"."role_id" WHERE "roles"."name" IN ($1, $2, $3)) LIMIT $4  [["name", "admin"], ["name", "foo"], ["name", "bar"], ["LIMIT", 11]]
  UserRole Load (0.6ms)  SELECT "user_roles".* FROM "user_roles" WHERE "user_roles"."user_id" = $1  [["user_id", 1]]
  Role Load (0.5ms)  SELECT "roles".* FROM "roles" WHERE "roles"."id" = $1  [["id", 1]]

Also if you want to use the loaded records you need to use map not pluck as pluck by design creates a separate select query.

def roles
  object.roles.map(&:name)
end

Upvotes: 1

Related Questions