canned 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in canned.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ require "canned/version"
2
+ require "canned/controller_ext"
3
+
4
+ # Extend action controller
5
+ if defined? ActionController::Base
6
+ ActionController::Base.class_eval do
7
+ include Canned::ControllerExt
8
+ end
9
+ end
@@ -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,10 @@
1
+ module Canned
2
+ module Context
3
+ class Default < Base
4
+ include Matchers::The
5
+ include Matchers::Load
6
+ include Matchers::AsksWith
7
+ include Matchers::AsksFor
8
+ end
9
+ end
10
+ 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,13 @@
1
+ module Canned
2
+ module Context
3
+ module Matchers
4
+ module Helpers
5
+
6
+ def self.resolve(_target, _key)
7
+ if _target.is_a? Hash then _target[_key] else _target.send(_key) end
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+ 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