dynamican 0.2.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 718d4f53519e491f005167ae5fa8577ca56d0253142c44b2bc7b4f6d76a714ca
4
- data.tar.gz: 3457c9b4d3bdbc5303a0435fae99e2ae680353b71d9db1a410943081eead9f60
3
+ metadata.gz: cc1cfc7f02f93e35dc7103dc3aeda6f542b87409cc83ea9a4c860a577b0bdc96
4
+ data.tar.gz: dadb49882f3d180376751cce2b1fecac2fd93589152ed89759e5a7a0fe5c2223
5
5
  SHA512:
6
- metadata.gz: 0d77071260975f88bfc627c5a4803d35410ca121f0397ca7ac7a1d2fb4bfa0bc59e86db4847043fdb7906da0342b15d4fea7adbc77beb4890dd81b35e686cedd
7
- data.tar.gz: 75f7f3ee945f3e2d36920632e129d0dd9081dced0803c009013131c607ead455f839a46db4a0cdff0061b645e8b6567e43f9865ecdda59758939c6c23f9fcd51
6
+ metadata.gz: 256b081ed7c6620a65327d9c3473610128a4ebd9e67d8dd2f5d34e1fcc99899697c932e382ddea70249acaab61372a0ab795022cee491341b0d57f77d4967055
7
+ data.tar.gz: '09d697b8c66f6c3257ea7b25451f71d45fd4c8730214bf579b0f4ff996b57ad8049e25b44c60d708258e4cbdbcf4c69d1cf840067f817ea9a66dd70ddd049cf3'
data/README.md CHANGED
@@ -6,33 +6,21 @@ Inside your Gemfile put
6
6
 
7
7
  gem 'dynamican'
8
8
 
