cancan 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,18 @@
1
+ 1.0.0 (Dec 13, 2009)
2
+
3
+ * Don't set resource instance variable if it has been set already - see issue #13
4
+
5
+ * Allowing :nested option to accept an array for deep nesting
6
+
7
+ * Adding :nested option to load resource method - see issue #10
8
+
9
+ * Pass :only and :except options to before filters for load/authorize resource methods.
10
+
11
+ * Adding :collection and :new options to load_resource method so we can specify behavior of additional actions if needed.
12
+
13
+ * BACKWARDS INCOMPATIBLE: turning load and authorize resource methods into class methods which set up the before filter so they can accept additional arguments.
14
+
15
+
1
16
  0.2.1 (Nov 26, 2009)
2
17
 
3
18
  * many internal refactorings - see issues #11 and #12
@@ -6,6 +21,7 @@
6
21
 
7
22
  * support custom objects (usually symbols) in can definition - see issue #8
8
23
 
24
+
9
25
  0.2.0 (Nov 17, 2009)
10
26
 
11
27
  * fix behavior of load_and_authorize_resource for namespaced controllers - see issue #3
@@ -10,7 +10,7 @@ See the RDocs[http://rdoc.info/projects/ryanb/cancan] and Wiki[http://wiki.githu
10
10
 
11
11
  You can set it up as a gem in your environment.rb file.
12
12
 
13
- config.gem "cancan", :source => "http://gemcutter.org"
13
+ config.gem "cancan"
14
14
 
15
15
  And then install the gem.
16
16
 
@@ -21,7 +21,7 @@ Alternatively you can install it as a Rails plugin.
21
21
  script/plugin install git://github.com/ryanb/cancan.git
22
22
 
23
23
 
24
- == Setup
24
+ == Getting Started
25
25
 
26
26
  First, define a class called Ability in "models/ability.rb".
27
27
 
@@ -52,10 +52,10 @@ You can also use these methods in a controller along with the "unauthorized!" me
52
52
  unauthorized! if cannot? :read, @article
53
53
  end
54
54
 
55
- Setting this for every action can be tedious, therefore a before filter is also provided to automatically authorize all actions in a RESTful style resource controller.
55
+ Setting this for every action can be tedious, therefore the load_and_authorize_resource method is also provided to automatically authorize all actions in a RESTful style resource controller. It will set up a before filter which loads the resource into the instance variable and authorizes it.
56
56
 
57
57
  class ArticlesController < ApplicationController
58
- before_filter :load_and_authorize_resource
58
+ load_and_authorize_resource
59
59
 
60
60
  def show
61
61
  # @article is already loaded
@@ -150,6 +150,30 @@ The following aliases are added by default for conveniently mapping common contr
150
150
  alias_action :edit, :to => :update
151
151
 
152
152
 
153
+ == Authorizing Controller Actions
154
+
155
+ As mentioned in the Getting Started section, you can use the +load_and_authorize_resource+ method in your controller to load the resource into an instance variable and authorize it. If you have a nested resource you can specify that as well.
156
+
157
+ load_and_authorize_resource :nested => :author
158
+
159
+ You can also pass an array to the :+nested+ attribute for deep nesting.
160
+
161
+ If you want to customize the loading behavior on certain actions, you can do so in a before filter.
162
+
163
+ class BooksController < ApplicationController
164
+ before_filter :find_book_by_permalink, :only => :show
165
+ load_and_authorize_resource
166
+
167
+ private
168
+
169
+ def find_book_by_permalink
170
+ @book = Book.find_by_permalink!(params[:id)
171
+ end
172
+ end
173
+
174
+ Here the @book instance variable is already set so it will not be loaded again for that action. This works for nested resources as well.
175
+
176
+
153
177
  == Assumptions & Configuring
154
178
 
155
179
  CanCan makes two assumptions about your application.
@@ -5,5 +5,6 @@ module CanCan
5
5
  end
6
6
 
7
7
  require File.dirname(__FILE__) + '/cancan/ability'
8
+ require File.dirname(__FILE__) + '/cancan/controller_resource'
8
9
  require File.dirname(__FILE__) + '/cancan/resource_authorization'
9
10
  require File.dirname(__FILE__) + '/cancan/controller_additions'
@@ -3,7 +3,109 @@ module CanCan
3
3
  # This module is automatically included into all controllers.
4
4
  # It also makes the "can?" and "cannot?" methods available to all views.
5
5
  module ControllerAdditions
6
+ module ClassMethods
7
+ # Sets up a before filter which loads and authorizes the current resource. This performs both
8
+ # load_resource and authorize_resource and accepts the same arguments. See those methods for details.
9
+ #
10
+ # class BooksController < ApplicationController
11
+ # load_and_authorize_resource
12
+ # end
13
+ #
14
+ def load_and_authorize_resource(options = {})
15
+ before_filter(options.slice(:only, :except)) { |c| ResourceAuthorization.new(c, c.params, options.except(:only, :except)).load_and_authorize_resource }
16
+ end
17
+
18
+ # Sets up a before filter which loads the appropriate model resource into an instance variable.
19
+ # For example, given an ArticlesController it will load the current article into the @article
20
+ # instance variable. It does this by either calling Article.find(params[:id]) or
21
+ # Article.new(params[:article]) depending upon the action. It does nothing for the "index"
22
+ # action.
23
+ #
24
+ # Call this method directly on the controller class.
25
+ #
26
+ # class BooksController < ApplicationController
27
+ # load_resource
28
+ # end
29
+ #
30
+ # A resource is not loaded if the instance variable is already set. This makes it easy to override
31
+ # the behavior through a before_filter on certain actions.
32
+ #
33
+ # class BooksController < ApplicationController
34
+ # before_filter :find_book_by_permalink, :only => :show
35
+ # load_resource
36
+ #
37
+ # private
38
+ #
39
+ # def find_book_by_permalink
40
+ # @book = Book.find_by_permalink!(params[:id)
41
+ # end
42
+ # end
43
+ #
44
+ # See load_and_authorize_resource to automatically authorize the resource too.
45
+ #
46
+ # Options:
47
+ # [:+only+]
48
+ # Only applies before filter to given actions.
49
+ #
50
+ # [:+except+]
51
+ # Does not apply before filter to given actions.
52
+ #
53
+ # [:+nested+]
54
+ # Specify which resource this is nested under.
55
+ #
56
+ # load_resource :nested => :author
57
+ #
58
+ # Deep nesting can be defined in an array.
59
+ #
60
+ # load_resource :nested => [:publisher, :author]
61
+ #
62
+ # [:+collection+]
63
+ # Specify which actions are resource collection actions in addition to :+index+. This
64
+ # is usually not necessary because it will try to guess depending on if an :+id+
65
+ # is present in +params+.
66
+ #
67
+ # load_resource :collection => [:sort, :list]
68
+ #
69
+ # [:+new+]
70
+ # Specify which actions are new resource actions in addition to :+new+ and :+create+.
71
+ # Pass an action name into here if you would like to build a new resource instead of
72
+ # fetch one.
73
+ #
74
+ # load_resource :new => :build
75
+ #
76
+ def load_resource(options = {})
77
+ before_filter(options.slice(:only, :except)) { |c| ResourceAuthorization.new(c, c.params, options.except(:only, :except)).load_resource }
78
+ end
79
+
80
+ # Sets up a before filter which authorizes the current resource using the instance variable.
81
+ # For example, if you have an ArticlesController it will check the @article instance variable
82
+ # and ensure the user can perform the current action on it. Under the hood it is doing
83
+ # something like the following.
84
+ #
85
+ # unauthorized! if cannot?(params[:action].to_sym, @article || Article)
86
+ #
87
+ # Call this method directly on the controller class.
88
+ #
89
+ # class BooksController < ApplicationController
90
+ # authorize_resource
91
+ # end
92
+ #
93
+ # See load_and_authorize_resource to automatically load the resource too.
94
+ #
95
+ # Options:
96
+ # [:+only+]
97
+ # Only applies before filter to given actions.
98
+ #
99
+ # [:+except+]
100
+ # Does not apply before filter to given actions.
101
+ #
102
+ def authorize_resource(options = {})
103
+ before_filter(options.slice(:only, :except)) { |c| ResourceAuthorization.new(c, c.params, options.except(:only, :except)).authorize_resource }
104
+ end
105
+ end
106
+
6
107
  def self.included(base)
108
+ base.extend ClassMethods
7
109
  base.helper_method :can?, :cannot?
8
110
  end
9
111
 
@@ -70,48 +172,6 @@ module CanCan
70
172
  def cannot?(*args)
71
173
  (@current_ability ||= current_ability).cannot?(*args)
72
174
  end
73
-
74
- # This method loads the appropriate model resource into an instance variable. For example,
75
- # given an ArticlesController it will load the current article into the @article instance
76
- # variable. It does this by either calling Article.find(params[:id]) or
77
- # Article.new(params[:article]) depending upon the action. It does nothing for the "index"
78
- # action.
79
- #
80
- # You would often use this as a before filter in the controller. See
81
- # load_and_authorize_resource to handle authorization too.
82
- #
83
- # before_filter :load_resource
84
- #
85
- def load_resource
86
- ResourceAuthorization.new(self, params).load_resource
87
- end
88
-
89
- # Authorizes the resource in the current instance variable. For example,
90
- # if you have an ArticlesController it will check the @article instance variable
91
- # and ensure the user can perform the current action on it.
92
- # Under the hood it is doing something like the following.
93
- #
94
- # unauthorized! if cannot?(params[:action].to_sym, @article || Article)
95
- #
96
- # You would often use this as a before filter in the controller.
97
- #
98
- # before_filter :authorize_resource
99
- #
100
- # See load_and_authorize_resource to automatically load the resource too.
101
- def authorize_resource
102
- ResourceAuthorization.new(self, params).authorize_resource
103
- end
104
-
105
- # Calls load_resource to load the current resource model into an instance variable.
106
- # Then calls authorize_resource to ensure the current user is authorized to access the page.
107
- # You would often use this as a before filter in the controller.
108
- #
109
- # before_filter :load_and_authorize_resource
110
- #
111
- def load_and_authorize_resource
112
- load_resource
113
- authorize_resource
114
- end
115
175
  end
116
176
  end
117
177
 
@@ -0,0 +1,39 @@
1
+ module CanCan
2
+ class ControllerResource # :nodoc:
3
+ def initialize(controller, name, parent = nil)
4
+ @controller = controller
5
+ @name = name
6
+ @parent = parent
7
+ end
8
+
9
+ def model_class
10
+ @name.to_s.camelize.constantize
11
+ end
12
+
13
+ def find(id)
14
+ self.model_instance ||= base.find(id)
15
+ end
16
+
17
+ def build(attributes)
18
+ if base.kind_of? Class
19
+ self.model_instance ||= base.new(attributes)
20
+ else
21
+ self.model_instance ||= base.build(attributes)
22
+ end
23
+ end
24
+
25
+ def model_instance
26
+ @controller.instance_variable_get("@#{@name}")
27
+ end
28
+
29
+ def model_instance=(instance)
30
+ @controller.instance_variable_set("@#{@name}", instance)
31
+ end
32
+
33
+ private
34
+
35
+ def base
36
+ @parent ? @parent.model_instance.send(@name.to_s.pluralize) : model_class
37
+ end
38
+ end
39
+ end
@@ -2,9 +2,10 @@ module CanCan
2
2
  class ResourceAuthorization # :nodoc:
3
3
  attr_reader :params
4
4
 
5
- def initialize(controller, params)
5
+ def initialize(controller, params, options = {})
6
6
  @controller = controller
7
7
  @params = params
8
+ @options = options
8
9
  end
9
10
 
10
11
  def load_and_authorize_resource
@@ -13,29 +14,44 @@ module CanCan
13
14
  end
14
15
 
15
16
  def load_resource
16
- self.model_instance = params[:id] ? model_class.find(params[:id]) : model_class.new(params[model_name.to_sym]) unless params[:action] == "index"
17
+ unless collection_actions.include? params[:action].to_sym
18
+ if new_actions.include? params[:action].to_sym
19
+ resource.build(params[model_name.to_sym])
20
+ elsif params[:id]
21
+ resource.find(params[:id])
22
+ end
23
+ end
17
24
  end
18
25
 
19
26
  def authorize_resource
20
- @controller.unauthorized! if @controller.cannot?(params[:action].to_sym, model_instance || model_class)
27
+ @controller.unauthorized! if @controller.cannot?(params[:action].to_sym, resource.model_instance || resource.model_class)
21
28
  end
22
29
 
23
30
  private
24
31
 
25
- def model_name
26
- params[:controller].split('/').last.singularize
32
+ def resource
33
+ @resource ||= ControllerResource.new(@controller, model_name, parent_resource)
34
+ end
35
+
36
+ def parent_resource
37
+ parent = nil
38
+ [@options[:nested]].flatten.compact.each do |name|
39
+ parent = ControllerResource.new(@controller, name, parent)
40
+ parent.find(@params["#{name}_id".to_sym])
41
+ end
42
+ parent
27
43
  end
28
44
 
29
- def model_class
30
- model_name.camelcase.constantize
45
+ def model_name
46
+ params[:controller].split('/').last.singularize
31
47
  end
32
48
 
33
- def model_instance
34
- @controller.instance_variable_get("@#{model_name}")
49
+ def collection_actions
50
+ [:index] + [@options[:collection]].flatten
35
51
  end
36
52
 
37
- def model_instance=(instance)
38
- @controller.instance_variable_set("@#{model_name}", instance)
53
+ def new_actions
54
+ [:new, :create] + [@options[:new]].flatten
39
55
  end
40
56
  end
41
57
  end
@@ -27,19 +27,21 @@ describe CanCan::ControllerAdditions do
27
27
  @controller.cannot?(:foo, :bar).should be_true
28
28
  end
29
29
 
30
- it "should load resource" do
31
- mock.instance_of(CanCan::ResourceAuthorization).load_resource
32
- @controller.load_resource
30
+ it "load_and_authorize_resource should setup a before filter which passes call to ResourceAuthorization" do
31
+ stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_and_authorize_resource
32
+ mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) }
33
+ @controller_class.load_and_authorize_resource :foo => :bar
33
34
  end
