consul 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of consul might be problematic. Click here for more details.

data/lib/consul.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require 'consul/power'
2
2
  require 'consul/errors'
3
3
  require 'consul/controller'
4
+ require 'consul/active_record'
@@ -0,0 +1,14 @@
1
+ module Consul
2
+ module ActiveRecord
3
+
4
+ private
5
+
6
+ def authorize_values_for(property, options = {})
7
+ method_defined?(:power) or attr_accessor :power
8
+ assignable_values_for property, options.merge(:through => lambda { ::Power.current })
9
+ end
10
+
11
+ end
12
+ end
13
+
14
+ ActiveRecord::Base.send(:extend, Consul::ActiveRecord)
@@ -8,6 +8,14 @@ module Consul
8
8
 
9
9
  module ClassMethods
10
10
 
11
+ def current_power_initializer
12
+ @current_power_initializer || (superclass.respond_to?(:current_power_initializer) && superclass.current_power_initializer)
13
+ end
14
+
15
+ def current_power_initializer=(initializer)
16
+ @current_power_initializer = initializer
17
+ end
18
+
11
19
  private
12
20
 
13
21
  def require_power_check(options = {})
@@ -18,6 +26,12 @@ module Consul
18
26
  skip_before_filter :unchecked_power, options
19
27
  end
20
28
 
29
+ def current_power(&initializer)
30
+ self.current_power_initializer = initializer
31
+ around_filter :with_current_power
32
+ helper_method :current_power
33
+ end
34
+
21
35
  def power(*args)
22
36
 
23
37
  args_copy = args.dup
@@ -72,6 +86,21 @@ module Consul
72
86
  raise Consul::UncheckedPower, "This controller does not check against a power"
73
87
  end
74
88
 
89
+ def current_power
90
+ @current_power_class && @current_power_class.current
91
+ end
92
+
93
+ def with_current_power(&action)
94
+ power = instance_eval(&self.class.current_power_initializer) or raise Consul::Error, 'current_power initializer returned nil'
95
+ @current_power_class = power.class
96
+ @current_power_class.current = power
97
+ action.call
98
+ ensure
99
+ if @current_power_class
100
+ @current_power_class.current = nil
101
+ end
102
+ end
103
+
75
104
  end
76
105
 
77
106
  end
data/lib/consul/errors.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Consul
2
- class Powerless < StandardError; end
3
- class UncheckedPower < StandardError; end
4
- class UnmappedAction < StandardError; end
2
+ class Error < StandardError; end
3
+ class Powerless < Error; end
4
+ class UncheckedPower < Error; end
5
+ class UnmappedAction < Error; end
5
6
  end
data/lib/consul/power.rb CHANGED
@@ -50,6 +50,8 @@ module Consul
50
50
  "#{name.to_s.singularize}_ids"
51
51
  end
52
52
 
53
+ attr_accessor :current
54
+
53
55
  end
54
56
 
55
57
  end
@@ -0,0 +1,3 @@
1
+ module Consul
2
+ VERSION = '0.2.0'
3
+ end
@@ -3,10 +3,8 @@ class ApplicationController < ActionController::Base
3
3
 
4
4
  require_power_check
5
5
 
6
- private
7
-
8
- def current_power
9
- Power.new User.new
6
+ current_power do
7
+ Power.new(User.new)
10
8
  end
11
9
 
12
10
  end
@@ -1,8 +1,19 @@
1
1
  class DashboardsController < ApplicationController
2
2
 
3
- power :dashboard
3
+ power :always_true
4
4
 
5
5
  def show
6
+ observe(current_power)
7
+ end
8
+
9
+ def error
10
+ raise 'error during action'
11
+ end
12
+
13
+ private
14
+
15
+ def observe(object)
16
+ # test spy
6
17
  end
7
18
 
8
19
  end
@@ -1,13 +1,12 @@
1
1
  class SongsController < ApplicationController
2
2
 
3
- # power check is missing
4
-
5
- skip_power_check :only => :index
3
+ # power check is missing, but we're skipping it for a single action'
4
+ skip_power_check :only => :show
6
5
 
7
6
  def show
8
7
  end
9
8
 
10
- def index
9
+ def update
11
10
  end
12
11
 
13
12
  end
@@ -1,11 +1,11 @@
1
1
  class UsersController < ApplicationController
2
2
 
3
- power :admin, :map => { :index => :dashboard }
3
+ power :always_false, :map => { :show => :always_true }
4
4
 
5
- def index
5
+ def show
6
6
  end
7
7
 
8
- def show
8
+ def update
9
9
  end
10
10
 
11
11
  end
@@ -1,7 +1,7 @@
1
1
  class Power
