rolify 2.2.2 → 3.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.
Files changed (40) hide show
  1. data/.travis.yml +11 -7
  2. data/CHANGELOG.rdoc +6 -0
  3. data/README.rdoc +36 -4
  4. data/UPGRADE.rdoc +22 -0
  5. data/lib/generators/rolify/role/role_generator.rb +5 -6
  6. data/lib/generators/rolify/role/templates/README-active_record +21 -0
  7. data/lib/generators/rolify/role/templates/README-mongoid +17 -0
  8. data/lib/generators/rolify/role/templates/initializer.rb +5 -6
  9. data/lib/generators/rolify/role/templates/{role.rb → role-active_record.rb} +0 -0
  10. data/lib/generators/rolify/role/templates/role-mongoid.rb +8 -0
  11. data/lib/rolify/adapters/active_record.rb +83 -0
  12. data/lib/rolify/adapters/base.rb +53 -0
  13. data/lib/rolify/adapters/mongoid.rb +87 -0
  14. data/lib/rolify/configure.rb +40 -0
  15. data/lib/rolify/dynamic.rb +21 -0
  16. data/lib/rolify/railtie.rb +20 -0
  17. data/lib/rolify/resource.rb +25 -0
  18. data/lib/rolify/role.rb +32 -110
  19. data/lib/rolify/version.rb +1 -1
  20. data/lib/rolify.rb +35 -0
  21. data/rolify.gemspec +3 -2
  22. data/spec/generators/rolify/role/role_generator_spec.rb +75 -16
  23. data/spec/rolify/config_spec.rb +189 -0
  24. data/spec/rolify/custom_spec.rb +15 -0
  25. data/spec/rolify/resource_spec.rb +317 -0
  26. data/spec/rolify/role_spec.rb +8 -495
  27. data/spec/rolify/shared_contexts.rb +74 -0
  28. data/spec/rolify/shared_examples/shared_examples_for_dynamic.rb +110 -0
  29. data/spec/rolify/shared_examples/shared_examples_for_has_all_roles.rb +71 -0
  30. data/spec/rolify/shared_examples/shared_examples_for_has_any_role.rb +71 -0
  31. data/spec/rolify/shared_examples/shared_examples_for_has_no_role.rb +97 -0
  32. data/spec/rolify/shared_examples/shared_examples_for_has_role_getter.rb +135 -0
  33. data/spec/rolify/shared_examples/shared_examples_for_has_role_setter.rb +92 -0
  34. data/spec/rolify/shared_examples/shared_examples_for_roles.rb +80 -0
  35. data/spec/spec_helper.rb +10 -5
  36. data/spec/support/{models.rb → adapters/active_record.rb} +11 -8
  37. data/spec/support/adapters/mongoid.rb +56 -0
  38. data/spec/support/data.rb +14 -5
  39. metadata +76 -20
  40. data/benchmarks/performance.rb +0 -51
data/lib/rolify/role.rb CHANGED
@@ -1,80 +1,51 @@
1
1
  module Rolify
2
- @@role_cname = "Role"
3
- @@user_cname = "User"
4
- @@dynamic_shortcuts = false
5
-
6
- def self.configure
7
- yield self if block_given?
8
- end
9
-
10
- def self.role_cname
11
- @@role_cname.constantize
12
- end
13
-
14
- def self.role_cname=(role_cname)
15
- @@role_cname = role_cname.camelize
16
- end
17
-
18
- def self.user_cname
19
- @@user_cname.constantize
20
- end
21
-
22
- def self.user_cname=(user_cname)
23
- @@user_cname = user_cname.camelize
24
- end
25
-
26
- def self.dynamic_shortcuts
27
- @@dynamic_shortcuts || false
28
- end
29
-
30
- def self.dynamic_shortcuts=(is_dynamic)
31
- @@dynamic_shortcuts = is_dynamic
32
- self.user_cname.load_dynamic_methods if is_dynamic
33
- end
34
-
35
- module Roles
36
-
2
+ module Role
37
3
  def has_role(role_name, resource = nil)
