cancan 0.2.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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