canard 0.5.0.pre → 0.6.0.pre
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 +5 -5
- data/.hound.yml +3 -0
- data/.rubocop.yml +27 -0
- data/.rubocop_todo.yml +37 -0
- data/.travis.yml +1 -1
- data/Gemfile +7 -6
- data/README.md +40 -37
- data/Rakefile +5 -2
- data/canard.gemspec +8 -7
- data/lib/ability.rb +14 -13
- data/lib/canard.rb +4 -2
- data/lib/canard/abilities.rb +7 -9
- data/lib/canard/adapters/active_record.rb +32 -29
- data/lib/canard/adapters/mongoid.rb +18 -11
- data/lib/canard/find_abilities.rb +8 -9
- data/lib/canard/railtie.rb +11 -16
- data/lib/canard/user_model.rb +66 -67
- data/lib/canard/version.rb +3 -1
- data/lib/generators/ability_definition.rb +16 -14
- data/lib/generators/canard/ability/ability_generator.rb +16 -12
- data/lib/generators/rspec/ability/ability_generator.rb +9 -9
- data/lib/tasks/canard.rake +6 -6
- data/test/abilities/administrators.rb +2 -2
- data/test/canard/abilities_test.rb +14 -21
- data/test/canard/ability_test.rb +40 -52
- data/test/canard/adapters/active_record_test.rb +71 -135
- data/test/canard/adapters/mongoid_test.rb +61 -132
- data/test/canard/canard_test.rb +8 -10
- data/test/canard/find_abilities_test.rb +9 -11
- data/test/canard/user_model_test.rb +22 -32
- data/test/dummy/Rakefile +3 -1
- data/test/dummy/app/abilities/admins.rb +4 -4
- data/test/dummy/app/abilities/authors.rb +3 -3
- data/test/dummy/app/abilities/editors.rb +2 -2
- data/test/dummy/app/abilities/guests.rb +3 -3
- data/test/dummy/app/abilities/users.rb +4 -4
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/models/activity.rb +3 -1
- data/test/dummy/app/models/member.rb +3 -3
- data/test/dummy/app/models/mongoid_user.rb +5 -3
- data/test/dummy/app/models/plain_ruby_non_user.rb +2 -2
- data/test/dummy/app/models/plain_ruby_user.rb +3 -3
- data/test/dummy/app/models/post.rb +3 -1
- data/test/dummy/app/models/user.rb +3 -2
- data/test/dummy/app/models/user_without_role.rb +4 -4
- data/test/dummy/app/models/user_without_role_mask.rb +3 -3
- data/test/dummy/config.ru +3 -1
- data/test/dummy/config/application.rb +9 -8
- data/test/dummy/config/boot.rb +4 -2
- data/test/dummy/config/environment.rb +3 -1
- data/test/dummy/config/environments/development.rb +2 -0
- data/test/dummy/config/environments/test.rb +4 -2
- data/test/dummy/config/initializers/secret_token.rb +2 -0
- data/test/dummy/config/initializers/session_store.rb +3 -1
- data/test/dummy/config/initializers/wrap_parameters.rb +3 -1
- data/test/dummy/config/mongoid3.yml +5 -1
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/db/migrate/20120430083231_initialize_db.rb +7 -7
- data/test/dummy/db/schema.rb +11 -11
- data/test/dummy/script/rails +4 -2
- data/test/support/reloadable.rb +14 -15
- data/test/test_helper.rb +7 -13
- metadata +18 -18
- data/test/dummy/config/mongoid2.yml +0 -9
@@ -1,51 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Canard
|
2
4
|
module Adapters
|
3
|
-
module ActiveRecord
|
4
|
-
|
5
|
+
module ActiveRecord # :nodoc:
|
5
6
|
private
|
6
7
|
|
7
|
-
def add_role_scopes(
|
8
|
-
options
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
8
|
+
def add_role_scopes(**options)
|
9
|
+
define_scopes(options) if active_record_table_exists?
|
10
|
+
end
|
11
|
+
|
12
|
+
def define_scopes(options)
|
13
|
+
valid_roles.each do |role|
|
14
|
+
define_scopes_for_role role, options[:prefix]
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: change hard coded :role_mask to roles_attribute_name
|
18
|
+
define_singleton_method(:with_any_role) do |*roles|
|
19
|
+
where("#{role_mask_column} & :role_mask > 0", role_mask: mask_for(*roles))
|
20
|
+
end
|
21
|
+
|
22
|
+
define_singleton_method(:with_all_roles) do |*roles|
|
23
|
+
where("#{role_mask_column} & :role_mask = :role_mask", role_mask: mask_for(*roles))
|
24
|
+
end
|
25
|
+
|
26
|
+
define_singleton_method(:with_only_roles) do |*roles|
|
27
|
+
where("#{role_mask_column} = :role_mask", role_mask: mask_for(*roles))
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
+
def active_record_table_exists?
|
31
32
|
respond_to?(:table_exists?) && table_exists?
|
33
|
+
rescue ActiveRecord::NoDatabaseError, StandardError
|
34
|
+
false
|
32
35
|
end
|
33
36
|
|
34
|
-
# TODO extract has_roles_attribute? and change to has_roles_attribute? || super
|
37
|
+
# TODO: extract has_roles_attribute? and change to has_roles_attribute? || super
|
35
38
|
def has_roles_mask_accessors?
|
36
|
-
|
39
|
+
active_record_table_exists? && column_names.include?(roles_attribute_name.to_s) || super
|
37
40
|
end
|
38
41
|
|
39
|
-
def define_scopes_for_role(role, prefix=nil)
|
42
|
+
def define_scopes_for_role(role, prefix = nil)
|
40
43
|
include_scope = [prefix, String(role).pluralize].compact.join('_')
|
41
44
|
exclude_scope = "non_#{include_scope}"
|
42
45
|
|
43
46
|
define_singleton_method(include_scope) do
|
44
|
-
where("#{role_mask_column} & :role_mask > 0",
|
47
|
+
where("#{role_mask_column} & :role_mask > 0", role_mask: mask_for(role))
|
45
48
|
end
|
46
49
|
|
47
50
|
define_singleton_method(exclude_scope) do
|
48
|
-
where("#{role_mask_column} & :role_mask = 0 or #{role_mask_column} is null",
|
51
|
+
where("#{role_mask_column} & :role_mask = 0 or #{role_mask_column} is null", role_mask: mask_for(role))
|
49
52
|
end
|
50
53
|
end
|
51
54
|
|
@@ -54,4 +57,4 @@ module Canard
|
|
54
57
|
end
|
55
58
|
end
|
56
59
|
end
|
57
|
-
end
|
60
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Canard
|
2
4
|
module Adapters
|
3
|
-
module Mongoid
|
4
|
-
|
5
|
+
module Mongoid # :nodoc:
|
5
6
|
private
|
6
7
|
|
7
8
|
def add_role_scopes(*args)
|
@@ -10,34 +11,40 @@ module Canard
|
|
10
11
|
define_scopes_for_role role, options[:prefix]
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
+
define_singleton_method(:with_any_role) do |*roles|
|
14
15
|
where("(this.#{roles_attribute_name} & #{mask_for(*roles)}) > 0")
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
+
define_singleton_method(:with_all_roles) do |*roles|
|
18
19
|
where("(this.#{roles_attribute_name} & #{mask_for(*roles)}) === #{mask_for(*roles)}")
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
+
define_singleton_method(:with_only_roles) do |*roles|
|
22
23
|
where("this.#{roles_attribute_name} === #{mask_for(*roles)}")
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
27
|
def has_roles_mask_accessors?
|
27
|
-
|
28
|
+
fields.include?(roles_attribute_name.to_s) || super
|
28
29
|
end
|
29
30
|
|
30
|
-
def define_scopes_for_role(role, prefix=nil)
|
31
|
+
def define_scopes_for_role(role, prefix = nil)
|
31
32
|
include_scope = [prefix, String(role).pluralize].compact.join('_')
|
32
33
|
exclude_scope = "non_#{include_scope}"
|
33
34
|
|
34
35
|
scope include_scope, -> { where("(this.#{roles_attribute_name} & #{mask_for(role)}) > 0") }
|
35
|
-
scope exclude_scope,
|
36
|
+
scope exclude_scope, lambda {
|
37
|
+
any_of(
|
38
|
+
{ roles_attribute_name => { '$exists' => false } },
|
39
|
+
{ roles_attribute_name => nil },
|
40
|
+
'$where' => "(this.#{roles_attribute_name} & #{mask_for(role)}) === 0"
|
41
|
+
)
|
42
|
+
}
|
36
43
|
end
|
37
44
|
end
|
38
45
|
end
|
39
46
|
end
|
40
47
|
|
41
|
-
Mongoid::Document::ClassMethods.send
|
42
|
-
Mongoid::Document::ClassMethods.send
|
43
|
-
Canard.find_abilities
|
48
|
+
Mongoid::Document::ClassMethods.send(:include, Canard::Adapters::Mongoid)
|
49
|
+
Mongoid::Document::ClassMethods.send(:include, Canard::UserModel)
|
50
|
+
Canard.find_abilities
|
@@ -1,15 +1,16 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Canard # :nodoc:
|
3
4
|
def self.ability_definitions
|
4
5
|
Abilities.definitions
|
5
6
|
end
|
6
7
|
|
7
8
|
def self.ability_key(class_name)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
String(class_name)
|
10
|
+
.gsub('::', '')
|
11
|
+
.gsub(/(.)([A-Z])/, '\1_\2')
|
12
|
+
.downcase
|
13
|
+
.to_sym
|
13
14
|
end
|
14
15
|
|
15
16
|
def self.load_paths
|
@@ -22,7 +23,5 @@ module Canard
|
|
22
23
|
load file
|
23
24
|
end
|
24
25
|
end
|
25
|
-
|
26
26
|
end
|
27
|
-
|
28
|
-
end
|
27
|
+
end
|
data/lib/canard/railtie.rb
CHANGED
@@ -1,21 +1,18 @@
|
|
1
|
-
|
2
|
-
require 'rails'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module Canard
|
5
|
-
class Railtie < Rails::Railtie
|
6
|
-
|
7
|
-
|
8
|
-
ActiveSupport::Dependencies.autoload_paths.reject!{ |path| Canard.load_paths.include?(path) }
|
4
|
+
class Railtie < Rails::Railtie # :nodoc:
|
5
|
+
initializer 'canard.no_eager_loading', before: 'before_eager_loading' do |app|
|
6
|
+
ActiveSupport::Dependencies.autoload_paths.reject! { |path| Canard.load_paths.include?(path) }
|
9
7
|
# Don't eagerload our configs, we'll deal with them ourselves
|
10
8
|
app.config.eager_load_paths = app.config.eager_load_paths.reject do |path|
|
11
9
|
Canard.load_paths.include?(path)
|
12
10
|
end
|
13
|
-
|
14
|
-
|
15
|
-
end
|
11
|
+
|
12
|
+
app.config.watchable_dirs.merge! Hash[Canard.load_paths.product([[:rb]])] if app.config.respond_to?(:watchable_dirs)
|
16
13
|
end
|
17
14
|
|
18
|
-
initializer
|
15
|
+
initializer 'canard.active_record' do |_app|
|
19
16
|
ActiveSupport.on_load :active_record do
|
20
17
|
require 'canard/adapters/active_record'
|
21
18
|
Canard::Abilities.default_path = File.expand_path('app/abilities', Rails.root)
|
@@ -24,19 +21,17 @@ module Canard
|
|
24
21
|
end
|
25
22
|
end
|
26
23
|
|
27
|
-
initializer
|
24
|
+
initializer 'canard.mongoid' do |_app|
|
28
25
|
require 'canard/adapters/mongoid' if defined?(Mongoid)
|
29
26
|
end
|
30
27
|
|
31
|
-
initializer
|
28
|
+
initializer 'canard.abilities_reloading', after: 'action_dispatch.configure' do |_app|
|
32
29
|
reloader = rails5? ? ActiveSupport::Reloader : ActionDispatch::Reloader
|
33
|
-
if reloader.respond_to?(:to_prepare)
|
34
|
-
reloader.to_prepare { Canard.find_abilities }
|
35
|
-
end
|
30
|
+
reloader.to_prepare { Canard.find_abilities } if reloader.respond_to?(:to_prepare)
|
36
31
|
end
|
37
32
|
|
38
33
|
rake_tasks do
|
39
|
-
load File.expand_path('
|
34
|
+
load File.expand_path('../tasks/canard.rake', __dir__)
|
40
35
|
end
|
41
36
|
|
42
37
|
private
|
data/lib/canard/user_model.rb
CHANGED
@@ -1,77 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Canard
|
4
|
+
# Canard applies roles to a model using the acts_as_user class method. The following User model
|
5
|
+
# will be given the :manager and :admin roles
|
6
|
+
#
|
7
|
+
# class User < ActiveRecord::Base
|
8
|
+
#
|
9
|
+
# acts_as_user :roles => [:manager, :admin]
|
10
|
+
#
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# If using Canard with a non ActiveRecord class you can still assign roles but you will need to
|
14
|
+
# extend the class with Canard::UserModel and add a roles_mask attribute.
|
15
|
+
#
|
16
|
+
# class User
|
17
|
+
#
|
18
|
+
# extend Canard::UserModel
|
19
|
+
#
|
20
|
+
# attr_accessor :roles_mask
|
21
|
+
#
|
22
|
+
# acts_as_user :roles => [:manager, :admin]
|
23
|
+
#
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# You can choose the attribute used for the roles_mask by specifying :roles_mask. If no
|
27
|
+
# roles_mask is specified it uses RoleModel's default of 'roles_mask'
|
28
|
+
#
|
29
|
+
# acts_as_user :roles_mask => :my_roles_mask, :roles => [:manager, :admin]
|
30
|
+
#
|
31
|
+
# == Scopes
|
32
|
+
#
|
33
|
+
# Beyond applying the roles to the model, acts_as_user also creates some useful scopes for
|
34
|
+
# ActiveRecord models;
|
35
|
+
#
|
36
|
+
# User.with_any_role(:manager, :admin)
|
37
|
+
#
|
38
|
+
# returns all the managers and admins
|
39
|
+
#
|
40
|
+
# User.with_all_roles(:manager, :admin)
|
41
|
+
#
|
42
|
+
# returns only the users with both the manager and admin roles
|
43
|
+
#
|
44
|
+
# User.admins
|
45
|
+
#
|
46
|
+
# returns all the admins as
|
47
|
+
#
|
48
|
+
# User.managers
|
49
|
+
#
|
50
|
+
# returns all the users with the maager role likewise
|
51
|
+
#
|
52
|
+
# User.non_admins
|
53
|
+
#
|
54
|
+
# returns all the users who don't have the admin role and
|
55
|
+
#
|
56
|
+
# User.non_managers
|
57
|
+
#
|
58
|
+
# returns all the users who don't have the manager role.
|
2
59
|
module UserModel
|
3
|
-
|
4
|
-
# Canard applies roles to a model using the acts_as_user class method. The following User model
|
5
|
-
# will be given the :manager and :admin roles
|
6
|
-
#
|
7
|
-
# class User < ActiveRecord::Base
|
8
|
-
#
|
9
|
-
# acts_as_user :roles => [:manager, :admin]
|
10
|
-
#
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
# If using Canard with a non ActiveRecord class you can still assign roles but you will need to
|
14
|
-
# extend the class with Canard::UserModel and add a roles_mask attribute.
|
15
|
-
#
|
16
|
-
# class User
|
17
|
-
#
|
18
|
-
# extend Canard::UserModel
|
19
|
-
#
|
20
|
-
# attr_accessor :roles_mask
|
21
|
-
#
|
22
|
-
# acts_as_user :roles => [:manager, :admin]
|
23
|
-
#
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# You can choose the attribute used for the roles_mask by specifying :roles_mask. If no
|
27
|
-
# roles_mask is specified it uses RoleModel's default of 'roles_mask'
|
28
|
-
#
|
29
|
-
# acts_as_user :roles_mask => :my_roles_mask, :roles => [:manager, :admin]
|
30
|
-
#
|
31
|
-
# == Scopes
|
32
|
-
#
|
33
|
-
# Beyond applying the roles to the model, acts_as_user also creates some useful scopes for
|
34
|
-
# ActiveRecord models;
|
35
|
-
#
|
36
|
-
# User.with_any_role(:manager, :admin)
|
37
|
-
#
|
38
|
-
# returns all the managers and admins
|
39
|
-
#
|
40
|
-
# User.with_all_roles(:manager, :admin)
|
41
|
-
#
|
42
|
-
# returns only the users with both the manager and admin roles
|
43
|
-
#
|
44
|
-
# User.admins
|
45
|
-
#
|
46
|
-
# returns all the admins as
|
47
|
-
#
|
48
|
-
# User.managers
|
49
|
-
#
|
50
|
-
# returns all the users with the maager role likewise
|
51
|
-
#
|
52
|
-
# User.non_admins
|
53
|
-
#
|
54
|
-
# returns all the users who don't have the admin role and
|
55
|
-
#
|
56
|
-
# User.non_managers
|
57
|
-
#
|
58
|
-
# returns all the users who don't have the manager role.
|
59
60
|
def acts_as_user(*args)
|
60
61
|
include RoleModel
|
61
62
|
include InstanceMethods
|
62
|
-
|
63
|
+
|
63
64
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
64
|
-
|
65
|
+
|
65
66
|
if defined?(ActiveRecord) && self < ActiveRecord::Base
|
66
67
|
extend Adapters::ActiveRecord
|
67
|
-
elsif defined?(Mongoid) &&
|
68
|
+
elsif defined?(Mongoid) && included_modules.include?(Mongoid::Document)
|
68
69
|
extend Adapters::Mongoid
|
69
|
-
field (options[:roles_mask] || :roles_mask), :
|
70
|
+
field (options[:roles_mask] || :roles_mask), type: Integer
|
70
71
|
end
|
71
72
|
|
72
|
-
roles_attribute options[:roles_mask] if options.
|
73
|
+
roles_attribute options[:roles_mask] if options.key?(:roles_mask)
|
73
74
|
|
74
|
-
roles options[:roles] if options.
|
75
|
+
roles options[:roles] if options.key?(:roles) && has_roles_mask_accessors?
|
75
76
|
|
76
77
|
add_role_scopes(prefix: options[:prefix]) if respond_to?(:add_role_scopes, true)
|
77
78
|
end
|
@@ -81,18 +82,16 @@ module Canard
|
|
81
82
|
# This is overridden by the ActiveRecord adapter as the attribute accessors
|
82
83
|
# don't show up in instance_methods.
|
83
84
|
def has_roles_mask_accessors?
|
84
|
-
instance_method_names = instance_methods.map
|
85
|
-
[roles_attribute_name.to_s, "#{roles_attribute_name}="].all? do |accessor|
|
85
|
+
instance_method_names = instance_methods.map(&:to_s)
|
86
|
+
[roles_attribute_name.to_s, "#{roles_attribute_name}="].all? do |accessor|
|
86
87
|
instance_method_names.include?(accessor)
|
87
88
|
end
|
88
89
|
end
|
89
90
|
|
90
|
-
module InstanceMethods
|
91
|
-
|
91
|
+
module InstanceMethods # :nodoc:
|
92
92
|
def ability
|
93
93
|
@ability ||= Ability.new(self)
|
94
94
|
end
|
95
|
-
|
96
95
|
end
|
97
96
|
end
|
98
97
|
end
|
data/lib/canard/version.rb
CHANGED
@@ -1,43 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/inflector'
|
2
4
|
|
3
|
-
class AbilityDefinition
|
4
|
-
|
5
|
+
class AbilityDefinition # :nodoc:
|
5
6
|
attr_accessor :cans, :cannots
|
6
|
-
|
7
|
+
|
7
8
|
def self.parse(definitions)
|
8
9
|
@@ability_definitions ||= {}
|
9
|
-
limitation, ability_list, model_list =
|
10
|
-
ability_names
|
10
|
+
limitation, ability_list, model_list = definitions.split(':')
|
11
|
+
ability_names = extract(ability_list)
|
12
|
+
model_names = extract(model_list)
|
11
13
|
model_names.each do |model_name|
|
12
14
|
definition = @@ability_definitions[model_name] || AbilityDefinition.new
|
13
15
|
definition.merge(limitation.pluralize, ability_names)
|
14
16
|
@@ability_definitions[model_name] = definition
|
15
17
|
end
|
16
18
|
end
|
17
|
-
|
19
|
+
|
18
20
|
def self.extract(string)
|
19
|
-
|
21
|
+
string.gsub(/[\[\]\s]/, '').split(',')
|
20
22
|
end
|
21
|
-
|
23
|
+
|
22
24
|
def self.models
|
23
25
|
@@ability_definitions
|
24
26
|
end
|
25
|
-
|
27
|
+
|
26
28
|
def initialize
|
27
|
-
@cans
|
29
|
+
@cans = []
|
30
|
+
@cannots = []
|
28
31
|
end
|
29
|
-
|
32
|
+
|
30
33
|
def merge(limitation, ability_names)
|
31
34
|
combined_ability_names = instance_variable_get("@#{limitation}") | ability_names
|
32
35
|
instance_variable_set("@#{limitation}", combined_ability_names)
|
33
36
|
end
|
34
|
-
|
37
|
+
|
35
38
|
def can
|
36
39
|
@cans
|
37
40
|
end
|
38
|
-
|
41
|
+
|
39
42
|
def cannot
|
40
43
|
@cannots
|
41
44
|
end
|
42
|
-
|
43
45
|
end
|