34
35
 
35
- it "should authorize resource" do
36
- mock.instance_of(CanCan::ResourceAuthorization).authorize_resource
37
- @controller.authorize_resource
36
+ it "authorize_resource should setup a before filter which passes call to ResourceAuthorization" do
37
+ stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.authorize_resource
38
+ mock(@controller_class).before_filter(:except => :show) { |options, block| block.call(@controller) }
39
+ @controller_class.authorize_resource :foo => :bar, :except => :show
38
40
  end
39
41
 
40
- it "should load and authorize resource in one call through controller" do
41
- mock(@controller).load_resource
42
- mock(@controller).authorize_resource
43
- @controller.load_and_authorize_resource
42
+ it "load_resource should setup a before filter which passes call to ResourceAuthorization" do
43
+ stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_resource
44
+ mock(@controller_class).before_filter(:only => [:show, :index]) { |options, block| block.call(@controller) }
45
+ @controller_class.load_resource :foo => :bar, :only => [:show, :index]
44
46
  end
45
47
  end
@@ -0,0 +1,43 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe CanCan::ControllerResource do
4
+ before(:each) do
5
+ @controller = Object.new
6
+ end
7
+
8
+ it "should determine model class by constantizing give name" do
9
+ CanCan::ControllerResource.new(@controller, :ability).model_class.should == Ability
10
+ end
11
+
12
+ it "should fetch model through model class and assign it to the instance" do
13
+ stub(Ability).find(123) { :some_ability }
14
+ CanCan::ControllerResource.new(@controller, :ability).find(123)
15
+ @controller.instance_variable_get(:@ability).should == :some_ability
16
+ end
17
+
18
+ it "should fetch model through parent and assign it to the instance" do
19
+ parent = Object.new
20
+ stub(parent).model_instance.stub!.abilities.stub!.find(123) { :some_ability }
21
+ CanCan::ControllerResource.new(@controller, :ability, parent).find(123)
22
+ @controller.instance_variable_get(:@ability).should == :some_ability
23
+ end
24
+
25
+ it "should build model through model class and assign it to the instance" do
26
+ stub(Ability).new(123) { :some_ability }
27
+ CanCan::ControllerResource.new(@controller, :ability).build(123)
28
+ @controller.instance_variable_get(:@ability).should == :some_ability
29
+ end
30
+
31
+ it "should build model through parent and assign it to the instance" do
32
+ parent = Object.new
33
+ stub(parent).model_instance.stub!.abilities.stub!.build(123) { :some_ability }
34
+ CanCan::ControllerResource.new(@controller, :ability, parent).build(123)
35
+ @controller.instance_variable_get(:@ability).should == :some_ability
36
+ end
37
+
38
+ it "should not load resource if instance variable is already provided" do
39
+ @controller.instance_variable_set(:@ability, :some_ability)
40
+ CanCan::ControllerResource.new(@controller, :ability).find(123)
41
+ @controller.instance_variable_get(:@ability).should == :some_ability
42
+ end
43
+ end
@@ -56,4 +56,44 @@ describe CanCan::ResourceAuthorization do
56
56
  authorization.authorize_resource
