allowance 0.0.4 → 0.1.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.
data/.documentup.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "twitter": "hmans",
3
+ "travis": true
4
+ }
data/README.md CHANGED
@@ -1,73 +1,252 @@
1
1
  # Allowance
2
2
 
3
- Allowance is a general-use permission management library that can be used
4
- in any framework or application.
3
+ **Allowance is a general-use permission management library for Ruby.
4
+ It's decidedly simple, highly flexible, and has out-of-the-box support
5
+ for ActiveModel-compliant classes.**
6
+
7
+ It was inspired by Ryan Bates' fantastic Rails authorization plugin [cancan](https://github.com/ryanb/cancan) and, unlike most other gems
8
+ of its kind, is not bound to a specific framework.
5
9
 
6
10
  A simple Example:
7
11
 
8
12
  ``` ruby
9
13
  p = Allowance.define do |can|
10
- can.sing!
11
- can.play!
14
+ # Allow logging in
15
+ can.login!
16
+
17
+ # Allow creating new Article instances
18
+ can.create! Article
19
+
20
+ # Allow the user to edit Article instances that belong to him
21
+ can.edit! Article, :author_id => current_user.id
22
+
23
+ # Allow viewing all Article instances that are published or the user's
24
+ can.read! Article, ['published = ? OR author_id = ?', true, current_user.id]
12
25
  end
26
+ ```
27
+
28
+ Allowance parses these permission definitions and stores them in the object the
29
+ `Allowance.define` call returns. It is now up to you to query that object where
30
+ necessary. Some examples:
13
31
 
14
- p.sing? # true
15
- p.play? # true
16
- p.dance? # false
32
+ ``` ruby
33
+ p.login? # true
34
+ p.create? Article # true
35
+ p.read? @article # true or false, depending on state of @article
17
36
  ```
18
37
 
19
- You can specify permissions on objects (and their classes), too:
38
+ You can use the same object to provide you with correctly scoped models, too:
20
39
 
