Home > Blockchain >  In Rails, how do I query two has_many associations in a single finder?
In Rails, how do I query two has_many associations in a single finder?

Time:12-20

I’m using Rails 4.2. I have the following user model with a couple of has_many associations

class User < ActiveRecord::Base
    …
  has_many :roles, through: :roles_users
  has_many :addresses, dependent: :destroy, as: :addressable, inverse_of: :addressable


class Role < ActiveRecord::Base

  has_and_belongs_to_many :users
  has_many :roles_users


class RolesUser < ActiveRecord::Base
  belongs_to :user
  belongs_to :role

end


class Address < ActiveRecord::Base

  belongs_to :addressable, polymorphic: true
  alias :user :addressable

I would like to find all users of a specific role without any addresses. I thought the below would do it

> users = User.includes(:roles, :addresses).where(:roles => {:name => 'User'}, :addresses => {:user_id => nil})

But when I check the results, I’m still getting results that have addresses …

2.7.1 :012 > users.last.addresses.count
…
 => 2

What’s the proper way to write a finder that queries these two has_many associations?

CodePudding user response:

Checking for children records with a nil parent id is like the way of doing this in Rails 4. But if that doesn't work, you could use the NOT IN clause combination:

User
  .where
  .not(
    id: User
          .joins(:addresses, :roles)
          .where(roles: { name: 'User' })
          .select(:id)
  )

It's basically filtering out by the user id all those user rows that have an address associated, have a role, and the role name is exactly "User". You end up with a SQL query like this:

SELECT "users".*
FROM "users"
WHERE "users"."id" NOT IN (
  SELECT "users"."id"
  FROM "users"
  INNER JOIN "roles_users" ON "roles_users"."user_id" = "users"."id"
  INNER JOIN "roles" ON "roles"."id" = "roles_users"."role_id"
  WHERE "roles"."name" = 'User'
)
  • Related