rolify 2.2.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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