38
- role = Rolify.role_cname.find_or_create_by_name_and_resource_type_and_resource_id(role_name,
39
- (resource.is_a?(Class) ? resource.to_s : resource.class.name if resource),
40
- (resource.id if resource && !resource.is_a?(Class)))
4
+ role = self.class.adapter.find_or_create_by(role_name,
5
+ (resource.is_a?(Class) ? resource.to_s : resource.class.name if resource),
6
+ (resource.id if resource && !resource.is_a?(Class)))
7
+
41
8
  if !roles.include?(role)
42
9
  self.class.define_dynamic_method(role_name, resource) if Rolify.dynamic_shortcuts
43
- self.role_ids |= [role.id]
10
+ self.class.adapter.add(self, role)
44
11
  end
12
+ role
45
13
  end
46
14
  alias_method :grant, :has_role
47
-
15
+
48
16
  def has_role?(role_name, resource = nil)
49
- query, values = build_query(role_name, resource)
50
- self.roles.where(query, *values).size > 0
17
+ self.class.adapter.find(self.roles, role_name, resource).size > 0
51
18
  end
52
19
 
53
20
  def has_all_roles?(*args)
54
- conditions, values, count = sql_conditions(args, true)
55
- self.roles.where([ conditions.join(' OR '), *values ]).where(count.join(') AND (')).size > 0
21
+ args.each do |arg|
22
+ if arg.is_a? Hash
23
+ return false if !self.has_role?(arg[:name], arg[:resource])
24
+ elsif arg.is_a?(String) || arg.is_a?(Symbol)
25
+ return false if !self.has_role?(arg)
26
+ else
27
+ raise ArgumentError, "Invalid argument type: only hash or string or symbol allowed"
28
+ end
29
+ end
30
+ true
56
31
  end
57
32
 
58
33
  def has_any_role?(*args)
59
- conditions, values = sql_conditions(args)
60
- self.roles.where([ conditions.join(' OR '), *values ]).size > 0
34
+ self.class.adapter.where(self.roles, args).size > 0
61
35
  end
62
-
36
+
63
37
  def has_no_role(role_name, resource = nil)
64
- role = self.roles.where(:name => role_name)
65
- role = role.where(:resource_type => (resource.is_a?(Class) ? resource.to_s : resource.class.name)) if resource
66
- role = role.where(:resource_id => resource.id) if resource && !resource.is_a?(Class)
67
- self.roles.delete(role) if role
38
+ self.class.adapter.remove(self.roles, role_name, resource)
68
39
  end
69
40
  alias_method :revoke, :has_no_role
70
-
41
+
71
42
  def roles_name
72
43
  self.roles.select(:name).map { |r| r.name }
73
44
  end
74
45
 
75
46
  def method_missing(method, *args, &block)
76
47
  if method.to_s.match(/^is_(\w+)_of[?]$/) || method.to_s.match(/^is_(\w+)[?]$/)
77
- if Rolify.role_cname.where(:name => $1).count > 0
48
+ if self.class.role_class.where(:name => $1).count > 0
78
49
  resource = args.first
79
50
  self.class.define_dynamic_method $1, resource
80
51
  return has_role?("#{$1}", resource)
@@ -82,65 +53,16 @@ module Rolify
82
53
  end unless !Rolify.dynamic_shortcuts
83
54
  super
84
55
  end
85
-
86
- private
87
-
88
- def sql_conditions(args, count = false)
89
- conditions = []
90
- count_conditions = [] if count
91
- values = []
92
- args.each do |arg|
93
- if arg.is_a? Hash
94
- a, v = build_query(arg[:name], arg[:resource])
95
- elsif arg.is_a? String
96
- a, v = build_query(arg)
97
- else
98
- raise ArgumentError, "Invalid argument type: only hash or string allowed"
99
- end
100
- conditions << a
101
- count_conditions << self.roles.where(a, *v).select("COUNT(id)").to_sql + " > 0" if count
102
- values += v
103
- end
104
- count ? [ conditions, values, count_conditions ] : [ conditions, values ]
105
- end
106
56
 