2
2
  include Consul::Power
3
3
 
4
- def initialize(user)
4
+ def initialize(user = nil)
5
5
  @user = user
6
6
  end
7
7
 
@@ -17,16 +17,20 @@ class Power
17
17
  Note.scoped(:joins => :client)
18
18
  end
19
19
 
20
- power :admin do
20
+ power :always_true do
21
+ true
22
+ end
23
+
24
+ power :always_false do
21
25
  false
22
26
  end
23
27
 
24
- power :moderator do
28
+ power :always_nil do
25
29
  nil
26
30
  end
27
-
28
- power :dashboard do
29
- true
31
+
32
+ def assignable_user_roles
33
+ %w[guest admin]
30
34
  end
31
35
 
32
36
  end
@@ -1,6 +1,6 @@
1
1
  ActionController::Routing::Routes.draw do |map|
2
2
 
3
- map.resource :dashboard
3
+ map.resource :dashboard, :member => { :error => :post }
4
4
 
5
5
  map.resources :songs
6
6
 
@@ -1,7 +1,9 @@
1
1
  class CreateUsers < ActiveRecord::Migration
2
2
 
3
3
  def self.up
4
- create_table :users
4
+ create_table :users do |t|
5
+ t.string :role
6
+ end
5
7
  end
6
8
 
7
9
  def self.down
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Consul::ActiveRecord do
4
+
5
+ describe '.authorize_values_for' do
6
+
7
+ it 'should be a shortcut for .assignable_values_for :attribute, :through => lambda { ::Power.current }' do
8
+ klass = Note.disposable_copy
9
+ klass.should_receive(:assignable_values_for).with(:attribute, :option => 'option', :through => kind_of(Proc))
10
+ klass.class_eval do
11
+ authorize_values_for :attribute, :option => 'option'
12
+ end
13
+ end
14
+
15
+ it 'should generate a getter and setter for a @power field' do
16
+ klass = Note.disposable_copy do
17
+ authorize_values_for :attribute
18
+ end
19
+ song = klass.new
20
+ song.should respond_to(:power)
21
+ song.should respond_to(:power=)
22
+ end
23
+
24
+ it 'should obtain assignable values from Power.current' do
25
+ Power.current = Power.new
26
+ klass = User.disposable_copy do
27
+ authorize_values_for :role
28
+ end
29
+ user = klass.new
30
+ user.assignable_roles.should =~ %w[guest admin]
31
+ user.should allow_value('guest').for(:role)
32
+ user.should_not allow_value('invalid-value').for(:role)
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -72,15 +72,15 @@ describe Consul::Power do
72
72
  end
73
73
 
74
74
  it 'should return true when the queried power is not a scope, but returns true' do
75
- @user.power.dashboard?.should be_true
75
+ @user.power.always_true?.should be_true
76
76
  end
77
77
 
78
78
  it 'should return false when the queried power is not a scope, but returns false' do
79
- @user.power.admin?.should be_false
79
+ @user.power.always_false?.should be_false
80
80
  end
81
81
 
82
82
  it 'should return false when the queried power is not a scope, but returns nil' do
83
- @user.power.moderator?.should be_false
83
+ @user.power.always_nil?.should be_false
84
84
  end
85
85
 
86
86
  end
@@ -100,15 +100,26 @@ describe Consul::Power do
100
100
  end
101
101
 
102
102
  it 'should not raise Consul::Powerless when the queried power is not a scope, but returns true' do
103
- expect { @user.power.dashboard! }.to_not raise_error
103
+ expect { @user.power.always_true! }.to_not raise_error
104
104
  end
105
105
 
106
106
  it 'should raise Consul::Powerless when the queried power is not a scope, but returns false' do
107
- expect { @user.power.admin! }.to raise_error
107
+ expect { @user.power.always_false! }.to raise_error(Consul::Powerless)
108
108
  end
109
109
 
110
110
  it 'should raise Consul::Powerless when the queried power is not a scope, but returns nil' do
111
- expect { @user.power.moderator! }.to raise_error
111
+ expect { @user.power.always_nil! }.to raise_error(Consul::Powerless)
112
+ end
113
+
114
+ end
115
+
116
+ describe '.current' do
117
+
118
+ it 'should provide a class method to set and get the current Power' do
119
+ Power.current = 'current power'
120
+ Power.current.should == 'current power'
121
+ Power.current = nil
122
+ Power.current.should be_nil
112
123
  end
113
124
 
114
125
  end
@@ -6,4 +6,21 @@ describe DashboardsController do
6
6
  expect { get :show }.to_not raise_error
7
7
  end
8
8
 
