moulin_rouge 0.0.1.beta1 → 0.0.1.beta2
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.
- data/CHANGELOG.md +5 -0
- data/MIT-LICENSE +1 -1
- data/README.md +72 -51
- data/lib/generators/moulin_rouge/auth_generator.rb +18 -0
- data/lib/generators/moulin_rouge/install_generator.rb +3 -3
- data/lib/generators/moulin_rouge/templates/authorization.rb +21 -0
- data/lib/generators/moulin_rouge/templates/initializer.rb +14 -0
- data/lib/moulin_rouge.rb +2 -2
- data/lib/moulin_rouge/{permission.rb → ability.rb} +35 -62
- data/lib/moulin_rouge/authorization.rb +46 -0
- data/lib/moulin_rouge/cancan/ability.rb +6 -6
- data/lib/moulin_rouge/version.rb +1 -1
- data/spec/fixtures/fixture_authorization.rb +5 -0
- data/spec/moulin_rouge/ability_spec.rb +229 -0
- data/spec/moulin_rouge/authorization_spec.rb +120 -0
- data/spec/moulin_rouge/cancan/ability_spec.rb +5 -5
- data/spec/moulin_rouge_spec.rb +9 -19
- data/spec/spec_helper.rb +9 -9
- metadata +51 -38
- data/lib/generators/moulin_rouge/permission_generator.rb +0 -18
- data/lib/generators/moulin_rouge/templates/install/config/initializers/moulin_rouge.rb +0 -15
- data/lib/generators/moulin_rouge/templates/permission.rb +0 -19
- data/spec/fixtures/role.rb +0 -3
- data/spec/moulin_rouge/permission_spec.rb +0 -316
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
CHANGELOG
|
2
2
|
=========
|
3
3
|
|
4
|
+
0.0.1.beta2 (Mar 28, 2011)
|
5
|
+
|
6
|
+
* Instead of declarate the authorization in plain ruby file, that could break spec and other things, manage the scope to the MoulinRouge::Authorization class
|
7
|
+
* Changing the name of the generator to match the name changes
|
8
|
+
|
4
9
|
0.0.1.beta (Mar 16, 2011)
|
5
10
|
|
6
11
|
* Implementing the generators
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Moulin Rouge
|
2
2
|
============
|
3
3
|
|
4
|
-
**Moulin Rouge**
|
4
|
+
**Moulin Rouge** offers a custom and easy DSL to manage your authorizations and groups of access with [CanCan](https://github.com/ryanb/cancan). The main feature is the ability to split your authorizations in many ruby files, that are automatically pushed to CanCan authorization system. It is also decoupled from any role system giving you flexibility.
|
5
5
|
|
6
|
-
There are a bunch of examples bellow to show you how to
|
6
|
+
There are a bunch of examples bellow to show you how to get started and explaining all features.
|
7
7
|
|
8
8
|
Installation
|
9
9
|
------------
|
@@ -22,13 +22,15 @@ Run the generator to install the roles and permissions structure:
|
|
22
22
|
|
23
23
|
Generate a permission file:
|
24
24
|
|
25
|
-
rails g moulin_rouge:
|
25
|
+
rails g moulin_rouge:auth <name>
|
26
26
|
|
27
27
|
Add your permissions to newly created file:
|
28
28
|
|
29
29
|
```ruby
|
30
|
-
|
31
|
-
|
30
|
+
class UserAuthorization < MoulinRouge::Authorization
|
31
|
+
role :name do
|
32
|
+
can :read, :something
|
33
|
+
end
|
32
34
|
end
|
33
35
|
```
|
34
36
|
|
@@ -41,33 +43,35 @@ First of all, you have to accept that the role registering belongs to the ruby c
|
|
41
43
|
|
42
44
|
When you run:
|
43
45
|
|
44
|
-
bundle g
|
46
|
+
bundle g moulin_rouge:install
|
45
47
|
|
46
48
|
This will create the following folder structure:
|
47
49
|
|
48
50
|
root
|
49
51
|
| app/
|
50
|
-
| |
|
52
|
+
| | authorizations/
|
51
53
|
| config/
|
52
54
|
| | initalizers
|
53
55
|
| | | moulin_rouge.rb
|
54
56
|
|
55
57
|
### Defining roles ###
|
56
58
|
|
57
|
-
All your
|
59
|
+
All your authorization files will be stored inside the `app/authorizations` folder. Just create a ruby file inside and the definitions will be automatically defined.
|
58
60
|
|
59
61
|
```ruby
|
60
|
-
|
61
|
-
|
62
|
-
|
62
|
+
class UserAuthorization < MoulinRouge::Authorization
|
63
|
+
role :superuser do
|
64
|
+
can :manage, :all
|
65
|
+
end
|
63
66
|
|
64
|
-
role :editors do
|
65
|
-
|
66
|
-
end
|
67
|
+
role :editors do
|
68
|
+
can :manage, Articles
|
69
|
+
end
|
67
70
|
|
68
|
-
role :authors do
|
69
|
-
|
70
|
-
|
71
|
+
role :authors do
|
72
|
+
can :manage, Article do |article|
|
73
|
+
article.user_id == current_user.id
|
74
|
+
end
|
71
75
|
end
|
72
76
|
end
|
73
77
|
```
|
@@ -78,20 +82,22 @@ Also, the others CanCan methods are avaliable (`cannot`, `can?`, `cannot?`) and
|
|
78
82
|
|
79
83
|
### Groups ###
|
80
84
|
|
81
|
-
A group is an easy way to organize your
|
85
|
+
A group is an easy way to organize your authorization. All groups with the same name, will have their abilities and authorizations nested together.
|
82
86
|
|
83
|
-
The group will delegate all abilities defined
|
87
|
+
The group will delegate all abilities defined inside of it, to their childrens, so any role or group will have the same abilities defined in the parent. Also the group is not an avaliable role, they only serve has namespaces.
|
84
88
|
|
85
89
|
```ruby
|
86
|
-
|
87
|
-
|
90
|
+
class UserAuthorization < MoulinRouge::Authorization
|
91
|
+
group :marketing do
|
92
|
+
can :read, Dashboard
|
88
93
|
|
89
|
-
|
90
|
-
|
91
|
-
|
94
|
+
role :manager do
|
95
|
+
can :manage, Proposal
|
96
|
+
end
|
92
97
|
|
93
|
-
|
94
|
-
|
98
|
+
role :salesman do
|
99
|
+
can :manage, Proposal, :user_id => current_user.id
|
100
|
+
end
|
95
101
|
end
|
96
102
|
end
|
97
103
|
```
|
@@ -101,7 +107,7 @@ To avoid name conflicts, whenever you have a nested roles or groups, their name
|
|
101
107
|
Following the example above, will generate two roles:
|
102
108
|
|
103
109
|
```ruby
|
104
|
-
MoulinRouge::
|
110
|
+
MoulinRouge::Authorization.defined_roles
|
105
111
|
# => [:marketing_manager, :marketing_salesman]
|
106
112
|
# => :marketing_manager => can :read, Dashboard, can :manage, Proposal
|
107
113
|
# => :marketing_salesman => can :read, Dashboard, can :manage, Proposal, :user_id => current_user.id
|
@@ -112,11 +118,13 @@ MoulinRouge::Permission.list
|
|
112
118
|
When you have nested rules, they will act has namespaces, no ability will be shared unless is explicited with the `include` method.
|
113
119
|
|
114
120
|
```ruby
|
115
|
-
|
116
|
-
|
121
|
+
class UserAuthorization < MoulinRouge::Authorization
|
122
|
+
role :marketing do
|
123
|
+
can :manage, Proposal
|
117
124
|
|
118
|
-
|
119
|
-
|
125
|
+
role :salesman do
|
126
|
+
can :read, Proposal
|
127
|
+
end
|
120
128
|
end
|
121
129
|
end
|
122
130
|
```
|
@@ -124,7 +132,7 @@ end
|
|
124
132
|
Following the example above, this will generate two roles with the abilities:
|
125
133
|
|
126
134
|
```ruby
|
127
|
-
MoulinRouge::
|
135
|
+
MoulinRouge::Authorization.defined_roles
|
128
136
|
# => [:marketing, :marketing_salesman]
|
129
137
|
# => :marketing => can :manage, Proposal
|
130
138
|
# => :marketing_salesman => can :read, Proposal
|
@@ -132,19 +140,21 @@ MoulinRouge::Permission.list
|
|
132
140
|
|
133
141
|
### Extending ###
|
134
142
|
|
135
|
-
|
143
|
+
If you want extend the abilities from a role to another, **MoulinRouge** let you `include` them automatically. All the abilities from the target will be appended to the caller.
|
136
144
|
|
137
|
-
Only roles can be included, and if you provide a name that
|
145
|
+
Only roles can be included, and if you provide a name that is not defined, a `RoleNotFound` will be raised. *Notice* by the example bellow, that you should provide the full name of the role in order to find them.
|
138
146
|
|
139
147
|
```ruby
|
140
|
-
|
141
|
-
|
142
|
-
|
148
|
+
class UserAuthorization < MoulinRouge::Authorization
|
149
|
+
group :marketing do
|
150
|
+
role :admin do
|
151
|
+
can :do, :something
|
152
|
+
end
|
143
153
|
end
|
144
|
-
end
|
145
154
|
|
146
|
-
role :super do
|
147
|
-
|
155
|
+
role :super do
|
156
|
+
include :marketing_admin
|
157
|
+
end
|
148
158
|
end
|
149
159
|
```
|
150
160
|
|
@@ -153,15 +163,15 @@ Configuration
|
|
153
163
|
|
154
164
|
```ruby
|
155
165
|
MoulinRouge.configure do |config|
|
156
|
-
# Cache
|
166
|
+
# Cache authorizations
|
157
167
|
config.cache = Rails.env.production?
|
158
|
-
#
|
159
|
-
config.path = 'app/
|
160
|
-
#
|
168
|
+
# Path for search the authorizations files
|
169
|
+
config.path = 'app/authorizations/**/*.rb'
|
170
|
+
# Method name that will send to the user to test if the role is assigned to him
|
161
171
|
config.test_method = :is?
|
162
|
-
#
|
172
|
+
# Your user model
|
163
173
|
config.model = User
|
164
|
-
#
|
174
|
+
# The method name that will access the current user information
|
165
175
|
config.model_instance = :current_user
|
166
176
|
end
|
167
177
|
```
|
@@ -169,7 +179,7 @@ end
|
|
169
179
|
Goodies
|
170
180
|
-------
|
171
181
|
|
172
|
-
For those who
|
182
|
+
For those who does not like the `load_and_authorize_resource` method from CanCan, here is provided a cleaner and more flexible solution through `ActionController::Responder`, the `MoulinRouge::CanCan::Responder` bellow there are instruction to activate them.
|
173
183
|
|
174
184
|
Create the file `lib/application_responder.rb` with the following:
|
175
185
|
|
@@ -198,8 +208,17 @@ More about the `Responder` class:
|
|
198
208
|
* http://blog.plataformatec.com.br/2009/08/embracing-rest-with-mind-body-and-soul/
|
199
209
|
* http://archives.ryandaigle.com/articles/2009/8/6/what-s-new-in-edge-rails-cleaner-restful-controllers-w-respond_with/
|
200
210
|
|
211
|
+
Testing
|
212
|
+
-------
|
213
|
+
|
214
|
+
This gem uses the [RSpec-2](https://www.relishapp.com/rspec) lib for BDD testing, with the help of [Guard](https://github.com/guard/guard) to autotest. For development just execute the following line:
|
215
|
+
|
216
|
+
bundle exec guard
|
217
|
+
|
218
|
+
And it will perform all tests, and reload every time you implement something new. Also you can follow the test coverage by [simplecov](https://github.com/colszowka/simplecov).
|
219
|
+
|
201
220
|
Thanks
|
202
|
-
|
221
|
+
------
|
203
222
|
|
204
223
|
* [Troles](https://github.com/kristianmandrup/trole)
|
205
224
|
* [CanTango](https://github.com/kristianmandrup/cantango)
|
@@ -209,7 +228,9 @@ Thanks
|
|
209
228
|
|
210
229
|
Mouling Rouge is a cabaret in Paris best know as the birthplace on modern [CanCan](https://github.com/ryanb/cancan) second to [Wikipedia](http://en.wikipedia.org/wiki/Moulin_Rouge).
|
211
230
|
|
212
|
-
|
213
|
-
|
231
|
+
Copyrights
|
232
|
+
----------
|
233
|
+
|
234
|
+
Copyrights 2012 [**Edson Hilios**](http://edson.hilios.com.br) edson (at) hilios (dot) com (dot) br
|
214
235
|
|
215
|
-
|
236
|
+
This software is licensed under [MIT-LICENSE](https://github.com/hilios/moulin_rouge/blob/master/MIT-LICENSE)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module MoulinRouge
|
4
|
+
module Generators
|
5
|
+
class AuthGenerator < ::Rails::Generators::Base
|
6
|
+
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
desc "Creates a new authorization file"
|
10
|
+
|
11
|
+
argument :name
|
12
|
+
|
13
|
+
def creates_an_authorization_file
|
14
|
+
copy_file("authorization.rb", "app/authorizations/#{name}_authorization.rb")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -6,11 +6,11 @@ module MoulinRouge
|
|
6
6
|
|
7
7
|
source_root File.expand_path('../templates', __FILE__)
|
8
8
|
|
9
|
-
desc "Installs the gem folder structure
|
9
|
+
desc "Installs the gem folder structure"
|
10
10
|
|
11
11
|
def generate_folder_structure
|
12
|
-
|
13
|
-
generate("moulin_rouge:permission", "
|
12
|
+
initializer("moulin_rouge.rb", "initializer.rb")
|
13
|
+
generate("moulin_rouge:permission", "user")
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class <%= name.camelize %>Authorization < MoulinRouge::Authorization
|
2
|
+
# can :read, Post
|
3
|
+
# can :read, Comment
|
4
|
+
# can :manage, Comment do |comment|
|
5
|
+
# comment.user_id == current_user.id
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# role :admin do
|
9
|
+
# can :manage, :all
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# group :main do
|
13
|
+
# role :editor do
|
14
|
+
# can :manage, Post
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# role :author do
|
18
|
+
# can :manage, Post, :user_id => current_user.id
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
MoulinRouge.configure do |config|
|
2
|
+
# Cache authorizations
|
3
|
+
config.cache = Rails.env.production?
|
4
|
+
# Path for search the authorizations files
|
5
|
+
config.path = 'app/authorizations/**/*.rb'
|
6
|
+
# Method name that will send to the user to test if the role is assigned to him
|
7
|
+
config.test_method = :is?
|
8
|
+
# Your user model
|
9
|
+
config.model = User
|
10
|
+
# The method name that will access the current user information
|
11
|
+
config.model_instance = :current_user
|
12
|
+
end
|
13
|
+
|
14
|
+
MoulinRouge.run! # Creates the Ability class
|
data/lib/moulin_rouge.rb
CHANGED
@@ -23,7 +23,7 @@ module MoulinRouge
|
|
23
23
|
|
24
24
|
# Import all permission files in the configuration
|
25
25
|
def self.load!
|
26
|
-
MoulinRouge::
|
26
|
+
MoulinRouge::Authorization.compile!
|
27
27
|
end
|
28
28
|
|
29
29
|
# Returns true if the run! method was called and false oterwise
|
@@ -39,6 +39,6 @@ module MoulinRouge
|
|
39
39
|
|
40
40
|
# Reset all constants
|
41
41
|
def self.reset! #:nodoc:
|
42
|
-
MoulinRouge::
|
42
|
+
MoulinRouge::Authorization.reset!
|
43
43
|
end
|
44
44
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module MoulinRouge
|
2
2
|
class RoleNotFound < Exception; end
|
3
|
-
#
|
4
|
-
class
|
3
|
+
# The CanCan wrapper with the custom DSL methods
|
4
|
+
class Ability
|
5
5
|
CANCAN_METHODS = [:can, :cannot, :can?, :cannot?]
|
6
6
|
# Returns a string with the given name
|
7
7
|
attr_reader :singular_name
|
@@ -22,11 +22,12 @@ module MoulinRouge
|
|
22
22
|
@is_group = options.delete(:group)
|
23
23
|
abilities.concat(parent.abilities) if not parent.nil? and parent.group?
|
24
24
|
instance_eval(&block) if block_given?
|
25
|
-
#
|
26
|
-
|
25
|
+
# Register this ability
|
26
|
+
MoulinRouge::Authorization.register(self) unless parent.nil? or group?
|
27
27
|
end
|
28
28
|
|
29
|
-
|
29
|
+
# Stores the CanCan methods and current_user method to later evaluation
|
30
|
+
def method_missing(name, *args, &block) #:nodoc:
|
30
31
|
return store_method(name, *args, &block) if CANCAN_METHODS.include?(name)
|
31
32
|
return MoulinRouge::ModelDouble.new if MoulinRouge.configuration.model_instance == name
|
32
33
|
super(name, *args, &block)
|
@@ -34,7 +35,7 @@ module MoulinRouge
|
|
34
35
|
|
35
36
|
# Define a new role inside this scope. If exists a role with the
|
36
37
|
# same name evaluate the block inside them instead of create a new one
|
37
|
-
def role(name, options = {}, &block)
|
38
|
+
def role(name = :main, options = {}, &block)
|
38
39
|
if children = find(name)
|
39
40
|
children.instance_eval(&block)
|
40
41
|
children
|
@@ -50,11 +51,37 @@ module MoulinRouge
|
|
50
51
|
role(name, options, &block)
|
51
52
|
end
|
52
53
|
|
54
|
+
# Appends all childrens and abilities from one object to another,
|
55
|
+
# raises an error if could not found a match.
|
56
|
+
def include(name)
|
57
|
+
unless from = MoulinRouge::Authorization.abilities[name]
|
58
|
+
raise RoleNotFound
|
59
|
+
end
|
60
|
+
from.childrens.each { |children| childrens << children.dup }
|
61
|
+
from.abilities.each { |ability| abilities << ability.dup }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a symbol with the name appended with the parents separeted by a underscore
|
65
|
+
def name
|
66
|
+
unless @name
|
67
|
+
@name = []
|
68
|
+
@name << self.parent.name.to_s if not self.parent.nil? and not self.parent.parent.nil?
|
69
|
+
@name << self.singular_name.to_s
|
70
|
+
@name = @name.join('_')
|
71
|
+
end
|
72
|
+
@name.to_sym
|
73
|
+
end
|
74
|
+
|
53
75
|
# Returns true if is a group
|
54
76
|
def group?
|
55
77
|
@is_group
|
56
78
|
end
|
57
79
|
|
80
|
+
# Returns true if is a role
|
81
|
+
def role?
|
82
|
+
!group?
|
83
|
+
end
|
84
|
+
|
58
85
|
# Add the given parameters to the authorizations list
|
59
86
|
def store_method(name, *args, &block)
|
60
87
|
abilities << MoulinRouge::CanCan::Method.new(name, *args, &block)
|
@@ -71,70 +98,16 @@ module MoulinRouge
|
|
71
98
|
@abilities ||= []
|
72
99
|
end
|
73
100
|
|
74
|
-
# Returns
|
75
|
-
#
|
101
|
+
# Returns an array with the abilities collected from this node and from
|
102
|
+
# their childrens.
|
76
103
|
def inherithed_abilities
|
77
104
|
abilities.concat(childrens.map(&:inherithed_abilities).flatten).uniq
|
78
105
|
end
|
79
106
|
|
80
|
-
# Execute all files in the given path in the class scope
|
81
|
-
def import(path)
|
82
|
-
Dir[path].each { |file| instance_eval(File.open(file).read) }
|
83
|
-
end
|
84
|
-
|
85
107
|
# Returns the instance of the children with the given name if exists and nil otherwise
|
86
108
|
def find(name)
|
87
109
|
childrens.each { |children| return children if children.name == name or children.singular_name == name }
|
88
110
|
return nil
|
89
111
|
end
|
90
|
-
|
91
|
-
# Appends all childrens and abilities from one object to another,
|
92
|
-
# raises an error if could not found a match.
|
93
|
-
def include(name)
|
94
|
-
unless from = self.class.all[name]
|
95
|
-
raise RoleNotFound
|
96
|
-
end
|
97
|
-
from.childrens.each { |children| childrens << children.dup }
|
98
|
-
from.abilities.each { |ability| abilities << ability.dup }
|
99
|
-
end
|
100
|
-
|
101
|
-
# Returns a symbol with the name appended with the parents separeted by a underscore
|
102
|
-
def name
|
103
|
-
unless @name
|
104
|
-
@name = []
|
105
|
-
@name << self.parent.name.to_s if not self.parent.nil? and not self.parent.parent.nil?
|
106
|
-
@name << self.singular_name.to_s
|
107
|
-
@name = @name.join('_')
|
108
|
-
end
|
109
|
-
@name.to_sym
|
110
|
-
end
|
111
|
-
|
112
|
-
class << self
|
113
|
-
# The instance of the main container, if don't exist create one
|
114
|
-
def main
|
115
|
-
@main ||= self.new(:main)
|
116
|
-
end
|
117
|
-
# Returns an array with the name of all roles created
|
118
|
-
def list
|
119
|
-
@list ||= []
|
120
|
-
end
|
121
|
-
|
122
|
-
# Returns an hash with all permissions defined
|
123
|
-
def all
|
124
|
-
@all ||= {}
|
125
|
-
end
|
126
|
-
|
127
|
-
# Stores the instance on the all hash and add the name to the list
|
128
|
-
def add(instance)
|
129
|
-
name = instance.name
|
130
|
-
self.all[name] = instance
|
131
|
-
self.list << name unless self.list.include?(name)
|
132
|
-
end
|
133
|
-
|
134
|
-
# Reset all constants
|
135
|
-
def reset! #:nodoc:
|
136
|
-
@main, @list, @all = nil
|
137
|
-
end
|
138
|
-
end
|
139
112
|
end
|
140
113
|
end
|