107
- def build_query(role, resource = nil)
108
- return [ "name = ?", [ role ] ] if resource == :any
109
- query = "((name = ?) AND (resource_type IS NULL) AND (resource_id IS NULL))"
110
- values = [ role ]
111
- if resource
112
- query.insert(0, "(")
113
- query += " OR ((name = ?) AND (resource_type = ?) AND (resource_id IS NULL))"
114
- values << role << (resource.is_a?(Class) ? resource.to_s : resource.class.name)
115
- if !resource.is_a? Class
116
- query += " OR ((name = ?) AND (resource_type = ?) AND (resource_id = ?))"
117
- values << role << resource.class.name << resource.id
118
- end
119
- query += ")"
57
+ def respond_to?(method, include_private = false)
58
+ if Rolify.dynamic_shortcuts && (method.to_s.match(/^is_(\w+)_of[?]$/) || method.to_s.match(/^is_(\w+)[?]$/))
59
+ query = self.class.role_class.where(:name => $1)
60
+ query = self.class.adapter.exists?(query, :resource_type) if method.to_s.match(/^is_(\w+)_of[?]$/)
61
+ return true if query.count > 0
62
+ false
63
+ else
64
+ super
120
65
  end
121
- [ query, values ]
122
66
  end
123
-
124
- end
125
-
126
- module Dynamic
127
-
128
- def load_dynamic_methods
129
- Rolify.role_cname.all.each do |r|
130
- define_dynamic_method(r.name, r.resource)
131
- end
132
- end
133
-
134
- def define_dynamic_method(role_name, resource)
135
- class_eval do
136
- define_method("is_#{role_name}?".to_sym) do
137
- has_role?("#{role_name}")
138
- end if !method_defined?("is_#{role_name}?".to_sym)
139
-
140
- define_method("is_#{role_name}_of?".to_sym) do |arg|
141
- has_role?("#{role_name}", arg)
142
- end if !method_defined?("is_#{role_name}_of?".to_sym) && resource
143
- end
144
- end
145
67
  end
146
68
  end
@@ -1,3 +1,3 @@
1
1
  module Rolify
2
- VERSION = "2.2.2"
2
+ VERSION = "3.0.0"
3
3
  end
data/lib/rolify.rb CHANGED
@@ -1,4 +1,39 @@
1
1
  require 'active_record'
2
2
 
3
+ require 'rolify/adapters/active_record' if defined?(ActiveRecord)
4
+ require 'rolify/adapters/mongoid' if defined?(Mongoid)
5
+ require 'rolify/railtie' if defined?(Rails)
3
6
  require 'rolify/role'
7
+ require 'rolify/configure'
8
+ require 'rolify/dynamic'
9
+ require 'rolify/resource'
4
10
 
11
+ module Rolify
12
+ extend Configure
13
+ attr_accessor :role_cname, :adapter
14
+
15
+ def rolify(options = { :role_cname => 'Role' })
16
+ include Role
17
+ extend Dynamic if Rolify.dynamic_shortcuts
18
+ rolify_options = { :class_name => options[:role_cname].camelize }
19
+ rolify_options.merge!({ :join_table => "#{self.to_s.tableize}_#{options[:role_cname].tableize}" }) if Rolify.orm == "active_record"
20
+ has_and_belongs_to_many :roles, rolify_options
21
+
22
+ load_dynamic_methods if Rolify.dynamic_shortcuts
23
+ self.role_cname = options[:role_cname]
24
+ self.adapter = Rolify::Adapter.const_get(Rolify.orm.camelize).new(options[:role_cname])
25
+ end
26
+
27
+ def resourcify(options = { :role_cname => 'Role' })
28
+ include Resource
29
+ resourcify_options = { :class_name => options[:role_cname].camelize }
30
+ resourcify_options.merge!({ :as => :resource })
31
+ has_many :roles, resourcify_options
32
+ self.role_cname = options[:role_cname]
33
+ self.adapter = Rolify::Adapter.const_get(Rolify.orm.camelize).new(options[:role_cname])
34
+ end
35
+
36
+ def role_class
37
+ self.role_cname.constantize
38
+ end
39
+ end
data/rolify.gemspec CHANGED
@@ -19,13 +19,14 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_dependency "activerecord", ">= 3.1.0"
23
-
24
22
  if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
