From 199ca1e602da831cc06e37d36e820ba26ac65970 Mon Sep 17 00:00:00 2001 From: Sergey Alekseev Date: Wed, 27 Feb 2019 18:03:07 +0300 Subject: [PATCH 1/2] Significantly optimize #without_role (#500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :scissors: remove some obsolete code since https://github.com/RolifyCommunity/rolify/commit/522c0d7d84a70dd3576b569edf19f9f6cb118a61 Rails < 4 is no longer supported * significantly optimize #without_role Imagine a big production application with millions of users. The previous version generates 3 queries for `User.without_role(:admin)`: 1) For `user.all` in [`#all_except`](https://github.com/RolifyCommunity/rolify/blob/522c0d7d84a70dd3576b569edf19f9f6cb118a61/lib/rolify/adapters/active_record/role_adapter.rb#L83): ```sql SELECT "users".* FROM "users" ``` 2) For `self.with_role(role_name, resource)` in [`#without_role`](https://github.com/RolifyCommunity/rolify/blob/522c0d7d84a70dd3576b569edf19f9f6cb118a61/lib/rolify/finders.rb#L8): ```sql SELECT "users".* FROM "users" INNER JOIN "users_roles" ON "users_roles"."user_id" = "users"."id" INNER JOIN "roles" ON "roles"."id" = "users_roles"."role_id" WHERE (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) ``` 3) For `user.where(prime_key => (user.all - excluded_obj).map(&prime_key))` in [`#all_except`](https://github.com/RolifyCommunity/rolify/blob/522c0d7d84a70dd3576b569edf19f9f6cb118a61/lib/rolify/adapters/active_record/role_adapter.rb#L83): ```sql SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, ..., N) ``` where `N` is a number of users without the `admin` role. While a number of values in the `IN` clause for the third query can be huge there is even a worse problem. First, `user.all - excluded_obj` creates an `Array` of users and thenĀ `(user.all - excluded_obj).map(&prime_key)` calls a primary key method on every object in this array. From tests --- Before (3 queries): ```sql SELECT "customers".* FROM "customers" SELECT "customers".* FROM "customers" INNER JOIN "customers_privileges" ON "customers_privileges"."customer_id" = "customers"."id" INNER JOIN "privileges" ON "privileges"."id" = "customers_privileges"."privilege_id" WHERE (((privileges.name = 'admin') AND (privileges.resource_type IS NULL) AND (privileges.resource_id IS NULL))) SELECT "customers".* FROM "customers" WHERE "customers"."id" IN (2, 3, 4) ``` After (1 query with a subquery): ```sql SELECT "customers".* FROM "customers" WHERE ("customers"."id" NOT IN (SELECT "customers"."id" FROM "customers" INNER JOIN "customers_privileges" ON "customers_privileges"."customer_id" = "customers"."id" INNER JOIN "privileges" ON "privileges"."id" = "customers_privileges"."privilege_id" WHERE (((privileges.name = 'admin') AND (privileges.resource_type IS NULL) AND (privileges.resource_id IS NULL))))) ``` --- lib/rolify/adapters/active_record/resource_adapter.rb | 2 +- lib/rolify/adapters/active_record/role_adapter.rb | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/rolify/adapters/active_record/resource_adapter.rb b/lib/rolify/adapters/active_record/resource_adapter.rb index 26487c63..aa18c3c7 100644 --- a/lib/rolify/adapters/active_record/resource_adapter.rb +++ b/lib/rolify/adapters/active_record/resource_adapter.rb @@ -40,7 +40,7 @@ def applied_roles(relation, children) def all_except(resource, excluded_obj) prime_key = resource.primary_key.to_sym - resource.where(prime_key => (resource.all - excluded_obj).map(&prime_key)) + resource.where.not(prime_key => excluded_obj.pluck(prime_key)) end private diff --git a/lib/rolify/adapters/active_record/role_adapter.rb b/lib/rolify/adapters/active_record/role_adapter.rb index 4c670fdc..011a0411 100644 --- a/lib/rolify/adapters/active_record/role_adapter.rb +++ b/lib/rolify/adapters/active_record/role_adapter.rb @@ -68,19 +68,13 @@ def exists?(relation, column) end def scope(relation, conditions) - if Rails.version < "4.0" - query = relation.scoped - else - query = relation.all - end - query = query.joins(:roles) + query = relation.joins(:roles) query = where(query, conditions) query end def all_except(user, excluded_obj) - prime_key = user.primary_key.to_sym - user.where(prime_key => (user.all - excluded_obj).map(&prime_key)) + user.where.not(user.primary_key => excluded_obj) end private From bb3d445cf8ac7ee5790daf2a7e1fd82a35da1615 Mon Sep 17 00:00:00 2001 From: maerch Date: Wed, 27 Feb 2019 23:05:04 +0100 Subject: [PATCH 2/2] Add `#has_strict_role?` documentation (#498) --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 4a48cdd5..66d00d26 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,17 @@ user.has_role? :moderator, Forum.last => true ``` +To check if a user has the exact role scoped to a resource class: + +```ruby +user = User.find(5) +user.add_role :moderator # sets a global role +user.has_role? :moderator, Forum.first +=> true +user.has_strict_role? :moderator, Forum.last +=> false +``` + ### 6. Resource roles querying Starting from rolify 3.0, you can search roles on instance level or class level resources.