57
57
  }.should raise_error(CanCan::AccessDenied)
58
58
  end
59
+
60
+ it "should call load_resource and authorize_resource for load_and_authorize_resource" do
61
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
62
+ mock(authorization).load_resource
63
+ mock(authorization).authorize_resource
64
+ authorization.load_and_authorize_resource
65
+ end
66
+
67
+ it "should not build a resource when on custom collection action" do
68
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "sort"}, {:collection => [:sort, :list]})
69
+ authorization.load_resource
70
+ @controller.instance_variable_get(:@ability).should be_nil
71
+ end
72
+
73
+ it "should build a resource when on custom new action even when params[:id] exists" do
74
+ stub(Ability).new(nil) { :some_resource }
75
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "build", :id => 123}, {:new => :build})
76
+ authorization.load_resource
77
+ @controller.instance_variable_get(:@ability).should == :some_resource
78
+ end
79
+
80
+ it "should not try to load resource for other action if params[:id] is undefined" do
81
+ authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "list")
82
+ authorization.load_resource
83
+ @controller.instance_variable_get(:@ability).should be_nil
84
+ end
85
+
86
+ it "should load nested resource and fetch other resource through the association" do
87
+ stub(Person).find(456).stub!.abilities.stub!.find(123) { :some_ability }
88
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "show", :id => 123, :person_id => 456}, {:nested => :person})
89
+ authorization.load_resource
90
+ @controller.instance_variable_get(:@ability).should == :some_ability
91
+ end
92
+
93
+ it "should load nested resource and build resource through a deep association" do
94
+ stub(Person).find(456).stub!.behaviors.stub!.find(789).stub!.abilities.stub!.build(nil) { :some_ability }
95
+ authorization = CanCan::ResourceAuthorization.new(@controller, {:controller => "abilities", :action => "new", :person_id => 456, :behavior_id => 789}, {:nested => [:person, :behavior]})
96
+ authorization.load_resource
97
+ @controller.instance_variable_get(:@ability).should == :some_ability
98
+ end
59
99
  end
@@ -16,3 +16,7 @@ class Ability
16
16
  def initialize(user)
17
17
  end
18
18
  end
19
+
20
+ # this class helps out in testing nesting
21
+ class Person
22
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cancan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Bates
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-26 00:00:00 -08:00
12
+ date: 2009-12-13 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -26,10 +26,12 @@ extra_rdoc_files:
26
26
  files:
27
27
  - lib/cancan/ability.rb
28
28
  - lib/cancan/controller_additions.rb
29
+ - lib/cancan/controller_resource.rb
29
30
  - lib/cancan/resource_authorization.rb
30
31
  - lib/cancan.rb
31
32
  - spec/cancan/ability_spec.rb
32
33
  - spec/cancan/controller_additions_spec.rb
34
+ - spec/cancan/controller_resource_spec.rb
33
35
  - spec/cancan/resource_authorization_spec.rb
34
36
  - spec/spec_helper.rb
35
37
  - LICENSE