joshuaclayton-sentinel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Joshua Clayton
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.textile ADDED
@@ -0,0 +1,224 @@
1
+ h1. Sentinel
2
+
3
+ Stupid-simple authorization for Rails
4
+
5
+ h2. Let's Start with an Example
6
+
7
+ Sentinels are objects that track permissions. They're flexible, handy, and very easy to use.
8
+
9
+ For example, here's a ForumSentinel:
10
+
11
+ <code><pre>
12
+ class ForumSentinel < Sentinel::Sentinel
13
+ def creatable?
14
+ current_user_admin?
15
+ end
16
+
17
+ def reorderable?
18
+ current_user_admin?
19
+ end
20
+
21
+ def viewable?
22
+ self.forum.public? || (current_user? && self.forum.members.include?(self.current_user)) || current_user_admin?
23
+ end
24
+
25
+ def editable?
26
+ (current_user? && self.forum.owner == self.current_user) || current_user_admin?
27
+ end
28
+
29
+ def destroyable?
30
+ editable?
31
+ end
32
+
33
+ private
34
+
35
+ def current_user?
36
+ !self.current_user.nil?
37
+ end
38
+
39
+ def current_user_admin?
40
+ current_user? && self.current_user.admin?
41
+ end
42
+ end
43
+ </pre></code>
44
+
45
+ So, what's this guy do? He personally tracks ability to essentially CRUD a forum, based on the current user.
46
+
47
+ How do we instantiate something like this?
48
+
49
+ forum_sentinel = ForumSentinel.new :current_user => User.first, :forum => Forum.first
50
+
51
+ From there, you can call methods like any PORO.
52
+
53
+ But, there's more.
54
+
55
+ You may be asking, "What about when I'm looping through a recordset and want to determine permissions on the fly?" That's a legitimate question, really. I've got an easy answer for you. Imagine your view looks something like this:
56
+
57
+ <code><pre>
58
+ <% @forums.each do |forum| %>
59
+ <% sentinel = ForumSentinel.new(:current_user => current_user, :forum => forum) %>
60
+ <% if sentinel.viewable? %>
61
+ <div id="<%= dom_id(forum) %>">
62
+ <h3><%= link_to h(forum.name), forum %></h3>
63
+ <%= textilize(forum.description) %>
64
+ </div>
65
+ <% end %>
66
+ <% end %>
67
+ </pre></code>
68
+
69
+ You get the idea. This is still pretty nasty though, since we're instantiating a new sentinel for each item in the recordset. Let's handle this in the controller.
70
+
71
+ <code><pre>
72
+ class ForumsController < ApplicationController
73
+ controls_access_with do
74
+ ForumSentinel.new :current_user => current_user, :forum => @forum
75
+ end
76
+
77
+ # ...etc
78
+ end
79
+ </pre></code>
80
+
81
+ Here, we setup the sentinel in the controller and make a @sentinel@ view helper to access the instantiated object. So, if `@forum` is set up in the show action, we'll have access to it. The index action, not so much. Not to fear.
82
+
83
+ <code><pre>
84
+ <% @forums.each do |forum| %>
85
+ <% if sentinel[:forum => forum].viewable? %>
86
+ <div id="<%= dom_id(forum) %>">
87
+ <h3><%= link_to h(forum.name), forum %></h3>
88
+ <%= textilize(forum.description) %>
89
+ </div>
90
+ <% end %>
91
+ <% end %>
92
+ </pre></code>
93
+
94
+ Essentially the same view as before, except we're not instantiating on every line and it keeps the view nice and clean. Notice we call `[]`, passing in a hash? Those are _temporary_ (as in, that call only) overrides. We assign forum to the current forum we're looping through and have the sentinel return permissions scoped to itself with whatever overrides.
95
+
96
+ So, handling permissions in the views are pretty easy now; hell, testing should be pretty simple too, since stubbing out simple methods like `viewable?`, `editable?`, etc will be cake.
97
+
98
+ "What about the controllers?" you may ask. Don't worry about the controllers; this is just as easy.
99
+
100
+ I introduce to you... @grants_access_to@.
101
+
102
+ <code><pre>
103
+ class ForumsController < ApplicationController
104
+ controls_access_with do
105
+ ForumSentinel.new :current_user => current_user, :forum => @forum
106
+ end
107
+
108
+ grants_access_to :reorderable?, :only => [:reorder]
109
+ grants_access_to :creatable?, :only => [:new, :create]
110
+ grants_access_to :viewable?, :only => [:show]
111
+ grants_access_to :destroyable?, :only => [:destroy]
112
+ end
113
+ </pre></code>
114
+
115
+ @grants_access_to@ is essentially a @before_filter@ on crack. It uses the sentinel we've set up and calls methods on it. So, if the sentinel returns true when :reorderable? is called, it won't deny the request. Other filters, however, may.
116
+
117
+ You need not call methods on the sentinel if you don't want to. Let's say you want to check if a user is logged in and an admin (contrived example, I know).
118
+
119
+ <code><pre>
120
+ class ForumsController < ApplicationController
121
+ controls_access_with do
122
+ ForumSentinel.new :current_user => current_user, :forum => @forum
123
+ end
124
+
125
+ grants_access_to :only => [:search] do
126
+ current_user && current_user.admin? && sentinel.creatable
127
+ end
128
+
129
+ grants_access_to :only => [:weird] do |s|
130
+ s.creatable? && s.forum.private?
131
+ end
132
+ end
133
+ </pre></code>
134
+
135
+ The first @grants_access_to@ evaluates in the scope of the controller. If the block passed has an arity of 1 (one required block-level variable), it evaluates in the context of the sentinel.
136
+
137
+ When granting access, you may want to handle different checks differently. You can essentially how the controller handles how things are denied. For example, you may want to include a couple basics within @ApplicationController@.
138
+
139
+ <code><pre>
140
+ class ApplicationController < ActionController::Base
141
+ on_denied_with :forbid_access do
142
+ respond_to do |wants|
143
+ wants.html { render :text => "You're forbidden to do this", :status => :forbidden }
144
+ wants.any { head :forbidden }
145
+ end
146
+ end
147
+
148
+ on_denied_with :redirect_home do
149
+ redirect_to root_path
150
+ end
151
+
152
+ # this would override the default denial handler
153
+ on_denied_with do
154
+ respond_to do |wants|
155
+ wants.html { render :text => "Unauthorized request", :status => :unauthorized }
156
+ wants.any { head :unauthorized }
157
+ end
158
+ end
159
+ end
160
+ </pre></code>
161
+
162
+ If these are set up, you can then have your actions deny with whatever you want, like so:
163
+
164
+ <code><pre>
165
+ class ForumsController < ApplicationController
166
+ controls_access_with do
167
+ ForumSentinel.new :current_user => current_user, :forum => @forum
168
+ end
169
+
170
+ grants_access_to :reorderable?, :only => [:reorder], :denies_with => :redirect_home
171
+ grants_access_to :creatable?, :only => [:new, :create]
172
+ grants_access_to :viewable?, :only => [:show], :denies_with => :unauthorized
173
+ grants_access_to :destroyable?, :only => [:destroy], :denies_with => :forbidden
174
+ end
175
+ </pre></code>
176
+
177
+ Testing the sentinels themselves are fairly easy to do; I won't go into detail with that.
178
+
179
+ Testing the controllers, however, can be a bit tricky. Luckily, there are a handful of Shoulda macros (easily grok'able, in case you want to port to RSpec or the like).
180
+
181
+ Here's a short example of what you may want to test:
182
+
183
+ <code><pre>
184
+ class SentinelControllerTest < ActionController::TestCase
185
+ include ActionView::Helpers::UrlHelper
186
+ include ActionView::Helpers::TagHelper
187
+
188
+ def setup
189
+ @controller = ForumsController.new
190
+ end
191
+
192
+ sentinel_context do
193
+ should_not_guard "get :index"
194
+ end
195
+
196
+ sentinel_context({:viewable? => true}) do
197
+ should_grant_access_to "get :show"
198
+ end
199
+
200
+ sentinel_context({:creatable? => false}) do
201
+ should_deny_access_to "get :new", :with => :redirect_to_index
202
+ should_deny_access_to "post :create, :forum => {:name => 'My New Forum'}", :with => :redirect_to_index
203
+ end
204
+
205
+ sentinel_context({:creatable? => true}) do
206
+ should_grant_access_to "get :new"
207
+ should_grant_access_to "post :create, :forum => {:name => 'My New Forum'}"
208
+ end
209
+ end
210
+ </pre></code>
211
+
212
+ @sentinel_context@ allows you to stub out responses for whatever methods you want on the sentinel. Assign attributes (`:current_user`, `:forum`, etc) or stub the permission methods themselves (that's what I would recommend, since your sentinel unit tests should check what the permissions return).
213
+
214
+ @should_not_guard@ ensures that @grants_access_to@ never gets called on that action. @should_grant_access_to@ and @should_deny_access_to@ are fairly straightforward. If @grants_access_to@ denies with a certain handler, you'll want to pass that handler name in (otherwise, you'll have failing tests).
215
+
216
+ h2. Why?
217
+
218
+ I'm all for putting permissions stuff like this in "presenters":http://htmltimes.com/presenters-in-Ruby-on-Rails-applications.php. However, my presenters have been getting fat, a bit harder to test, and in my mind, that's just not cool. I also hate trying to test controllers with a ton of contrived examples that are a pain in the ass to set up. This plugin provides the best of all worlds; encapsulated, easy-to-test permissions (controller, unit, AND view) that are simple to set up, extensible with different handlers, and easy to read.
219
+
220
+ h2. Questions or Comments?
221
+
222
+ If you like this plugin but have ideas, tweaks, fixes, or issues, shoot me a message on Github or fork/send a pull request. This is alpha software, so I'm pretty open to change.
223
+
224
+ Copyright (c) 2009 Joshua Clayton, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+ require 'rake/rdoctask'
5
+
6
+ Echoe.new("sentinel", "0.1.0") do |p|
7
+ p.description = "Simple authorization for Rails"
8
+ p.url = "http://github.com/joshuaclayton/sentinel"
9
+ p.author = "Joshua Clayton"
10
+ p.email = "joshua.clayton@gmail.com"
11
+ p.ignore_pattern = ["tmp/*"]
12
+ p.development_dependencies = ["actionpack >= 2.1.0"]
13
+ end
14
+
15
+ desc 'Generate documentation for the sentinel plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Sentinel'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,68 @@
1
+ module Sentinel
2
+ module Controller
3
+
4
+ def self.included(base)
5
+ base.send :include, InstanceMethods
6
+ base.extend ClassMethods
7
+
8
+ base.class_eval do
9
+ helper_method :sentinel
10
+ end
11
+
12
+ base.class_inheritable_accessor :sentinel, :access_denied, :access_granted
13
+
14
+ base.on_denied_with do
15
+ respond_to do |format|
16
+ format.html { render :text => "You do not have the proper privileges to access this page.", :status => :unauthorized }
17
+ format.any { head :unauthorized }
18
+ end
19
+ end
20
+
21
+ base.with_access do
22
+ true
23
+ end
24
+ end
25
+
26
+ module InstanceMethods
27
+ def sentinel
28
+ self.instance_eval(&self.class.sentinel)
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ def controls_access_with(&block)
34
+ self.sentinel = block
35
+ end
36
+
37
+ def on_denied_with(name = :default, &block)
38
+ self.access_denied ||= {}
39
+ self.access_denied[name] = block
40
+ end
41
+
42
+ def with_access(&block)
43
+ self.access_granted = block
44
+ end
45
+
46
+ def grants_access_to(*args, &block)
47
+ options = args.extract_options!
48
+ block = args.shift if args.first.respond_to?(:call)
49
+ sentinel_method = args.first
50
+ denied_handler = options.delete(:denies_with) || :default
51
+
52
+ before_filter(options) do |controller|
53
+ if block
54
+ if (block.arity == 1 ? controller.sentinel : controller).instance_eval(&block)
55
+ controller.instance_eval(&controller.class.access_granted)
56
+ else
57
+ controller.instance_eval(&controller.class.access_denied[denied_handler])
58
+ end
59
+ elsif sentinel_method && controller.sentinel && controller.sentinel.send(sentinel_method)
60
+ controller.instance_eval(&controller.class.access_granted)
61
+ else
62
+ controller.instance_eval(&controller.class.access_denied[denied_handler])
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,29 @@
1
+ module Sentinel
2
+ class Sentinel
3
+ def initialize(*args)
4
+ attributes = args.extract_options!
5
+ attributes.keys.each do |key|
6
+ create_accessor_for_attribute(key)
7
+ self.send("#{key}=", attributes[key]) if self.respond_to?("#{key}=")
8
+ end
9
+ end
10
+
11
+ def [](temporary_overrides)
12
+ temporary_overrides.keys.each do |key|
13
+ create_accessor_for_attribute(key)
14
+ end
15
+
16
+ returning self.clone do |duplicate|
17
+ temporary_overrides.keys.each do |key|
18
+ duplicate.send("#{key}=", temporary_overrides[key]) if self.respond_to?("#{key}=")
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def create_accessor_for_attribute(attribute)
26
+ self.class_eval { attr_accessor attribute } unless self.respond_to?(attribute) || self.respond_to?("#{attribute}=")
27
+ end
28
+ end
29
+ end
data/lib/sentinel.rb ADDED
@@ -0,0 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), "sentinel", "controller")
2
+ require File.join(File.dirname(__FILE__), "sentinel", "sentinel")
3
+
4
+ ActionController::Base.send :include, Sentinel::Controller
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib", "sentinel")
data/sentinel.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{sentinel}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Joshua Clayton"]
9
+ s.date = %q{2009-04-06}
10
+ s.description = %q{Simple authorization for Rails}
11
+ s.email = %q{joshua.clayton@gmail.com}
12
+ s.extra_rdoc_files = ["lib/sentinel/controller.rb", "lib/sentinel/sentinel.rb", "lib/sentinel.rb", "README.textile"]
13
+ s.files = ["lib/sentinel/controller.rb", "lib/sentinel/sentinel.rb", "lib/sentinel.rb", "Manifest", "MIT-LICENSE", "rails/init.rb", "Rakefile", "README.textile", "sentinel.gemspec", "shoulda_macros/sentinel.rb", "test/functional/sentinel_controller_test.rb", "test/partial_rails/controllers/application_controller.rb", "test/partial_rails/controllers/forums_controller.rb", "test/partial_rails/forum_sentinel.rb", "test/test_helper.rb", "test/unit/sentinel_test.rb"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/joshuaclayton/sentinel}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Sentinel", "--main", "README.textile"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{sentinel}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{Simple authorization for Rails}
21
+ s.test_files = ["test/functional/sentinel_controller_test.rb", "test/test_helper.rb", "test/unit/sentinel_test.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ s.add_development_dependency(%q<actionpack>, [">= 0", "= 2.1.0"])
29
+ else
30
+ s.add_dependency(%q<actionpack>, [">= 0", "= 2.1.0"])
31
+ end
32
+ else
33
+ s.add_dependency(%q<actionpack>, [">= 0", "= 2.1.0"])
34
+ end
35
+ end
@@ -0,0 +1,63 @@
1
+ module Sentinel
2
+ module Shoulda
3
+
4
+ def sentinel_context(options = {}, &block)
5
+ context "When sentinel is set up to #{options.inspect}" do
6
+ setup do
7
+ options.keys.each do |key|
8
+ @controller.sentinel.stubs(key).returns(options[key])
9
+ end
10
+ options.keys.each do |key|
11
+ assert_equal options[key], @controller.sentinel.send(key)
12
+ end
13
+ end
14
+
15
+ merge_block(&block)
16
+ end
17
+ end
18
+
19
+ def denied_with(denied_name, &block)
20
+ context "denied_with #{denied_name}" do
21
+ setup do
22
+ action = "action_#{Digest::MD5.hexdigest(Time.now.to_s.split(//).sort_by {rand}.join)}"
23
+ @controller.class.grants_access_to lambda { false }, :only => [action.to_sym], :denies_with => denied_name
24
+ get action.to_sym
25
+ end
26
+
27
+ merge_block(&block)
28
+ end
29
+ end
30
+
31
+ def should_grant_access_to(command)
32
+ context "performing `#{command}`" do
33
+ should "allow access" do
34
+ @controller.class.expects(:access_granted)
35
+ eval command
36
+ end
37
+ end
38
+ end
39
+
40
+ def should_deny_access_to(*args)
41
+ options = args.extract_options!
42
+ command = args.shift
43
+
44
+ context "performing `#{command}`" do
45
+ should "call the proper denied handler" do
46
+ @controller.class.access_denied.expects(:[]).with(options[:with] || :default)
47
+ eval command
48
+ end
49
+ end
50
+ end
51
+
52
+ def should_not_guard(command)
53
+ context "performing `#{command}`" do
54
+ should "not use guard with a sentinel" do
55
+ @controller.class.expects(:access_granted).never
56
+ @controller.class.expects(:access_denied).never
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ Test::Unit::TestCase.extend(Sentinel::Shoulda)
@@ -0,0 +1,44 @@
1
+ require 'test_helper'
2
+
3
+ class SentinelControllerTest < ActionController::TestCase
4
+ include ActionView::Helpers::UrlHelper
5
+ include ActionView::Helpers::TagHelper
6
+
7
+ def setup
8
+ @controller = ForumsController.new
9
+ end
10
+
11
+ sentinel_context do
12
+ should_not_guard "get :index"
13
+ end
14
+
15
+ sentinel_context({:viewable? => true}) do
16
+ should_grant_access_to "get :show"
17
+ end
18
+
19
+ sentinel_context({:creatable? => false}) do
20
+ should_deny_access_to "get :new", :with => :redirect_to_index
21
+ should_deny_access_to "post :create, :forum => {:name => 'My New Forum'}", :with => :redirect_to_index
22
+ end
23
+
24
+ sentinel_context({:creatable? => true}) do
25
+ should_grant_access_to "get :new"
26
+ should_grant_access_to "post :create, :forum => {:name => 'My New Forum'}"
27
+ end
28
+
29
+ sentinel_context({:viewable? => false}) do
30
+ should_deny_access_to "get :show", :with => :sentinel_unauthorized
31
+ end
32
+
33
+ denied_with :redirect_to_index do
34
+ should_redirect_to("forums root") { url_for(:controller => "forums")}
35
+ end
36
+
37
+ denied_with :sentinel_unauthorized do
38
+ should_respond_with :forbidden
39
+
40
+ should "render text as response" do
41
+ assert_equal "This is an even more unique default restricted warning", @response.body
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,51 @@
1
+ class ForumsController < ApplicationController
2
+ controls_access_with do
3
+ ForumSentinel.new :current_user => current_user, :forum => @forum
4
+ end
5
+
6
+ grants_access_to :reorderable?, :only => [:reorder]
7
+ grants_access_to :creatable?, :only => [:new, :create], :denies_with => :redirect_to_index
8
+ grants_access_to :viewable?, :only => [:show], :denies_with => :sentinel_unauthorized
9
+ grants_access_to :destroyable?, :only => [:destroy]
10
+
11
+ on_denied_with :redirect_to_index do
12
+ redirect_to url_for(:controller => "forums")
13
+ end
14
+
15
+ on_denied_with :sentinel_unauthorized do
16
+ respond_to do |wants|
17
+ wants.html { render :text => "This is an even more unique default restricted warning", :status => :forbidden }
18
+ wants.any { head :forbidden }
19
+ end
20
+ end
21
+
22
+ def index
23
+ handle_successfully
24
+ end
25
+
26
+ def new
27
+ handle_successfully
28
+ end
29
+
30
+ def show
31
+ handle_successfully
32
+ end
33
+
34
+ def edit
35
+ handle_successfully
36
+ end
37
+
38
+ def update
39
+ handle_successfully
40
+ end
41
+
42
+ def delete
43
+ handle_successfully
44
+ end
45
+
46
+ private
47
+
48
+ def handle_successfully
49
+ render :text => "forums"
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ class ForumSentinel < Sentinel::Sentinel
2
+ def creatable?
3
+ current_user_admin?
4
+ end
5
+
6
+ def reorderable?
7
+ current_user_admin?
8
+ end
9
+
10
+ def viewable?
11
+ self.forum.public? || (current_user? && self.forum.members.include?(self.current_user)) || current_user_admin?
12
+ end
13
+
14
+ def editable?
15
+ (current_user? && self.forum.owner == self.current_user) || current_user_admin?
16
+ end
17
+
18
+ def destroyable?
19
+ editable?
20
+ end
21
+
22
+ private
23
+
24
+ def current_user?
25
+ !self.current_user.nil?
26
+ end
27
+
28
+ def current_user_admin?
29
+ current_user? && self.current_user.admin?
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'active_support'
4
+ require 'active_support/test_case'
5
+
6
+ require 'action_controller'
7
+ require 'action_controller/test_case'
8
+
9
+ require File.join(File.dirname(__FILE__), "../rails/init")
10
+
11
+ require File.join(File.dirname(__FILE__), "partial_rails", "controllers", "application_controller")
12
+ require File.join(File.dirname(__FILE__), "partial_rails", "controllers", "forums_controller")
13
+ require File.join(File.dirname(__FILE__), "partial_rails", "forum_sentinel")
14
+ require File.join(File.dirname(__FILE__), "..", "shoulda_macros", "sentinel")
15
+
16
+ require 'redgreen'
17
+ require 'shoulda/rails'
18
+
19
+ ActionController::Routing::Routes.clear!
20
+ ActionController::Routing::Routes.draw {|m| m.connect ':controller/:action/:id' }
21
+ ActionController::Routing.use_controllers! "forums"
@@ -0,0 +1,56 @@
1
+ require 'test_helper'
2
+
3
+ class SentinelTest < ActiveSupport::TestCase
4
+ context "When assigning attributes" do
5
+ setup do
6
+ @sentinel = Sentinel::Sentinel
7
+ end
8
+
9
+ should "create attr_accessor's for each valid key" do
10
+ sentinel = @sentinel.new(:user => {:name => "John", :active => true}, :forum => {:name => "My Forum"})
11
+ assert_equal({:name => "John", :active => true}, sentinel.user)
12
+ assert_equal({:name => "My Forum"}, sentinel.forum)
13
+
14
+ sentinel.user = sentinel.forum = nil
15
+ assert_nil sentinel.user
16
+ assert_nil sentinel.forum
17
+ end
18
+
19
+ should "not create attr_accessors for methods that already exist" do
20
+ sentinel = @sentinel.new(:class => "fake", :to_s => "one", :user => "real")
21
+ assert_equal sentinel.user, "real"
22
+ assert_equal sentinel.class, Sentinel::Sentinel
23
+ assert_not_equal sentinel.to_s, "one"
24
+ end
25
+
26
+ should "reassign predefined attribute values if set" do
27
+ @sentinel.attr_accessor_with_default :message, "simple message"
28
+ assert_equal "simple message", @sentinel.new.message
29
+ assert_equal "complex message", @sentinel.new(:message => "complex message").message
30
+ end
31
+ end
32
+
33
+ context "When overriding attributes" do
34
+ setup do
35
+ @sentinel = Sentinel::Sentinel
36
+ end
37
+
38
+ should "only override for that specific instance" do
39
+ sentinel = @sentinel.new(:user => "assigned", :forum => nil)
40
+ assert_equal "assigned", sentinel.user
41
+ assert_nil sentinel.forum
42
+ assert_nil sentinel[:user => nil].user
43
+ assert_equal "forum", sentinel[:forum => "forum"].forum
44
+ end
45
+
46
+ should "define an attr_accessor if the attribute doesn't exist" do
47
+ sentinel = @sentinel.new
48
+ assert_raise NoMethodError do
49
+ sentinel.name
50
+ end
51
+
52
+ assert_equal "taken", sentinel[:name => "taken"].name
53
+ assert_nil sentinel.name
54
+ end
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: joshuaclayton-sentinel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joshua Clayton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-06 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: actionpack
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ - - "="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.1.0
27
+ version:
28
+ description: Simple authorization for Rails
29
+ email: joshua.clayton@gmail.com
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files:
35
+ - lib/sentinel/controller.rb
36
+ - lib/sentinel/sentinel.rb
37
+ - lib/sentinel.rb
38
+ - README.textile
39
+ files:
40
+ - lib/sentinel/controller.rb
41
+ - lib/sentinel/sentinel.rb
42
+ - lib/sentinel.rb
43
+ - Manifest
44
+ - MIT-LICENSE
45
+ - rails/init.rb
46
+ - Rakefile
47
+ - README.textile
48
+ - sentinel.gemspec
49
+ - shoulda_macros/sentinel.rb
50
+ - test/functional/sentinel_controller_test.rb
51
+ - test/partial_rails/controllers/application_controller.rb
52
+ - test/partial_rails/controllers/forums_controller.rb
53
+ - test/partial_rails/forum_sentinel.rb
54
+ - test/test_helper.rb
55
+ - test/unit/sentinel_test.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/joshuaclayton/sentinel
58
+ post_install_message:
59
+ rdoc_options:
60
+ - --line-numbers
61
+ - --inline-source
62
+ - --title
63
+ - Sentinel
64
+ - --main
65
+ - README.textile
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "1.2"
79
+ version:
80
+ requirements: []
81
+
82
+ rubyforge_project: sentinel
83
+ rubygems_version: 1.2.0
84
+ signing_key:
85
+ specification_version: 2
86
+ summary: Simple authorization for Rails
87
+ test_files:
88
+ - test/functional/sentinel_controller_test.rb
89
+ - test/test_helper.rb
90
+ - test/unit/sentinel_test.rb