25
23
  s.add_development_dependency "activerecord-jdbcsqlite3-adapter"
26
24
  else
27
25
  s.add_development_dependency "sqlite3"
28
26
  end
27
+ s.add_development_dependency "activerecord", ">= 3.1.0"
28
+ s.add_development_dependency "mongoid", ">= 2.3"
29
+ s.add_development_dependency "bson_ext"
29
30
  s.add_development_dependency "ammeter"
30
31
  s.add_development_dependency "rake"
31
32
  s.add_development_dependency "rspec"
@@ -6,9 +6,15 @@ require 'generators/rolify/role/role_generator'
6
6
  describe Rolify::Generators::RoleGenerator do
7
7
  # Tell the generator where to put its output (what it thinks of as Rails.root)
8
8
  destination File.expand_path("../../../../../tmp", __FILE__)
9
+ teardown :cleanup_destination_root
10
+
9
11
  before {
10
12
  prepare_destination
11
13
  }
14
+
15
+ def cleanup_destination_root
16
+ FileUtils.rm_rf destination_root
17
+ end
12
18
 
13
19
  describe 'no arguments' do
14
20
  before(:all) { arguments [] }
@@ -25,16 +31,20 @@ describe Rolify::Generators::RoleGenerator do
25
31
  describe 'config/initializers/rolify.rb' do
26
32
  subject { file('config/initializers/rolify.rb') }
27
33
  it { should exist }
28
- it { should contain "c.user_cname = \"User\"" }
29
- it { should contain "c.role_cname = \"Role\"" }
30
- it { should contain "c.dynamic_shortcuts = false" }
34
+ it { should contain "# c.use_dynamic_shortcuts" }
35
+ end
36
+
37
+ describe 'app/models/role.rb' do
38
+ subject { file('app/models/role.rb') }
39
+ it { should exist }
40
+ it { should contain "class Role < ActiveRecord::Base" }
41
+ it { should contain "has_and_belongs_to_many :users, :join_table => :users_roles" }
42
+ it { should contain "belongs_to :resource, :polymorphic => true" }
31
43
  end
32
44
 
33
45
  describe 'app/models/user.rb' do
34
46
  subject { file('app/models/user.rb') }
35
- it { should contain "include Rolify::Roles" }
36
- it { should contain "# extend Rolify::Dynamic" }
37
- it { should contain "has_and_belongs_to_many :roles, :join_table => :users_roles" }
47
+ it { should contain "rolify" }
38
48
  end
39
49
 
40
50
  describe 'migration file' do
@@ -60,16 +70,20 @@ describe Rolify::Generators::RoleGenerator do
60
70
  describe 'config/initializers/rolify.rb' do
61
71
  subject { file('config/initializers/rolify.rb') }
62
72
  it { should exist }
63
- it { should contain "c.user_cname = \"Client\"" }
64
- it { should contain "c.role_cname = \"Rank\"" }
65
- it { should contain "c.dynamic_shortcuts = false" }
73
+ it { should contain "# c.use_dynamic_shortcuts" }
74
+ end
75
+
76
+ describe 'app/models/rank.rb' do
77
+ subject { file('app/models/rank.rb') }
78
+ it { should exist }
79
+ it { should contain "class Rank < ActiveRecord::Base" }
80
+ it { should contain "has_and_belongs_to_many :clients, :join_table => :clients_ranks" }
81
+ it { should contain "belongs_to :resource, :polymorphic => true" }
66
82
  end
67
83
 
68
84
  describe 'app/models/client.rb' do
69
85
  subject { file('app/models/client.rb') }
70
- it { should contain "include Rolify::Roles" }
71
- it { should contain "# extend Rolify::Dynamic" }
72
- it { should contain "has_and_belongs_to_many :roles, :class_name => \"Rank\", :join_table => :clients_ranks" }
86
+ it { should contain "rolify" }
73
87
  end
74
88
 
75
89
  describe 'migration file' do
@@ -96,14 +110,20 @@ describe Rolify::Generators::RoleGenerator do
96
110
  describe 'config/initializers/rolify.rb' do