9
- and then run `bundle install`
10
-
11
- In each model you want to have the feature, just put the following.
12
-
13
- include Dynamican::Model
14
-
15
- Create `config/dynamican.yml` file in your project and compile it as follows for each of the models you included the code above into (let's say for instance you included Dynamican::Model into User and Role models).
16
-
17
- associations:
18
- role:
19
- class_name: 'Role'
20
- user:
21
- class_name: 'User'
22
-
23
- Once this config file is created, you can launch the following command.
9
+ and run `bundle install`, then you can launch the following command.
24
10
 
25
11
  rails g dynamican_migration
26
12
 
27
- This command will generate a migration file in your project. Inside this migration, the `permission_connectors` table will have a reference column for the models you configured in the config file. If you want to add the feature to a new model after your migrations are already run (and cannot be rollbacked) you need to create a new migration to add the corresponding columns in the `permission_connectors` table.
28
-
29
- Now that you have your migration, just migrate.
13
+ This command will generate a migration file in your project, which you can run with
30
14
 
31
15
  rails db:migrate
32
16
 
17
+ In each model you want to have the feature, just put the following.
18
+
19
+ include Dynamican::Model
20
+
33
21
  ### Using a model permissions on another model
34
22
 
35
- I wanted to have the possibility to assign permissions both directly to my User model and my Role model, so that if the User had one permission and one of its Role had another, the User could benefit from both. So i assigned the feature (as explained above) to both models and then decorated my User model like following.
23
+ I wanted to have the possibility to assign permissions both directly to my User model and my Role model, so that if the User had one permission and one of its Roles had another, the User could benefit from both. So i included the feature into both models and then decorated my User model like following.
36
24
 
37
25
  module Decorators
38
26
  module Models
@@ -40,16 +28,11 @@ I wanted to have the possibility to assign permissions both directly to my User
40
28
  module DynamicanOverrides
41
29
  def self.prepended(base)
42
30
  base.class_eval do
43
- has_many :user_permission_connectors, class_name: 'Dynamican::PermissionConnector'
44
- has_many :user_permissions, class_name: 'Dynamican::Permission', through: :user_permission_connectors, source: :permission
31
+ has_many :user_permissions, class_name: 'Dynamican::Permission', as: :permittable, inverse_of: :permittable, foreign_key: :permittable_id
45
32
  has_many :role_permissions, through: :roles, class_name: 'Dynamican::Permission', source: :permissions
46
33
  end
47
34
  end
48
35
 
49
- def permission_connectors
50
- Dynamican::PermissionConnector.where(id: user_permission_connectors.ids + roles.map(&:permission_connectors).flatten.map(&:id))
51
- end
52
-
53
36
  def permissions
54
37
  Dynamican::Permission.where(id: user_permissions.ids + role_permissions.ids)
55
38
  end
@@ -60,71 +43,80 @@ I wanted to have the possibility to assign permissions both directly to my User
60
43
  end
61
44
  end
62
45
 
63
- I personally have put this in `app/decorators/models/user/dynamican_overrides.rb` (you need to make rails load the folder if); you can make it work as you please but i recommend to keep it separate from the original User model.
64
-
65
- WARNING: if you have done this override, User `permissions` and `permission_connectors` methods are not relations anymore, so methods like `<<` and `create` won't work on them.
46
+ I personally have put this in `app/decorators/models/user/dynamican_overrides.rb` (rails needs to load the folder of course); you can make it work as you please but i recommend to keep it separate from the original User model.
66
47
 
67
48
  ## Usage
68
49
 
69
50
  Now the hard part: the real configuration.
70
51
 
71
- Create one `Dynamican::Permission` for each pair of action-object you need. For example i created CRUD permissions for my models. Let's say, for instance, you have the Order model.
52
+ Create one `Dynamican::Action` for each action you need. For example i created CRUD permissions.
72
53
 
73
- p1 = Dynamican::Permission.create(action: 'create', object_name: 'order')
74
- p2 = Dynamican::Permission.create(action: 'read', object_name: 'order')
75
- p3 = Dynamican::Permission.create(action: 'update', object_name: 'order')
76
- p4 = Dynamican::Permission.create(action: 'delete', object_name: 'order')
54
+ a1 = Dynamican::Action.create(name: 'create')
55
+ a2 = Dynamican::Action.create(name: 'read')
56
+ a3 = Dynamican::Action.create(name: 'update')
57
+ a4 = Dynamican::Action.create(name: 'delete')
77
58
 
78
- To assign one of these permissions to your Role or User, you need to create a `Dynamican::PermissionConnector` like follows.
59
+ Then create one `Dynamican::Item` for each resource you want your permittables to have permissions to act on. This is not mandatory for every permission though: you can also set a permission to do a general action, like `login`. Right now i'm trying to setup permissions for my permittable (Role model) to act on Order model.
79
60
 
80
- role.permission_connectors.create(permission: p1)
61
+ i1 = Dynamican::Item.create(name: 'Order')
81
62
 
82
- Or simply
63
+ NOTE: conventionally, the name should be PascalCase, so there is a `before_validation` hook that classifies the name you are setting.
83
64
 
84
- role.permissions << p1
65
+ Create one `Dynamican::Permission` for each action you want your `role` instance to have permissions for.
85
66
 
86
- Now your Role is considered able to create orders and you can evaluate permissions with `can?` method.
67
+ p1 = Dynamican::Permission.create(action: a1, items: [i1], permittable: role)
68
+ p2 = Dynamican::Permission.create(action: a2, items: [i1], permittable: role)
69
+ p3 = Dynamican::Permission.create(action: a3, items: [i1], permittable: role)
70
+ p4 = Dynamican::Permission.create(action: a4, items: [i1], permittable: role)
87
71
 
88
- role.can? :create, :order
72
+ Now your Role is considered able to create, read, update and destroy orders; you can evaluate permissions with `can?` method.
89
73
 
90
- # Returns true
74
+ role.can? :create, :order
91
75
 
92
- role.can? :read, :order
76
+ # true
93
77
 
94
- # Returns false
78
+ role.can? :else, :order
95
79
 
96
- You can pass as second argument (the object) a symbol, a string, the class itself and also the instance (instances are used for condition evaluations).
97
- If you pass an array of these elements, permissions for all single element will be evaluated. The `can?` method will return true only if permissions to all objects are evaluated positively.
98
- You can also create custom permissions which don't need an object, simply leaving the `object_name` empty. Let's say you want to give permission to a certain user to dance.
80
+ # false
99
81
 
100
- p5 = Dynamican::Permission.create(action: 'dance')
82
+ You can pass as second argument (the item) a symbol, a string, the class itself and also the instance (instances are required for condition evaluations).
83
+ If you pass an array of these elements, permissions for all single element will be evaluated. The `can?` method will return true only if permissions to all items are evaluated positively.
84
+ You can also create custom permissions which don't need an item, (item is not required for permission creation). Let's say you want to give permission to a certain `user` to dance.
101
85
 
102
- user.permissions << p5
86
+ action_dance = Dynamican::Action.create(name: 'dance')
87
+ p5 = Dynamican::Permission.create(action: action_dance, permittable: user)
103
88
 
104
89
  Call the `can?` method without any second argument
105
90
 
106
91
  user.can? :dance
107
92
 
108
- # Returns true
93
+ # true
109
94
 
110
95
 
111
96
  ### Conditions
112
97
 
113
- You can link a `Dynamican::PermissionConnector` to as many conditions as you want. In order to evaluate its conditions, the `conditional` property of the permission_connector needs to be set as `true` (default to `false`)
98
+ You can link a `Dynamican::Permission` to as many conditions as you want. A permission having at least one condition linked to is considered conditional (Permission class have `conditional` and `unconditional` scopes)
114
99
 
115
100
  Conditions are created like this.
116
101
 
117
- permission_connector.conditions.create(statement: '@user.orders.count < 5')
118
- permission_connector.conditions.create(statement: '@object.field.present?')
102
+ permission.conditions.create(statement: '@user.orders.count < 5')
103
+ permission.conditions.create(statement: '@item.field.present?')
119
104
 
120
105
  You can store in conditions statements whatever conditions you like in plain ruby and the string will be evaulated. Inside the statements, object have to be called as instance variables. These instance variables indeed need to be present and can be declared in a few ways.
121
106
 
122
- 1. The model name of the instance you called `can?` from, like the user, will be defined automatically based on model name, so if you call `user.can?` you will have `@user` variable defined.
123
- 2. The object you pass as second argument (which should match the `object_name` of your permission) will be defined as `@object`, so if you call `user.can? :read, @order` you will have `@object` variable defined containing your `@order`.
107
+ 1. The model name of the instance you called `can?` from, like the user, will be defined automatically based both on a fixed name and on model name, so if you call `user.can?` you will have `@subject` variable (this is fixed) and `@user` variable defined because user is of class `User` (namespaces get cut out, so `Something::User` still turne into `@user`).
108
+ 2. The same thing happens with the item: it will be defined both as `@item` and as the name of its class, so if you call `user.can? :read, @order` you will have `@item` and `@order` variable defined containing your `@order` object.
124
109
  3. You can pass as third argument an hash of objects like this `user.can? :read, @order, time: Time.zone.now, whatever: @you_want` and you will have `@time` and `@whatever` variables defined.
125
110
 
126
111
  WARNING: since the condition statement gets evaluated, i recommend not to allow anyone except project developers to create conditions, in order to prevent malicious code from being executed.
127
112
 
128
- If one `Dynamican::PermissionConnector` is linked to many conditions, the model will be allowed to make that action only if all conditions are true. If you want to set alternative conditions, you should store the `or` conditions inside the same condition statement, like this:
113
+ If one `Dynamican::Permission` is linked to many conditions, the model will be allowed to make that action only if all conditions are true. If you want to set alternative conditions, you should store the `or` conditions inside the same condition statement, like this:
129
114
 
130
115
  condition.statement = '@user.nice? || @user.polite?'
116
+
117
+ ### Permission scopes
118
+
119
+ You can apply the scope `for_action(action_name)` to Permission to find permissions bound to a specific action.
120
+ There is a `for_item(item_name)` scope, which turns to string and then classifies automatically the argument to match it with the classified item name. The scope filters all Permission records that have an item with the specified name in its items list.
121
+ There is also a `without_item` scope to filter records that are not linked to any item.
122
+ As mentioned before, you can also use `conditional` and `unconditional` scopes to find objects with or without any condition attached.
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'dynamican'
3
- s.version = '0.2.1'
4
- s.date = '2020-04-18'
3
+ s.version = '1.0.0'
4
+ s.date = '2020-09-10'
5
5
  s.summary = "Dynamic permissions"
6
6
  s.description = "Dynamic and flexible database configurable permissions for your application"
7
7
  s.authors = ["Valerio Bellaveglia"]
@@ -1,7 +1,7 @@
1
- require 'dynamican/configuration'
2
1
  require 'dynamican/evaluator'
3
2
  require 'dynamican/model'
4
- require 'models/dynamican/condition'
5
- require 'models/dynamican/permission_connector'
6
3
  require 'models/dynamican/permission'
4
+ require 'models/dynamican/action'
5
+ require 'models/dynamican/item'
6
+ require 'models/dynamican/condition'
7
7
  require 'generators/dynamican_migration_generator'
@@ -1,42 +1,41 @@
1
1
  module Dynamican
2
2
  class Evaluator
3
- attr_reader :subject, :action, :object, :object_name, :conditions_instances
3
+ attr_reader :subject, :action, :item, :item_name, :conditions_instances
4
4
 
5
- def initialize(subject, action, object, conditions_instances = {})
5
+ def initialize(subject, action, item, conditions_instances = {})
6
6
  @subject = subject
7
7
  @action = action
8
- @object = object
9
- @object_name = calculate_object_name
8
+ @item = item
9
+ @item_name = calculate_item_name
10
10
  @conditions_instances = conditions_instances
11
11
  end
12
12
 
13
13
  def evaluate
14
14
  set_instance_variables
15
15
 
16
- matching_connectors = subject.permission_connectors.for_action(action).for_object(object_name)
17
- matching_conditions_statements = matching_connectors.conditional.map(&:conditions).flatten.map(&:statement)
16
+ matching_permissions = item.present? ? subject.permissions.for_action(action).for_item(item_name) : subject.permissions.for_action(action).without_item
17
+ matching_permissions_statements = matching_permissions.conditional.map(&:conditions).flatten.map(&:statement)
18
18
 
19
- matching_connectors.unconditional.any? ||
20
- matching_conditions_statements.any? && matching_conditions_statements.map { |statement| eval statement }.all?
19
+ matching_permissions.unconditional.any? ||
20
+ matching_permissions_statements.any? && matching_permissions_statements.map { |statement| eval statement }.all?
21
21
  end
22
22
 
23
23
  private
24
24
 
25
25
  def set_instance_variables
26
26
  instance_variable_set("@#{subject.class.name.demodulize.underscore}", subject)
27
+ instance_variable_set("@#{item.class.name.demodulize.underscore}", item)
27
28
 
28
- conditions_instances.each do |instance_name, instance_object|
29
- instance_variable_set("@#{instance_name}", instance_object)
29
+ conditions_instances.each do |instance_name, instance_item|
30
+ instance_variable_set("@#{instance_name}", instance_item)
30
31
  end
31
32
  end
32
33
 
33
- def calculate_object_name
34
- if object.class.in? [Symbol, String, Class]
35
- object.to_s.downcase
36
- elsif object.is_a?(NilClass)
37
- nil
34
+ def calculate_item_name
35
+ if item.class.in? [Symbol, String, Class]
36
+ item.to_s.classify
38
37
  else
39
- object.class.name.demodulize.underscore
38
+ item.class.name.demodulize
40
39
  end
41
40
  end
42
41
  end
@@ -3,15 +3,14 @@ module Dynamican
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- has_many :permission_connectors, class_name: 'Dynamican::PermissionConnector'
7
- has_many :permissions, class_name: 'Dynamican::Permission', through: :permission_connectors, source: :permission
6
+ has_many :permissions, class_name: 'Dynamican::Permission', as: :permittable, dependent: :destroy
8
7
  end
9
8
 
10
- def can?(action, object = nil, conditions_instances = {})
11
- if object.respond_to? :each
12
- object.all? { |single_object| can? action, single_object }
9
+ def can?(action, item = nil, conditions_instances = {})
10
+ if item.respond_to? :each
11
+ item.all? { |single_item| can? action, single_item }
13
12
  else
14
- Dynamican::Evaluator.new(self, action, object, conditions_instances).evaluate
13
+ Dynamican::Evaluator.new(self, action, item, conditions_instances).evaluate
15
14
  end
16
15
  end
17
16
  end
@@ -11,49 +11,43 @@ class DynamicanMigrationGenerator < Rails::Generators::Base
11
11
  def migration_data
12
12
  <<MIGRATION
13
13
  class DynamicanMigration < ActiveRecord::Migration[5.2]
14
- # 0.1.2 Release
15
14
  def change
16
15
  unless table_exists? :permissions
17
- create_table :conditions do |t|
18
- t.string :description
19
- t.string :statement
16
+ create_table :permissions do |t|
17
+ t.bigint :permittable_id
18
+ t.string :permittable_type
19
+ t.bigint :action_id
20
20
 
21
21
  t.timestamps
22
22
  end
23
23
 
24
- create_table :permissions do |t|
25
- t.string :action
26
- t.string :object_name
24
+ create_table :actions do |t|
25
+ t.string :name
27
26
 
28
27
  t.timestamps
29
28
  end
30
29
 
31
- create_table :permission_connectors do |t|
32
- #{create_migration_associations_data}
33
- t.references :permission
34
- t.boolean :conditional, default: false
30
+ create_table :items do |t|
31
+ t.string :name
35
32
 
36
33
  t.timestamps
37
34
  end
38
35
 
39
- create_table :conditions_permission_connectors do |t|
40
- t.bigint :condition_id
41
- t.bigint :permission_connector_id
36
+ create_table :conditions do |t|
37
+ t.bigint :permission_id
38
+ t.string :statement
39
+ t.string :description
40
+
41
+ t.timestamps
42
+ end
43
+
44
+ create_table :items_permissions do |t|
45
+ t.bigint :item_id
46
+ t.bigint :permission_id
42
47
  end
43
48
  end
44
49
  end
45
50
  end
46
51
  MIGRATION
47
52
  end
48
-
49
- def create_migration_associations_data
50
- migration_associations_data = ""
51
- associations = Dynamican.configuration.associations.keys
52
-
53
- associations.each do |association|
54
- migration_associations_data += "t.references :#{association}#{"\n " unless association == associations.last}"
55
- end
56
-
57
- migration_associations_data
58
- end
59
53
  end
@@ -0,0 +1,9 @@
1
+ module Dynamican
2
+ class Action < ActiveRecord::Base
3
+ has_many :permissions, class_name: 'Dynamican::Permission', inverse_of: :action, foreign_key: :action_id, dependent: :destroy
4
+
5
+ validates :name, presence: true, uniqueness: true
6
+
7
+ attr_readonly :name
8
+ end
9
+ end
@@ -1,5 +1,7 @@
1
1
  module Dynamican
2
2
  class Condition < ActiveRecord::Base
3
- has_and_belongs_to_many :permission_connectors, class_name: 'Dynamican::PermissionConnector', foreign_key: :condition_id
3
+ belongs_to :permission, class_name: 'Dynamican::Permission', inverse_of: :conditions, foreign_key: :permission_id
4
+
5
+ validates_presence_of :statement
4
6
  end
5
7
  end
@@ -0,0 +1,15 @@
1
+ module Dynamican
2
+ class Item < ActiveRecord::Base
3
+ has_and_belongs_to_many :permissions, class_name: 'Dynamican::Permission', inverse_of: :items, foreign_key: :item_id, dependent: :destroy
4
+
5
+ validates :name, presence: true, uniqueness: true
6
+
7
+ attr_readonly :name
8
+
9
+ before_validation :classify_name
10
+
11
+ def classify_name
12
+ self.name = self.name.classify
13
+ end
14
+ end
15
+ end
@@ -1,10 +1,14 @@
1
1
  module Dynamican
2
2
  class Permission < ActiveRecord::Base
3
- has_many :permission_connectors, class_name: 'Dynamican::PermissionConnector', inverse_of: :permission, foreign_key: :permission_id
3
+ belongs_to :permittable, polymorphic: true
4
+ belongs_to :action, class_name: 'Dynamican::Action', inverse_of: :permissions, foreign_key: :action_id
5
+ has_and_belongs_to_many :items, class_name: 'Dynamican::Item', inverse_of: :permissions, foreign_key: :permission_id
6
+ has_many :conditions, class_name: 'Dynamican::Condition', inverse_of: :permission, foreign_key: :permission_id
4
7
 
5
- validates_presence_of :action
6
-
7
- scope :for_action, -> (actions) { where(action: actions) }
8
- scope :for_object, -> (object_names) { where(object_name: object_names) }
8
+ scope :for_action, -> (action_name) { joins(:action).where(actions: { name: action_name }) }
9
+ scope :for_item, -> (item_name) { joins(:items).where(items: { name: item_name.to_s.classify }) }
10
+ scope :without_item, -> { left_outer_joins(:items).where(items: { id: nil }) }
11
+ scope :conditional, -> { joins(:conditions) }
12
+ scope :unconditional, -> { left_outer_joins(:conditions).where(conditions: { id: nil }) }
9
13
  end
10
14
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamican
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Valerio Bellaveglia
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-18 00:00:00.000000000 Z
11
+ date: 2020-09-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Dynamic and flexible database configurable permissions for your application
14
- email:
14
+ email:
15
15
  executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
@@ -20,18 +20,18 @@ files:
20
20
  - README.md
21
21
  - dynamican.gemspec
22
22
  - lib/dynamican.rb
23
- - lib/dynamican/configuration.rb
24
23
  - lib/dynamican/evaluator.rb
25
24
  - lib/dynamican/model.rb
26
25
  - lib/generators/dynamican_migration_generator.rb
26
+ - lib/models/dynamican/action.rb
27
27
  - lib/models/dynamican/condition.rb
28
+ - lib/models/dynamican/item.rb
28
29
  - lib/models/dynamican/permission.rb
29
- - lib/models/dynamican/permission_connector.rb
30
30
  homepage: https://github.com/ValerioBellaveglia/Dynamican
31
31
  licenses:
32
32
  - MIT
33
33
  metadata: {}
34
- post_install_message:
34
+ post_install_message:
35
35
  rdoc_options: []
36
36
  require_paths:
37
37
  - lib
@@ -46,8 +46,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  requirements: []
49
- rubygems_version: 3.0.2
50
- signing_key:
49
+ rubygems_version: 3.1.2
50
+ signing_key:
51
51
  specification_version: 4
52
52
  summary: Dynamic permissions
53
53
  test_files: []
@@ -1,17 +0,0 @@
1
- module Dynamican
2
- def self.configuration
3
- @configuration ||= Configuration.new(configuration_hash)
4
- end
5
-
6
- def self.configuration_hash
7
- @configuration_hash ||= HashWithIndifferentAccess.new(YAML.load_file('config/dynamican.yml'))
8
- end
9
-
10
- class Configuration
11
- attr_accessor :associations
12
-
13
- def initialize(configuration_hash)
14
- @associations = configuration_hash[:associations]
15
- end
16
- end
17
- end
@@ -1,14 +0,0 @@
1
- module Dynamican
2
- class PermissionConnector < ActiveRecord::Base
3
- Dynamican.configuration.associations.each do |association_name, association_options|
4
- belongs_to association_name.to_sym, class_name: association_options[:class_name], inverse_of: :permission_connectors, foreign_key: "#{association_name}_id".to_sym, optional: true
5
- end
6
- has_and_belongs_to_many :conditions, class_name: 'Dynamican::Condition', inverse_of: :permission_connectors, foreign_key: :permission_connector_id
7
- belongs_to :permission, class_name: 'Dynamican::Permission', inverse_of: :permission_connectors, foreign_key: :permission_id
8
-
9
- scope :conditional, -> { where(conditional: true) }
10
- scope :unconditional, -> { where(conditional: false) }
11
- scope :for_action, -> (action) { joins(:permission).where(permissions: { action: action }) }
12
- scope :for_object, -> (object_name) { joins(:permission).where(permissions: { object_name: object_name }) }
13
- end
14
- end