9
+ it 'should define a method #current_power that returns the Power' do
10
+ controller.should_receive(:observe).with(kind_of(Power))
11
+ get :show
12
+ end
13
+
14
+ it "should set the current power before the request, and nilify it after the request" do
15
+ controller.send(:current_power).should be_nil
16
+ Power.should_receive_and_execute(:current=).ordered.with(kind_of(Power))
17
+ Power.should_receive_and_execute(:current=).ordered.with(nil)
18
+ get :show
19
+ end
20
+
21
+ it 'should nilify the current power even if the action raises an error' do
22
+ expect { post :error }.to raise_error(/error during action/)
23
+ Power.current.should be_nil
24
+ end
25
+
9
26
  end
@@ -2,12 +2,12 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe SongsController do
4
4
 
5
- it "should raise an error if an action is not checked against a power" do
6
- expect { get :show, :id => '123' }.to raise_error(Consul::UncheckedPower)
5
+ it 'should allow to skip a required power check' do
6
+ expect { get :show, :id => '1' }.to_not raise_error
7
7
  end
8
8
 
9
- it 'should allow to skip a required power check' do
10
- expect { get :index }.to_not raise_error
9
+ it "should raise an error if an action is not checked against a power" do
10
+ expect { put :update, :id => '1' }.to raise_error(Consul::UncheckedPower)
11
11
  end
12
12
 
13
13
  end
@@ -3,11 +3,11 @@ require File.dirname(__FILE__) + '/../spec_helper'
3
3
  describe UsersController do
4
4
 
5
5
  it "should raise an error if the checked power is not given" do
6
- expect { get :show, :id => '1' }.to raise_error(Consul::Powerless)
6
+ expect { get :update, :id => '1' }.to raise_error(Consul::Powerless)
7
7
  end
8
8
 
9
- it 'should allow to map actions to another power' do
10
- expect { get :index }.to_not raise_error
9
+ it 'should allow to map actions to another power using the :map option' do
10
+ expect { get :show, :id => '1' }.to_not raise_error
11
11
  end
12
12
 
13
13
  end
data/spec/spec_helper.rb CHANGED
@@ -5,12 +5,21 @@ ENV['RAILS_ENV'] ||= 'in_memory'
5
5
 
6
6
  # Load the Rails environment and testing framework
7
7
  require "#{File.dirname(__FILE__)}/app_root/config/environment"
8
- require "#{File.dirname(__FILE__)}/../lib/consul"
9
8
  require 'spec/rails'
10
9
 
10
+ # Load dependencies
11
+ require 'assignable_values'
12
+ require 'shoulda-matchers'
13
+
14
+ # Load the gem itself
15
+ require "#{File.dirname(__FILE__)}/../lib/consul"
16
+
11
17
  # Undo changes to RAILS_ENV
12
18
  silence_warnings {RAILS_ENV = ENV['RAILS_ENV']}
13
19
 
20
+ # Requires supporting files with custom matchers and macros, etc in ./support/ and its subdirectories.
21
+ Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
22
+
14
23
  # Run the migrations
15
24
  ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
