canned 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +39 -0
- data/Rakefile +23 -0
- data/lib/canned.rb +9 -0
- data/lib/canned/context/actor.rb +31 -0
- data/lib/canned/context/base.rb +40 -0
- data/lib/canned/context/default.rb +10 -0
- data/lib/canned/context/matchers/asks_for.rb +19 -0
- data/lib/canned/context/matchers/asks_with.rb +36 -0
- data/lib/canned/context/matchers/equality.rb +48 -0
- data/lib/canned/context/matchers/has.rb +23 -0
- data/lib/canned/context/matchers/helpers.rb +13 -0
- data/lib/canned/context/matchers/is.rb +23 -0
- data/lib/canned/context/matchers/load.rb +26 -0
- data/lib/canned/context/matchers/plus.rb +19 -0
- data/lib/canned/context/matchers/relation.rb +52 -0
- data/lib/canned/context/matchers/that.rb +23 -0
- data/lib/canned/context/matchers/the.rb +24 -0
- data/lib/canned/context/matchers/where.rb +36 -0
- data/lib/canned/context/multi.rb +8 -0
- data/lib/canned/context/resource.rb +11 -0
- data/lib/canned/context/value.rb +7 -0
- data/lib/canned/controller_ext.rb +216 -0
- data/lib/canned/definition.rb +79 -0
- data/lib/canned/errors.rb +6 -0
- data/lib/canned/profile.rb +62 -0
- data/lib/canned/profile_dsl.rb +130 -0
- data/lib/canned/stack.rb +63 -0
- data/lib/canned/version.rb +3 -0
- data/spec/canned/canned_spec.rb +116 -0
- data/spec/canned/controller_ext_spec.rb +95 -0
- data/spec/spec_helper.rb +35 -0
- metadata +113 -0
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Canned
|
2
|
+
|
3
|
+
Profile based authorization for ruby, with rails bindings.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'canned'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle install
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install canned
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## TODO
|
24
|
+
|
25
|
+
* TODO Section
|
26
|
+
* database backed profiles (maybe thats another gem...)
|
27
|
+
* Attribute accessibility helpers
|
28
|
+
|
29
|
+
## Credits
|
30
|
+
|
31
|
+
Some ideas about resource managing and general gem structure where taken from [canned](http://github.com/ryanb/cancan).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
1. Fork it
|
36
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
37
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
38
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
39
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Canned'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
# rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
Bundler::GemHelper.install_tasks
|
data/lib/canned.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Canned
|
2
|
+
module Context
|
3
|
+
|
4
|
+
class Actor < Base
|
5
|
+
include Matchers::Where
|
6
|
+
include Matchers::Has
|
7
|
+
include Matchers::Is
|
8
|
+
include Matchers::That
|
9
|
+
include Matchers::AsksFor
|
10
|
+
include Matchers::AsksWith
|
11
|
+
include Matchers::Load
|
12
|
+
|
13
|
+
def asks_with_same_id(*_args)
|
14
|
+
_args.all? { |a| asks_with_id(a).equal_to(own: a) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def asks_with_same(*_args)
|
18
|
+
_args.all? { |a| asks_with(a).equal_to(own: a) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def owns(_resource, _options={})
|
22
|
+
loads(_resource).that_belongs_to_it(_options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def belongs_to(_resource, _options={})
|
26
|
+
loads(_resource).to_which_it_belongs(_options)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Dir[File.dirname(__FILE__) + '/matchers/*.rb'].each { |file| require file }
|
2
|
+
|
3
|
+
module Canned
|
4
|
+
module Context
|
5
|
+
## Base class for other context types
|
6
|
+
class Base
|
7
|
+
|
8
|
+
def initialize(_ctx, _ext, _stack)
|
9
|
+
@ctx = _ctx
|
10
|
+
@ext = _ext
|
11
|
+
@stack = _stack
|
12
|
+
end
|
13
|
+
|
14
|
+
## The method missing callback is used to hook extensions provided by context.
|
15
|
+
def method_missing(_method, *_args, &_block)
|
16
|
+
ext = @ext[_method]
|
17
|
+
return super if ext.nil?
|
18
|
+
instance_exec(*_args, &exc)
|
19
|
+
end
|
20
|
+
|
21
|
+
## Returns true if context is in a "loaded" state
|
22
|
+
def indeed?
|
23
|
+
return @stack != false
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def _chain_context(_klass, _proc)
|
29
|
+
# this is the preferred way of changing the context type
|
30
|
+
# should be called by matchers that wish to change the context.
|
31
|
+
stack = if @stack then yield @stack else false end
|
32
|
+
|
33
|
+
if _proc.nil? then _klass.new(@ctx, @ext, stack)
|
34
|
+
elsif stack then _klass.new(@ctx, @ext, stack).instance_eval &_proc
|
35
|
+
else false end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Canned
|
2
|
+
module Context
|
3
|
+
module Matchers
|
4
|
+
module AsksFor
|
5
|
+
|
6
|
+
## Tests the action name
|
7
|
+
#
|
8
|
+
# @param [String|Symbol] _actions actions that will return true
|
9
|
+
# @returns [Boolean] true if current action matches any one of **_actions**
|
10
|
+
#
|
11
|
+
def asked_for(*_actions)
|
12
|
+
_actions.any? { |a| a.to_s == @ctx.action_name }
|
13
|
+
end
|
14
|
+
alias :asks_for :asked_for
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Canned
|
2
|
+
module Context
|
3
|
+
module Matchers
|
4
|
+
module AsksWith
|
5
|
+
|
6
|
+
## Loads a value context for a given parameter
|
7
|
+
#
|
8
|
+
# @param [String|Symbol] Parameter key
|
9
|
+
# @param [Hash] _options Various options:
|
10
|
+
# * as: If given, the value will use this value as alias for **where** blocks instead of the key.
|
11
|
+
# @param [Block] _block If given, then the block will be evaluated in the value context and the result
|
12
|
+
# of that returned by this function.
|
13
|
+
#
|
14
|
+
def asked_with(_key, _options={}, &_block)
|
15
|
+
_chain_context(Canned::Context::Value, _block) do |stack|
|
16
|
+
param = @ctx.params[_key]
|
17
|
+
break false if param.nil?
|
18
|
+
stack.push :value, _options.fetch(:as, _key), param
|
19
|
+
end
|
20
|
+
end
|
21
|
+
alias :asks_with :asked_with
|
22
|
+
|
23
|
+
## Same as **asked_with** but transforms parameter to an int.
|
24
|
+
def asked_with_id(_key, _options={}, &_block)
|
25
|
+
_chain_context(Canned::Context::Value, _block) do |stack|
|
26
|
+
param = @ctx.params[_key]
|
27
|
+
break false if param.nil?
|
28
|
+
stack.push :value, _options.fetch(:as, _key), param.to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
alias :asks_with_id :asked_with_id
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Canned
|
2
|
+
module Context
|
3
|
+
module Matchers
|
4
|
+
module Equality
|
5
|
+
|
6
|
+
## Returns true if a given value equals current context value.
|
7
|
+
#
|
8
|
+
# @param [Object] _value to compare to.
|
9
|
+
# @param [Hash] _options various options:
|
10
|
+
# * own: if set, context value is compared to the closest containing actor context attribute **own**.
|
11
|
+
#
|
12
|
+
def equal_to(_options={})
|
13
|
+
return false unless indeed?
|
14
|
+
@stack.top == _equality_load_value(_options)
|
15
|
+
end
|
16
|
+
|
17
|
+
## Works the same as **equal_to** but performs a **greater_than** comparison
|
18
|
+
def greater_than(_options={})
|
19
|
+
return false unless indeed?
|
20
|
+
@stack.top > _equality_load_value(_options)
|
21
|
+
end
|
22
|
+
|
23
|
+
## Works the same as **equal_to** but performs a **less_than** comparison
|
24
|
+
def less_than(_options={})
|
25
|
+
return false unless indeed?
|
26
|
+
@stack.top < _equality_load_value(_options)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @api auxiliary
|
32
|
+
def _equality_load_value(_options)
|
33
|
+
return _options unless _options.is_a? Hash
|
34
|
+
|
35
|
+
own = _options[:own]
|
36
|
+
if own
|
37
|
+
# use last actor as reference
|
38
|
+
actor = @stack.top(:actor)
|
39
|
+
raise Canned::SetupError.new '"own" option requires an enclosing actor context' if actor.nil?
|
40
|
+
return Helpers.resolve(actor, own)
|
41
|
+
end
|
42
|
+
|
43
|
+
return _options[:value]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Canned
|
2
|
+
module Context
|
3
|
+
module Matchers
|
4
|
+
module Has
|
5
|
+
|
6
|
+
## Acts on one of the top resource's attributes
|
7
|
+
#
|
8
|
+
# Examples:
|
9
|
+
# upon { loaded(:raffle).has(:app_id) { equal_to(20) or less_than(20) } }
|
10
|
+
# upon { loaded(:raffle).has('ceil(upper)').greater_than(20)
|
11
|
+
#
|
12
|
+
def has(_key, _options={}, &_block)
|
13
|
+
_chain_context(Canned::Context::Value, _block) do |stack|
|
14
|
+
value = Helpers.resolve(stack.top, _key)
|
15
|
+
stack.push(:value, _options.fetch(:as, _key), value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
alias :have :has
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Canned
|
2
|
+
module Context
|
3
|
+
module Matchers
|
4
|
+
module Is
|
5
|
+
## Test an expression or property
|
6
|
+
#
|
7
|
+
# Examples:
|
8
|
+
# upon { a(:calculator).is(:is_open?) }
|
9
|
+
# upon { the(:user).is('level > 20') }
|
10
|
+
#
|
11
|
+
# @param [String|Symbol] _key The value of _key is resolved in the current context object.
|
12
|
+
# @returns [Boolean] True if conditions are met
|
13
|
+
#
|
14
|
+
def is(_key)
|
15
|
+
return false unless indeed?
|
16
|
+
Helpers.resolve(@stack.top, _key)
|
17
|
+
end
|
18
|
+
|
19
|
+
alias :are :is
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Canned
|
2
|
+
module Context
|
3
|
+
module Matchers
|
4
|
+
module Load
|
5
|
+
|
6
|
+
## Loads a resource and returns a resource context.
|
7
|
+
#
|
8
|
+
# @param [String|Symbol] _name Resource name
|
9
|
+
# @param [Hash] _options Various options:
|
10
|
+
# * as: If given, the resource will use **as** as alias for **where** blocks instead of the name.
|
11
|
+
# @param [Block] _block If given, then the block will be evaluated in the resource context and the result
|
12
|
+
# of that returned by this function.
|
13
|
+
#
|
14
|
+
def loaded(_name, _options={}, &_block)
|
15
|
+
_chain_context(Canned::Context::Resource, _block) do |stack|
|
16
|
+
res = @ctx.resources[_name]
|
17
|
+
next false if res.nil?
|
18
|
+
stack.push(:resource, _options.fetch(:as, _name), res)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
alias :loads :loaded
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Canned
|
2
|
+
module Context
|
3
|
+
module Matchers
|
4
|
+
module Plus
|
5
|
+
|
6
|
+
## Very similar to **Load.load**, but does not accepts a block and
|
7
|
+
# returns **multi** context that only allows to execute where operations
|
8
|
+
def plus(_name, _options={})
|
9
|
+
_chain_context(Canned::Context::Multi, nil) do |stack|
|
10
|
+
resource = @ctx.resources[_resource]
|
11
|
+
return false if resource.nil?
|
12
|
+
@stack.push(:resource, _options.fetch(:as, _name), resource)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Canned
|
2
|
+
module Context
|
3
|
+
module Matchers
|
4
|
+
module Relation
|
5
|
+
|
6
|
+
def to_which_it_belongs(_options={})
|
7
|
+
return false unless indeed?
|
8
|
+
|
9
|
+
actor = @stack.top(:actor)
|
10
|
+
raise Canned::SetupError.new '"to_which_it_belongs" require an enclosing actor context' if actor.nil?
|
11
|
+
|
12
|
+
resource = @stack.top
|
13
|
+
|
14
|
+
as = _options[:as]
|
15
|
+
as = resource.class.name.parameterize if as.nil?
|
16
|
+
|
17
|
+
if actor.respond_to? :reflect_on_association
|
18
|
+
assoc = actor.reflect_on_association(as)
|
19
|
+
raise Canned::SetupError.new 'Invalid association name' if assoc.nil?
|
20
|
+
raise Canned::SetupError.new 'Thorugh assoc is not supported' if assoc.options.has_key? :through # TODO: support through!
|
21
|
+
raise Canned::SetupError.new 'Invalid association type' if assoc.macro != :belongs_to
|
22
|
+
actor.send(assoc.foreign_key) == resource.id
|
23
|
+
else
|
24
|
+
Helpers.resolve(resource, :id) == Helpers.resolve(actor, "#{as}_id".to_sym)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def that_belongs_to_it(_options={})
|
29
|
+
return false unless indeed?
|
30
|
+
|
31
|
+
actor = @stack.top(:actor)
|
32
|
+
raise Canned::SetupError.new '"that_belongs_to_it" require an enclosing actor context' if actor.nil?
|
33
|
+
resource = @stack.top
|
34
|
+
|
35
|
+
as = _options[:as]
|
36
|
+
as = resource.class.name.parameterize if as.nil?
|
37
|
+
|
38
|
+
if resource.respond_to? :reflect_on_association
|
39
|
+
assoc = resource.reflect_on_association(as)
|
40
|
+
raise Canned::SetupError.new 'Invalid association name' if assoc.nil?
|
41
|
+
raise Canned::SetupError.new 'Thorugh assoc is not supported' if assoc.options.has_key? :through # TODO: support through!
|
42
|
+
raise Canned::SetupError.new 'Invalid association type' if assoc.macro != :belongs_to
|
43
|
+
resource.send(assoc.foreign_key) == actor.id
|
44
|
+
else
|
45
|
+
Helpers.resolve(actor, :id) == Helpers.resolve(resource, "#{as}_id".to_sym)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|