21
40
  ``` ruby
22
- p = Allowance.define do |can|
23
- # Everyone can view posts that have been published
24
- can.view! Post, :published => true
41
+ p.scoped_model(:view, Article).all
42
+ # -> Article.where(['published = ? OR author_id = ?', true, current_user.id]).all
43
+ ```
44
+
45
+
46
+
47
+ ## Installation
48
+
49
+ ### Requirements
50
+
51
+ Allowance should work fine with Ruby 1.8.7, 1.9.2, 1.9.3 and respective JRuby versions. Please consult Allowance's [Travis status page](http://travis-ci.org/hmans/allowance) for details.
52
+
53
+ [![Build Status](https://secure.travis-ci.org/hmans/allowance.png?branch=master)](http://travis-ci.org/hmans/allowance)
54
+
55
+ ### Installing through Bundler
56
+
57
+ Well, you've done this before, haven't you? Just add the `allowance` gem to your project's Gemfile:
58
+
59
+ ``` ruby
60
+ gem 'allowance'
61
+ ```
62
+
63
+ ### Installing without Bundler
64
+
65
+ Install using RubyGems:
66
+
67
+ ```
68
+ gem install allowance
69
+ ```
70
+
71
+ Then require it in your code:
72
+
73
+ ```
74
+ require 'rubygems'
75
+ require 'allowance'
76
+ ```
77
+
78
+
79
+ ## Usage
80
+
81
+ ### Defining permissions
82
+
83
+ Use `Allowance.define` to create a new permissions object, then use its `allow!` or `can!`
84
+ methods to add permissions:
85
+
86
+ ``` ruby
87
+ p = Allowance.define
88
+ p.allow! :sing
89
+ ```
25
90
 
26
- # Post owners can delete their own posts
27
- can.delete! Post, :user_id => current_user.id
91
+ Instead of using `allow!` or `can!`, you can just name the permission directly:
28
92
 
29
- # Admin users can view and delete all posts
30
- if current_user.admin?
31
- can.view! Post
32
- can.delete! Post
93
+ ``` ruby
94
+ p.sing!
95
+ ```
96
+
97
+ You can also specify permissions as a block:
98
+
99
+ ``` ruby
100
+ p = Allowance.define do |allow|
101
+ allow.sing!
102
+ end
103
+ ```
104
+
105
+ ### Querying permissions
106
+
107
+ Similar to how you define permissions, you can use the `allowed?` or `can?` methods, or
108
+ query permissions directly by name. The following two lines are equivalent:
109
+
110
+ ``` ruby
111
+ p.allowed? :sing
112
+ p.can? :sing
113
+ p.sing?
114
+ ```
115
+
116
+ ### One-dimensional permissions
117
+
118
+ Allowance lets you define simple, one-dimenstional permissions like this:
119
+
120
+ ``` ruby
121
+ p = Allowance.define do |allow|
122
+ allow.sing!
123
+ allow.play!
124
+ allow.dance! if current_user.can_dance?
125
+ end
126
+ ```
127
+
128
+ ### Two-dimensional permissions
129
+
130
+ Most of the time, you will be using two-dimensional permissions, consisting of a
131
+ _verb_ and an _object_, with the object typically being some kind of model class.
132
+ For example:
133
+
134
+ ``` ruby
135
+ p = Allowance.define do |allow|
136
+ allow.view! Article
137
+
138
+ if current_user.is_admin?
139
+ allow.edit! Article
33
140
  end
34
141
  end
35
142
  ```
36
143
 
37
- Instead of condition hashes, you can specify lambdas. This is great for model
38
- classes that are ActiveModel based (eg. ActiveRecord, Mongoid etc.):
144
+ When querying for permissions, just pass an object as an additional parameter:
39
145
 
40
146
  ``` ruby
41
- p = Allowance.define do |can|
42
- # Everyone can view posts that have been published
43
- can.view! Post, lambda { |posts| posts.visible_posts }
147
+ p.edit? Article
148
+ ```
149
+
150
+ When you pass a class instance (instead of a class), Allowance will check for
151
+ the permission defined for its class, so the following will work, too:
152
+
153
+ ``` ruby
154
+ p.edit? @article
155
+ ```
156
+
157
+ Allowance will even allow you to define permissions in specific objects -- this is not recommended, though, since permission objects defined through Allowance only exist in memory; if you need to control permissions on individual model objects, you'll be better off with another authorization library, ideally one that stores its permissions in your datastore.
158
+
159
+
160
+ ### Defining model scopes
161
+
162
+ For classes implementing ActiveModel (eg. ActiveRecord, Mongoid and others), Allowance allows you to restrict certain permissions to specific scopes. For example, if, in a web application, a user should only be able to see articles that have been published, but admin users can see everything, you can define the permissions like this:
163
+
164
+ ``` ruby
165
+ p = Allowance.define do |allow|
166
+ allow.view! Article, :published => true
167
+
168
+ if current_user.is_admin?
169
+ allow.view! Article
170
+ end
44
171
  end
45
172
  ```
46
173
 
47
- More documentation coming up soon.
174
+ You can see here that when a specific permission (for a _verb_ and _object_ combination) is defined more than once, the last definition will overwrite all those that came before it.
48
175
 
49
- ## Installation
176
+ In the example above, we defined the scope as a so-called "where conditions hash", ie. a hash passed to the model class' `.where` method. Since the hash notation assumes a logical AND and isn't all that flexible overall, you can also use the same string/array syntax you know from ActiveModel:
177
+
178
+ ``` ruby
179
+ p.view! Article, ['published = ? OR user_id = ?', true, current_user.id]
180
+ ```
181
+
182
+ Lastly, you're encouraged to re-use scopes defined on the model class. Just define your scope using a lambda:
50
183
 
51
- Add this line to your application's Gemfile:
184
+ ``` ruby
185
+ p.view! Article, lambda { |r| r.viewable_by(current_user) }
186
+ ```
52
187
 
53
- gem 'allowance'
188
+ Not only can use check permissions against those scopes, but you can also use the `#scoped_model` method to retreive a correctly scoped model according to its previous scope definition. For example:
54
189
 
55
- And then execute:
190
+ ``` ruby
191
+ @articles = p.scoped_model(:view, Article).order('created_at DESC').all
192
+ ```
56
193
 
57
- $ bundle
194
+ ### Defining contextual permissions
58
195
 
59
- Or install it yourself as:
196
+ Since permissions are just Ruby code, you can use all your favorite language
197
+ constructs when defining permissions. For example, in a web application
198
+ providing a `current_user` method, you can do the following:
60
199
 
61
- $ gem install allowance
200
+ ``` ruby
201
+ permissions = Allowance.define do |allow|
202
+ allow.read! Article
62
203
 
63
- ## Usage
204
+ if current_user.is_admin?
205
+ allow.destroy! Article
206
+ end
207
+ end
208
+ ```
209
+
210
+ You can then query this permission like you'd expect:
64
211
 
65
- TODO: Write usage instructions here
212
+ ``` ruby
213
+ @article = Article.find(params[:id])
214
+ if permissions.destroy? @article
215
+ @article.destroy
216
+ else
217
+ raise "You're not allowed to do this"
218
+ end
219
+ ```
66
220
 
67
221
  ## Contributing
68
222
 
69
- 1. Fork it
70
- 2. Create your feature branch (`git checkout -b my-new-feature`)
71
- 3. Commit your changes (`git commit -am 'Add some feature'`)
72
- 4. Push to the branch (`git push origin my-new-feature`)
73
- 5. Create new Pull Request
223
+ I'm looking forward to seeing your Pull Requests. However, please be aware that,
224
+ like with pretty much all other projects I'm maintaining, I'm trying to keep it
225
+ as small as possible, so I'm going to be somewhat picky about which pull
226
+ requests to accept. If you're adding new features, I recommed you check back
227
+ with me first in order to avoid disappointment.
228
+
229
+ ## License
230
+
231
+ Copyright (c) 2012 Hendrik Mans <hendrik@mans.de>
232
+
233
+ MIT License
234
+
235
+ Permission is hereby granted, free of charge, to any person obtaining
236
+ a copy of this software and associated documentation files (the
237
+ "Software"), to deal in the Software without restriction, including
238
+ without limitation the rights to use, copy, modify, merge, publish,
239
+ distribute, sublicense, and/or sell copies of the Software, and to
240
+ permit persons to whom the Software is furnished to do so, subject to
241
+ the following conditions:
242
+
243
+ The above copyright notice and this permission notice shall be
244
+ included in all copies or substantial portions of the Software.
245
+
246
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
247
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
248
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
249
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
250
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
251
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
252
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -20,6 +20,8 @@ module Allowance
20
20
  false
21
21
  end
22
22
 
23
+ alias_method :can?, :allowed?
24
+
23
25
  def allow!(verbs, objects = nil, scope = true, &blk)
24
26
  expand_permissions(verbs).each do |verb|
25
27
  [objects].flatten.each do |object|
@@ -28,6 +30,8 @@ module Allowance
28
30
  end
29
31
  end
30
32
 
33
+ alias_method :can!, :allow!
34
+
31
35
  def method_missing(name, *args, &blk)
32
36
  if name.to_s =~ /(.+)!$/
33
37
  allow!($1.to_sym, *args, &blk)
@@ -40,12 +44,10 @@ module Allowance
40
44
 
41
45
  def scoped_model(verb, model)
42
46
  if p = @permissions[[verb, model]]
43
- if p.is_a?(Hash)
44
- model.where(p)
45
- elsif p.is_a?(Proc)
46
- p.call(model)
47
- else
48
- model
47
+ case p
48
+ when Hash, String, Array then model.where(p)
49
+ when Proc then p.call(model)
50
+ else model
49
51
  end
50
52
  else
51
53
  model.where(false)
@@ -1,3 +1,3 @@
1
1
  module Allowance
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -5,6 +5,14 @@ module Allowance
5
5
  SomeClass = Class.new
6
6
  SomeOtherClass = Class.new
7
7
 
8
+ it "should allow permissions to be specified and queried through its instance methods" do
9
+ subject.can! :sing
10
+ subject.allow! :dance
11
+
12
+ insist subject.can? :dance
13
+ insist subject.allowed? :sing
14
+ end
15
+
8
16
  it "should allow simple permissions to be specified" do
9
17
  subject.moo!
10
18
 
@@ -73,7 +81,7 @@ module Allowance
73
81
  subject.scoped_model(:view, model).should == scoped_model
74
82
  end
75
83
 
76
- it "should allow scopes to be defined through where conditions" do
84
+ it "should allow scopes to be defined through a conditions hash" do
77
85
  model = mock
78
86
  model.should_receive(:where).with(:awesome => true).and_return(scoped_model = mock)
79
87
 
@@ -81,6 +89,24 @@ module Allowance
81
89
 
82
90
  subject.scoped_model(:view, model).should == scoped_model
83
91
  end
92
+
93
+ it "should allow scopes to be defined through a conditions string" do
94
+ model = mock
95
+ model.should_receive(:where).with("awesome = true").and_return(scoped_model = mock)
96
+
97
+ subject.view! model, "awesome = true"
98
+
99
+ subject.scoped_model(:view, model).should == scoped_model
100
+ end
101
+
102
+ it "should allow scopes to be defined through a conditions array" do
103
+ model = mock
104
+ model.should_receive(:where).with(["awesome = ?", true]).and_return(scoped_model = mock)
105
+
106
+ subject.view! model, ["awesome = ?", true]
107
+
108
+ subject.scoped_model(:view, model).should == scoped_model
109
+ end
84
110
  end
85
111
  end
86
112
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: allowance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-28 00:00:00.000000000 Z
12
+ date: 2012-05-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70168031557120 !ruby/object:Gem::Requirement
16
+ requirement: &70231867448560 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70168031557120
24
+ version_requirements: *70231867448560
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70168031554220 !ruby/object:Gem::Requirement
27
+ requirement: &70231867447800 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70168031554220
35
+ version_requirements: *70231867447800
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: watchr
38
- requirement: &70168031553600 !ruby/object:Gem::Requirement
38
+ requirement: &70231867444600 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70168031553600
46
+ version_requirements: *70231867444600
47
47
  description: A generic, but decidedly awesome authorization control layer.
48
48
  email:
49
49
  - hendrik@mans.de
@@ -51,12 +51,12 @@ executables: []
51
51
  extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
+ - .documentup.json
54
55
  - .gitignore
55
56
  - .rspec
56
57
  - .travis.yml
57
58
  - .watchr
58
59
  - Gemfile
59
- - LICENSE
60
60
  - README.md
61
61
  - Rakefile
62
62
  - allowance.gemspec
data/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2012 Hendrik Mans
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.