16
25
 
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,179 @@
1
+ # From https://makandracards.com/makandra/627-the-definitive-spec_candy-rb-rspec-helper
2
+ Object.class_eval do
3
+
4
+ def should_receive_chain(*parts)
5
+ setup_expectation_chain(parts)
6
+ end
7
+
8
+ def self.new_with_stubs(attrs)
9
+ new.tap do |obj|
10
+ obj.stub_existing attrs
11
+ end
12
+ end
13
+
14
+ def stub_existing(attrs)
15
+ attrs.each do |method, value|
16
+ if respond_to?(method, true)
17
+ stub(method => value)
18
+ else
19
+ raise "Attempted to stub non-existing method ##{method} on a #{self.class.name}"
20
+ end
21
+ end
22
+ end
23
+
24
+ def should_receive_and_return(methods_and_values)
25
+ methods_and_values.each do |method, value|
26
+ should_receive(method).and_return(value)
27
+ end
28
+ end
29
+
30
+ def should_receive_all_with(methods_and_values)
31
+ methods_and_values.each do |method, value|
32
+ should_receive(method).with(value)
33
+ end
34
+ end
35
+
36
+ def should_not_receive_and_execute(method)
37
+ should_receive_and_execute(method, true)
38
+ end
39
+
40
+ def should_receive_and_execute(method, negate = false)
41
+ method_base = method.to_s.gsub(/([\?\!\=\[\]]+)$/, '')
42
+ method_suffix = $1
43
+
44
+ method_called = "_#{method_base}_called#{method_suffix}"
45
+ method_with_spy = "#{method_base}_with_spy#{method_suffix}"
46
+ method_without_spy = "#{method_base}_without_spy#{method_suffix}"
47
+
48
+ prototype = respond_to?(:singleton_class) ? singleton_class : metaclass
49
+ prototype.class_eval do
50
+
51
+ unless method_defined?(method_with_spy)
52
+
53
+ define_method method_called do
54
+ end
55
+
56
+ define_method method_with_spy do |*args, &block|
57
+ send(method_called, *args)
58
+ send(method_without_spy, *args, &block)
59
+ end
60
+ alias_method_chain method, :spy
61
+ end
62
+
63
+ end
64
+
65
+ expectation = negate ? :should_not_receive : :should_receive
66
+ send(expectation, method_called)
67
+ end
68
+
69
+ private
70
+
71
+ def setup_expectation_chain(parts)
72
+ obj = self
73
+ for part in parts
74
+ if part == parts.last
75
+ obj = add_expectation_chain_link(obj, part)
76
+ else
77
+ next_obj = Spec::Mocks::Mock.new('chain link')
78
+ add_expectation_chain_link(obj, part).at_least(:once).and_return(next_obj)
79
+ obj = next_obj
80
+ end
81
+ end
82
+ obj
83
+ end
84
+
85
+ def add_expectation_chain_link(obj, part)
86
+ if part.is_a?(Array)
87
+ obj.should_receive(part.first).with(*part[1..-1])
88
+ else
89
+ obj.should_receive(part)
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+ Spec::Example::ExampleGroupMethods.class_eval do
96
+
97
+ # Improves it_should_behave_like in some ways:
98
+ # - It scopes the reused examples so #let und #subject does not bleed into the reusing example groups
99
+ # - It allows to parametrize the reused example group by appending a hash argument.
100
+ # Every key/value pair in the hash will become a #let variable for the reused example group
101
+ # - You can call it with a block. It will be available to the reused example group as let(:block)
102
+ def it_should_act_like(shared_example_group, environment = {}, &block)
103
+ description = "as #{shared_example_group}"
104
+ description << " (#{environment.inspect})" if environment.present?
105
+ describe description do
106
+ environment.each do |name, value|
107
+ let(name) { value }
108
+ end
109
+ let(:block) { block } if block
110
+ it_should_behave_like(shared_example_group)
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ Spec::Rails::Example::ModelExampleGroup.class_eval do
117
+
118
+ def self.it_should_run_callbacks_in_order(*callbacks)
119
+ callbacks.push(:ordered => true)
120
+ it_should_run_callbacks(*callbacks)
121
+ end
122
+
123
+ def self.it_should_run_callbacks(*callbacks)
124
+ options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
125
+ reason = callbacks.pop if callbacks.last.is_a?(String)
126
+ hook = description_parts.last.sub(/^#/, '')
127
+ should = ['should run callbacks', callbacks.inspect, ('in order' if options[:ordered]), reason].compact.join ' '
128
+ send(:it, should) do
129
+ callbacks.each do |callback|
130
+ expectation = subject.should_receive(callback).once
131
+ expectation.ordered if options[:ordered]
132
+ end
133
+ run_state_machine_callbacks_from_prose(hook) || subject.run_callbacks(hook)
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ def run_state_machine_callbacks_from_prose(prose)
140
+ if parts = prose.match(/^(\w+) from ([\:\w]+) to ([\:\w]+)$/)
141
+ name = parts[1].to_sym
142
+ from = parts[2].sub(/^:/, '').to_sym
143
+ to = parts[3].sub(/^:/, '').to_sym
144
+ transition = StateMachine::Transition.new(subject, subject.class.state_machine, name, from, to)
145
+ transition.run_callbacks
146
+ end
147
+ end
148
+
149
+ end
150
+
151
+ ActiveRecord::Base.class_eval do
152
+
153
+ # Prevents the databse from being touched, but still runs all validations.
154
+ def keep_invalid!
155
+ errors.stub :empty? => false
156
+ end
157
+
158
+ end
159
+
160
+ Class.class_eval do
161
+
162
+ define_method :stub_any_instance do |stubs|
163
+ unstubbed_new = method(:new)
164
+ stub(:new).and_return do |*args|
165
+ unstubbed_new.call(*args).tap do |obj|
166
+ obj.stub stubs
167
+ end
168
+ end
169
+ stubs
170
+ end
171
+
172
+ define_method :disposable_copy do |&body|
173
+ this = self
174
+ copy = Class.new(self, &body)
175
+ copy.singleton_class.send(:define_method, :name) { this.name }
176
+ copy
177
+ end
178
+
179
+ end