97
111
  subject { file('config/initializers/rolify.rb') }
98
112
  it { should exist }
99
- it { should contain "c.dynamic_shortcuts = true" }
113
+ it { should_not contain "# c.use_dynamic_shortcuts" }
114
+ end
115
+
116
+ describe 'app/models/role.rb' do
117
+ subject { file('app/models/role.rb') }
118
+ it { should exist }
119
+ it { should contain "class Role < ActiveRecord::Base" }
120
+ it { should contain "has_and_belongs_to_many :users, :join_table => :users_roles" }
121
+ it { should contain "belongs_to :resource, :polymorphic => true" }
100
122
  end
101
123
 
102
124
  describe 'app/models/user.rb' do
103
125
  subject { file('app/models/user.rb') }
104
- it { should contain "include Rolify::Roles" }
105
- it { should contain "extend Rolify::Dynamic" }
106
- it { should contain "has_and_belongs_to_many :roles, :join_table => :users_roles" }
126
+ it { should contain "rolify" }
107
127
  end
108
128
 
109
129
  describe 'migration file' do
@@ -113,4 +133,43 @@ describe Rolify::Generators::RoleGenerator do
113
133
  it { should be_a_migration }
114
134
  end
115
135
  end
136
+
137
+ describe 'specifying orm adapter' do
138
+ before(:all) { arguments [ "Role", "User", "mongoid" ] }
139
+
140
+ before {
141
+ capture(:stdout) {
142
+ generator.create_file "app/models/user.rb" do
143
+ <<-CLASS
144
+ class User
145
+ include Mongoid::Document
146
+
147
+ field :login, :type => String
148
+ end
149
+ CLASS
150
+ end
151
+ }
152
+ run_generator
153
+ }
154
+
155
+ describe 'config/initializers/rolify.rb' do
156
+ subject { file('config/initializers/rolify.rb') }
157
+ it { should exist }
158
+ it { should_not contain "# c.use_mongoid" }
159
+ it { should contain "# c.use_dynamic_shortcuts" }
160
+ end
161
+
162
+ describe 'app/models/role.rb' do
163
+ subject { file('app/models/role.rb') }
164
+ it { should exist }
165
+ it { should contain "class Role\n" }
166
+ it { should contain "has_and_belongs_to_many :users\n" }
167
+ it { should contain "belongs_to :resource, :polymorphic => true" }
168
+ end
169
+
170
+ describe 'app/models/user.rb' do
171
+ subject { file('app/models/user.rb') }
172
+ it { should contain "rolify" }
173
+ end
174
+ end
116
175
  end
