ActiveRecordNestedScope

An ActiveRecord extension to build nested scopes through pre-defined associations.

Dependencies

  • ruby 2.3+
  • rails 5.0+ (activerecord and activesupport)

Installation

Add this line to your application's Gemfile:

gem 'activerecord_nested_scope'

Then execute:

$ bundle

Usage

For example, define in_group scope using nested_scope as follows:

class Group < ActiveRecord::Base
  nested_scope :in_group  # root scope
  ...
end

class User < ActiveRecord::Base
  belongs_to :group
  nested_scope :in_group, through: :group  # User belongs to Group
  ...
end

class UserConfig < ActiveRecord::Base
  belongs_to :user
  nested_scope :in_group, through: :user  # UserConfig belongs to Group through User
  ...
end

in_group scope generates SQL as follows:

User.in_group(id: 1)
#=> SELECT "users".* FROM "users" WHERE "users"."group_id" IN (
#     SELECT "groups"."id" FROM "groups" WHERE "groups"."id" = 1)

UserConfig.in_group(id: 1)
#=> SELECT "user_configs".* FROM "user_configs" WHERE "user_configs"."user_id" IN (
#     SELECT "users"."id" FROM "users" WHERE "users"."group_id" IN (
#       SELECT "groups"."id" FROM "groups" WHERE "groups"."id" = 1))

Polymorphic association

If you define a polymorphic association like that,

class Name < ActiveRecord::Base
  belongs_to :data, polymorphic: true
  nested_scope :in_group, through: :data
end

in_group scope generates union of subqueries for each polymorpchic type:

Name.in_group(id: 1)
#=> SELECT "names"."data_type" FROM "names" GROUP BY "names"."data_type"
#   SELECT "names".* FROM (
#     SELECT "names".* FROM "names" WHERE "names"."data_type" = 'Group' AND "names"."data_id" IN (SELECT "groups"."id" FROM "groups" WHERE "groups"."id" = 1) UNION 
#     SELECT "names".* FROM "names" WHERE "names"."data_type" = 'Manager' AND "names"."data_id" IN (SELECT "managers"."id" FROM "managers" WHERE "managers"."id" IN (SELECT "groups"."manager_id" FROM "groups" WHERE "groups"."id" = 1)) UNION 
#     SELECT "names".* FROM "names" WHERE "names"."data_type" = 'Supervisor' AND "names"."data_id" IN (SELECT "supervisors"."id" FROM "supervisors" WHERE "supervisors"."id" IN (SELECT "managers"."supervisor_id" FROM "managers" WHERE "managers"."id" IN (SELECT "groups"."manager_id" FROM "groups" WHERE "groups"."id" = 1) ORDER BY "managers"."id" ASC)) UNION 
#     SELECT "names".* FROM "names" WHERE "names"."data_type" = 'User' AND "names"."data_id" IN (SELECT "users"."id" FROM "users" WHERE "users"."group_id" IN (SELECT "groups"."id" FROM "groups" WHERE "groups"."id" = 1))) AS names

Note that the first SQL is executed to load data_type, and the second SQL is built using loaded data_type variations.

Scope examples

# pass a integer
User.in_group(1)

# pass a hash
User.in_group(id: 1)

# pass an AR relation
User.in_group(Group.where(id: 1))

Contributing

Bug reports and pull requests are welcome at https://github.com/kanety/activerecord_nested_scope.

License

The gem is available as open source under the terms of the MIT License.