@@ -0,0 +1,189 @@
1
+ require "spec_helper"
2
+
3
+ class ARUser < ActiveRecord::Base
4
+ extend Rolify
5
+ end
6
+
7
+ class MUser
8
+ include Mongoid::Document
9
+ extend Rolify
10
+ end
11
+
12
+ describe Rolify do
13
+ before do
14
+ Rolify.use_defaults
15
+ end
16
+
17
+ describe :dynamic_shortcuts do
18
+ context "using defaults values" do
19
+ subject { Rolify.dynamic_shortcuts }
20
+
21
+ it { should be_false }
22
+ end
23
+
24
+ context "using custom values" do
25
+ before do
26
+ Rolify.dynamic_shortcuts = true
27
+ end
28
+
29
+ subject { Rolify.dynamic_shortcuts }
30
+
31
+ it { should be_true }
32
+ end
33
+ end
34
+
35
+ describe :orm do
36
+ context "using defaults values" do
37
+ subject { Rolify.orm }
38
+
39
+ it { should eq("active_record") }
40
+
41
+ context "on the User class" do
42
+ before do
43
+ ARUser.rolify
44
+ end
45
+
46
+ subject { ARUser }
47
+
48
+ its("adapter.class") { should be(Rolify::Adapter::ActiveRecord) }
49
+ end
50
+
51
+ context "on the Forum class" do
52
+ before do
53
+ Forum.resourcify
54
+ end
55
+
56
+ subject { Forum }
57
+
58
+ its("adapter.class") { should be(Rolify::Adapter::ActiveRecord) }
59
+ end
60
+ end
61
+
62
+ context "using custom values" do
63
+ context "using :orm setter method" do
64
+ before do
65
+ Rolify.orm = "mongoid"
66
+ end
67
+
68
+ subject { Rolify.orm }
69
+
70
+ it { should eq("mongoid") }
71
+
72
+ context "on the User class" do
73
+ before do
74
+ MUser.rolify
75
+ end
76
+
77
+ subject { MUser }
78
+
79
+ its("adapter.class") { should be(Rolify::Adapter::Mongoid) }
80
+ end
81
+
82
+ context "on the Forum class" do
83
+ before do
84
+ Forum.resourcify
85
+ end
86
+
87
+ subject { Forum }
88
+
89
+ its("adapter.class") { should be(Rolify::Adapter::Mongoid) }
90
+ end
91
+ end
92
+
93
+ context "using :use_mongoid method" do
94
+ before do
95
+ Rolify.use_mongoid
96
+ end
97
+
98
+ subject { Rolify.orm }
99
+
100
+ it { should eq("mongoid") }
101
+
102
+ context "on the User class" do
103
+ before do
104
+ MUser.rolify
105
+ end
106
+
107
+ subject { MUser }
108
+
109
+ its("adapter.class") { should be(Rolify::Adapter::Mongoid) }
110
+ end
111
+
112
+ context "on the Forum class" do
113
+ before do
114
+ Forum.resourcify
115
+ end
116
+
117
+ subject { Forum }
118
+
119
+ its("adapter.class") { should be(Rolify::Adapter::Mongoid) }
120
+ end
121
+ end
122
+ end
123
+
124
+ describe :dynamic_shortcuts do
125
+ context "using defaults values" do
126
+ subject { Rolify.dynamic_shortcuts }
127
+
128
+ it { should be_false }
129
+ end
130
+
131
+ context "using custom values" do
132
+ context "using :dynamic_shortcuts setter method" do
133
+ before do
134
+ Rolify.dynamic_shortcuts = true
135
+ end
136
+
137
+ subject { Rolify.dynamic_shortcuts }
138
+
139
+ it { should be_true }
140
+ end
141
+
142
+ context "using :use_dynamic_shortcuts method" do
143
+ before do
144
+ Rolify.use_dynamic_shortcuts
145
+ end
146
+
147
+ subject { Rolify.dynamic_shortcuts }
148
+
149
+ it { should be_true }
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ describe :configure do
156
+ before do
157
+ Rolify.configure do |r|
158
+ r.dynamic_shortcuts = true
159
+ r.orm = "mongoid"
160
+ end
161
+ end
162
+
163
+ its(:dynamic_shortcuts) { should be_true }
164
+ its(:orm) { should eq("mongoid") }
165
+
166
+ context "on the User class" do
167
+ before do
168
+ MUser.rolify
169
+ end
170
+
171
+ subject { MUser }
172
+
173
+ it { should satisfy { |u| u.include? Rolify::Role }}
174
+ it { should satisfy { |u| u.singleton_class.include? Rolify::Dynamic } }
175
+ its("adapter.class") { should be(Rolify::Adapter::Mongoid) }
176
+ end
177
+
178
+ context "on the Forum class" do
179
+ before do
180
+ Forum.resourcify
181
+ end
182
+
183
+ subject { Forum }
184
+
185
+ it { should satisfy { |u| u.include? Rolify::Resource }}
186
+ its("adapter.class") { should be(Rolify::Adapter::Mongoid) }
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,15 @@
1
+ require "spec_helper"
2
+ require "rolify/shared_examples/shared_examples_for_roles"
3
+ require "rolify/shared_examples/shared_examples_for_dynamic"
4
+
5
+ describe "Using Rolify with custom User and Role class names" do
6
+ it_behaves_like Rolify::Role do
7
+ let(:user_class) { Customer }
8
+ let(:role_class) { Privilege }
9
+ end
10
+
11
+ it_behaves_like Rolify::Dynamic do
12
+ let(:user_class) { Customer }
13
+ let(:role_class) { Privilege }
14
+ end
15
+ end