cancan 1.3.4 → 1.4.1

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.
@@ -12,14 +12,18 @@ module CanCan
12
12
  # end
13
13
  #
14
14
  def load_and_authorize_resource(*args)
15
- ControllerResource.add_before_filter(self, :load_and_authorize_resource, *args)
15
+ cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
16
16
  end
17
17
 
18
18
  # Sets up a before filter which loads the model resource into an instance variable.
19
19
  # For example, given an ArticlesController it will load the current article into the @article
20
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.
21
+ # Article.new(params[:article]) depending upon the action. The index action will
22
+ # automatically set @articles to Article.accessible_by(current_ability).
23
+ #
24
+ # If a conditions hash is used in the Ability, the +new+ and +create+ actions will set
25
+ # the initial attributes based on these conditions. This way these actions will satisfy
26
+ # the ability restrictions.
23
27
  #
24
28
  # Call this method directly on the controller class.
25
29
  #
@@ -65,7 +69,14 @@ module CanCan
65
69
  # Does not apply before filter to given actions.
66
70
  #
67
71
  # [:+through+]
68
- # Load this resource through another one. This should match the name of the parent instance variable.
72
+ # Load this resource through another one. This should match the name of the parent instance variable or method.
73
+ #
74
+ # [:+through_association+]
75
+ # The name of the association to fetch the child records through the parent resource. This is normally not needed
76
+ # because it defaults to the pluralized resource name.
77
+ #
78
+ # [:+shallow+]
79
+ # Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+.
69
80
  #
70
81
  # [:+singleton+]
71
82
  # Pass +true+ if this is a singleton resource through a +has_one+ association.
@@ -99,7 +110,7 @@ module CanCan
99
110
  # load_resource :new => :build
100
111
  #
101
112
  def load_resource(*args)
102
- ControllerResource.add_before_filter(self, :load_resource, *args)
113
+ cancan_resource_class.add_before_filter(self, :load_resource, *args)
103
114
  end
104
115
 
105
116
  # Sets up a before filter which authorizes the resource using the instance variable.
@@ -148,8 +159,55 @@ module CanCan
148
159
  # [:+instance_name+]
149
160
  # The name of the instance variable for this resource.
150
161
  #
162
+ # [:+through+]
163
+ # Authorize conditions on this parent resource when instance isn't available.
164
+ #
151
165
  def authorize_resource(*args)
152
- ControllerResource.add_before_filter(self, :authorize_resource, *args)
166
+ cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
167
+ end
168
+
169
+ # Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
170
+ # If neither of these authorization methods are called, a CanCan::AuthorizationNotPerformed exception will be raised.
171
+ # This is normally added to the ApplicationController to ensure all controller actions do authorization.
172
+ #
173
+ # class ApplicationController < ActionController::Base
174
+ # check_authorization
175
+ # end
176
+ #
177
+ # Any arguments are passed to the +after_filter+ it triggers.
178
+ #
179
+ # See skip_authorization_check to bypass this check on specific controller actions.
180
+ def check_authorization(*args)
181
+ self.after_filter(*args) do |controller|
182
+ unless controller.instance_variable_defined?(:@_authorized)
183
+ raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
184
+ end
185
+ end
186
+ end
187
+
188
+ # Call this in the class of a controller to skip the check_authorization behavior on the actions.
189
+ #
190
+ # class HomeController < ApplicationController
191
+ # skip_authorization_check :only => :index
192
+ # end
193
+ #
194
+ # Any arguments are passed to the +before_filter+ it triggers.
195
+ def skip_authorization_check(*args)
196
+ self.before_filter(*args) do |controller|
197
+ controller.instance_variable_set(:@_authorized, true)
198
+ end
199
+ end
200
+
201
+ def skip_authorization(*args)
202
+ raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
203
+ end
204
+
205
+ def cancan_resource_class
206
+ if ancestors.map(&:to_s).include? "InheritedResources::Actions"
207
+ InheritedResource
208
+ else
209
+ ControllerResource
210
+ end
153
211
  end
154
212
  end
155
213
 
@@ -171,6 +229,16 @@ module CanCan
171
229
  #
172
230
  # authorize! :read, @article, :message => "Not authorized to read #{@article.name}"
173
231
  #
232
+ # You can also use I18n to customize the message. Action aliases defined in Ability work here.
233
+ #
234
+ # en:
235
+ # unauthorized:
236
+ # manage:
237
+ # all: "Not authorized to %{action} %{subject}."
238
+ # user: "Not allowed to manage other user accounts."
239
+ # update:
240
+ # project: "Not allowed to update this project."
241
+ #
174
242
  # You can rescue from the exception in the controller to customize how unauthorized
175
243
  # access is displayed to the user.
176
244
  #
@@ -185,12 +253,9 @@ module CanCan
185
253
  #
186
254
  # See the load_and_authorize_resource method to automatically add the authorize! behavior
187
255
  # to the default RESTful actions.
188
- def authorize!(action, subject, *args)
189
- message = nil
190
- if args.last.kind_of?(Hash) && args.last.has_key?(:message)
191
- message = args.pop[:message]
192
- end
193
- raise AccessDenied.new(message, action, subject) if cannot?(action, subject, *args)
256
+ def authorize!(*args)
257
+ @_authorized = true
258
+ current_ability.authorize!(*args)
194
259
  end
195
260
 
196
261
  def unauthorized!(message = nil)
@@ -223,6 +288,13 @@ module CanCan
223
288
  # <%= link_to "New Project", new_project_path %>
224
289
  # <% end %>
225
290
  #
291
+ # If it's a nested resource, you can pass the parent instance in a hash. This way it will
292
+ # check conditions which reach through that association.
293
+ #
294
+ # <% if can? :create, @category => Project %>
295
+ # <%= link_to "New Project", new_project_path %>
296
+ # <% end %>
297
+ #
226
298
  # This simply calls "can?" on the current_ability. See Ability#can?.
227
299
  def can?(*args)
228
300
  current_ability.can?(*args)
@@ -6,7 +6,7 @@ module CanCan
6
6
  options = args.extract_options!
7
7
  resource_name = args.first
8
8
  controller_class.before_filter(options.slice(:only, :except)) do |controller|
9
- ControllerResource.new(controller, resource_name, options.except(:only, :except)).send(method)
9
+ controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except)).send(method)
10
10
  end
11
11
  end
12
12
 
@@ -26,20 +26,22 @@ module CanCan
26
26
  end
27
27
 
28
28
  def load_resource
29
- if !resource_instance && (parent? || member_action?)
30
- @controller.instance_variable_set("@#{instance_name}", load_resource_instance)
29
+ if parent? || member_action?
30
+ self.resource_instance ||= load_resource_instance
31
+ elsif load_collection?
32
+ self.collection_instance ||= load_collection
31
33
  end
32
34
  end
33
35
 
34
36
  def authorize_resource
35
- @controller.authorize!(authorization_action, resource_instance || resource_class)
37
+ @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
36
38
  end
37
39
 
38
40
  def parent?
39
41
  @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
40
42
  end
41
43
 
42
- private
44
+ protected
43
45
 
44
46
  def load_resource_instance
45
47
  if !parent? && new_actions.include?(@params[:action].to_sym)
@@ -49,13 +51,34 @@ module CanCan
49
51
  end
50
52
  end
51
53
 
54
+ def load_collection?
55
+ resource_base.respond_to?(:accessible_by) &&
56
+ !current_ability.has_block?(authorization_action, resource_class)
57
+ end
58
+
59
+ def load_collection
60
+ resource_base.accessible_by(current_ability)
61
+ end
62
+
52
63
  def build_resource
53
- method_name = @options[:singleton] ? "build_#{name}" : "new"
54
- resource_base.send(*[method_name, @params[name]].compact)
64
+ if @options[:singleton] && resource_base.respond_to?("build_#{name}")
65
+ resource = resource_base.send("build_#{name}")
66
+ else
67
+ resource = resource_base.send("new")
68
+ end
69
+ initial_attributes.each do |name, value|
70
+ resource.send("#{name}=", value)
71
+ end
72
+ resource.attributes = @params[name] if @params[name]
73
+ resource
74
+ end
75
+
76
+ def initial_attributes
77
+ current_ability.attributes_for(@params[:action].to_sym, resource_class)
55
78
  end
56
79
 
57
80
  def find_resource
58
- if @options[:singleton]
81
+ if @options[:singleton] && resource_base.respond_to?(name)
59
82
  resource_base.send(name)
60
83
  else
61
84
  @options[:find_by] ? resource_base.send("find_by_#{@options[:find_by]}!", id_param) : resource_base.find(id_param)
@@ -86,24 +109,59 @@ module CanCan
86
109
  end
87
110
  end
88
111
 
112
+ def resource_class_with_parent
113
+ parent_resource ? {parent_resource => resource_class} : resource_class
114
+ end
115
+
116
+ def resource_instance=(instance)
117
+ @controller.instance_variable_set("@#{instance_name}", instance)
118
+ end
119
+
89
120
  def resource_instance
90
121
  @controller.instance_variable_get("@#{instance_name}")
91
122
  end
92
123
 
124
+ def collection_instance=(instance)
125
+ @controller.instance_variable_set("@#{instance_name.to_s.pluralize}", instance)
126
+ end
127
+
128
+ def collection_instance
129
+ @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
130
+ end
131
+
93
132
  # The object that methods (such as "find", "new" or "build") are called on.
94
133
  # If the :through option is passed it will go through an association on that instance.
134
+ # If the :shallow option is passed it will use the resource_class if there's no parent
95
135
  # If the :singleton option is passed it won't use the association because it needs to be handled later.
96
136
  def resource_base
97
- if through_resource
98
- @options[:singleton] ? through_resource : through_resource.send(name.to_s.pluralize)
137
+ if @options[:through]
138
+ if parent_resource
139
+ @options[:singleton] ? parent_resource : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
140
+ elsif @options[:shallow]
141
+ resource_class
142
+ else
143
+ raise AccessDenied # maybe this should be a record not found error instead?
144
+ end
99
145
  else
100
146
  resource_class
101
147
  end
102
148
  end
103
149
 
104
150
  # The object to load this resource through.
105
- def through_resource
106
- @options[:through] && [@options[:through]].flatten.map { |i| @controller.instance_variable_get("@#{i}") }.compact.first
151
+ def parent_resource
152
+ @options[:through] && [@options[:through]].flatten.map { |i| fetch_parent(i) }.compact.first
153
+ end
154
+
155
+ def fetch_parent(name)
156
+ if @controller.instance_variable_defined? "@#{name}"
157
+ @controller.instance_variable_get("@#{name}")
158
+ elsif @controller.respond_to? name
159
+ @controller.send(name)
160
+ end
161
+ end
162
+
163
+ def current_ability
164
+ @controller.send(:current_ability)
107
165
  end
108
166
 
109
167
  def name
@@ -5,6 +5,9 @@ module CanCan
5
5
  # Raised when removed code is called, an alternative solution is provided in message.
6
6
  class ImplementationRemoved < Error; end
7
7
 
8
+ # Raised when using check_authorization without calling authorized!
9
+ class AuthorizationNotPerformed < Error; end
10
+
8
11
  # This error is raised when a user isn't allowed to access a given controller action.
9
12
  # This usually happens within a call to ControllerAdditions#authorize! but can be
10
13
  # raised manually.
@@ -24,7 +27,8 @@ module CanCan
24
27
  # exception.default_message = "Default error message"
25
28
  # exception.message # => "Default error message"
26
29
  #
27
- # See ControllerAdditions#authorized! for more information on rescuing from this exception.
30
+ # See ControllerAdditions#authorized! for more information on rescuing from this exception
31
+ # and customizing the message using I18n.
28
32
  class AccessDenied < Error
29
33
  attr_reader :action, :subject
30
34
  attr_writer :default_message
@@ -0,0 +1,18 @@
1
+ module CanCan
2
+ # For use with Inherited Resources
3
+ class InheritedResource < ControllerResource # :nodoc:
4
+ def load_resource_instance
5
+ if parent?
6
+ @controller.send :parent
7
+ elsif new_actions.include? @params[:action].to_sym
8
+ @controller.send :build_resource
9
+ else
10
+ @controller.send :resource
11
+ end
12
+ end
13
+
14
+ def resource_base
15
+ @controller.send :end_of_association_chain
16
+ end
17
+ end
18
+ end
data/lib/cancan.rb CHANGED
@@ -5,3 +5,4 @@ require 'cancan/controller_additions'
5
5
  require 'cancan/active_record_additions'
6
6
  require 'cancan/exceptions'
7
7
  require 'cancan/query'
8
+ require 'cancan/inherited_resource'
@@ -24,6 +24,14 @@ describe CanCan::Ability do
24
24
  @ability.can?(:read, :some_symbol).should == true
25
25
  end
26
26
 
27
+ it "should pass nil to a block when no instance is passed" do
28
+ @ability.can :read, Symbol do |sym|
29
+ sym.should be_nil
30
+ true
31
+ end
32
+ @ability.can?(:read, Symbol).should be_true
33
+ end
34
+
27
35
  it "should pass to previous can definition, if block returns false or nil" do
28
36
  @ability.can :read, Symbol
29
37
  @ability.can :read, Integer do |i|
@@ -38,9 +46,8 @@ describe CanCan::Ability do
38
46
  @ability.can?(:read, 6).should be_false
39
47
  end
40
48
 
41
- it "should pass class with object if :all objects are accepted" do
42
- @ability.can :preview, :all do |object_class, object|
43
- object_class.should == Fixnum
49
+ it "should not pass class with object if :all objects are accepted" do
50
+ @ability.can :preview, :all do |object|
44
51
  object.should == 123
45
52
  @block_called = true
46
53
  end
@@ -48,23 +55,21 @@ describe CanCan::Ability do
48
55
  @block_called.should be_true
49
56
  end
50
57
 
51
- it "should pass class with no object if :all objects are accepted and class is passed directly" do
52
- @ability.can :preview, :all do |object_class, object|
53
- object_class.should == Hash
54
- object.should be_nil
58
+ it "should not call block when only class is passed, only return true" do
59
+ @block_called = false
60
+ @ability.can :preview, :all do |object|
55
61
  @block_called = true
56
62
  end
57
- @ability.can?(:preview, Hash)
58
- @block_called.should be_true
63
+ @ability.can?(:preview, Hash).should be_true
64
+ @block_called.should be_false
59
65
  end
60
66
 
61
- it "should pass action and object for global manage actions" do
62
- @ability.can :manage, Array do |action, object|
63
- action.should == :stuff
64
- object.should == [1, 2]
67
+ it "should pass only object for global manage actions" do
68
+ @ability.can :manage, String do |object|
69
+ object.should == "foo"
65
70
  @block_called = true
66
71
  end
67
- @ability.can?(:stuff, [1, 2]).should
72
+ @ability.can?(:stuff, "foo").should
68
73
  @block_called.should be_true
69
74
  end
70
75
 
@@ -82,10 +87,10 @@ describe CanCan::Ability do
82
87
  @ability.can?(:increment, 123).should be_true
83
88
  end
84
89
 
85
- it "should return block result for action, object_class, and object for any action" do
86
- @ability.can :manage, :all do |action, object_class, object|
90
+ it "should always call block with arguments when passing no arguments to can" do
91
+ @ability.can do |action, object_class, object|
87
92
  action.should == :foo
88
- object_class.should == Fixnum
93
+ object_class.should == 123.class
89
94
  object.should == 123
90
95
  @block_called = true
91
96
  end
@@ -93,6 +98,17 @@ describe CanCan::Ability do
93
98
  @block_called.should be_true
94
99
  end
95
100
 
101
+ it "should pass nil to object when comparing class with can check" do
102
+ @ability.can do |action, object_class, object|
103
+ action.should == :foo
104
+ object_class.should == Hash
105
+ object.should be_nil
106
+ @block_called = true
107
+ end
108
+ @ability.can?(:foo, Hash)
109
+ @block_called.should be_true
110
+ end
111
+
96
112
  it "should automatically alias index and show into read calls" do
97
113
  @ability.can :read, :all
98
114
  @ability.can?(:index, 123).should be_true
@@ -122,9 +138,9 @@ describe CanCan::Ability do
122
138
  end
123
139
 
124
140
  it "should be able to specify multiple classes and match any" do
125
- @ability.can :update, [String, Array]
141
+ @ability.can :update, [String, Range]
126
142
  @ability.can?(:update, "foo").should be_true
127
- @ability.can?(:update, []).should be_true
143
+ @ability.can?(:update, 1..3).should be_true
128
144
  @ability.can?(:update, 123).should be_false
129
145
  end
130
146
 
@@ -149,18 +165,7 @@ describe CanCan::Ability do
149
165
  @ability.can?(:read, 123).should be_false
150
166
  end
151
167
 
152
- it "should support block on 'cannot' method" do
153
- @ability.can :read, :all
154
- @ability.cannot :read, Integer do |int|
155
- int > 5
156
- end
157
- @ability.can?(:read, "foo").should be_true
158
- @ability.can?(:read, 3).should be_true
159
- @ability.can?(:read, 123).should be_false
160
- end
161
-
162
168
  it "should pass to previous can definition, if block returns false or nil" do
163
- #same as previous
164
169
  @ability.can :read, :all
165
170
  @ability.cannot :read, Integer do |int|
166
171
  int > 10 ? nil : ( int > 5 )
@@ -169,7 +174,6 @@ describe CanCan::Ability do
169
174
  @ability.can?(:read, 3).should be_true
170
175
  @ability.can?(:read, 8).should be_false
171
176
  @ability.can?(:read, 123).should be_true
172
-
173
177
  end
174
178
 
175
179
  it "should always return `false` for single cannot definition" do
@@ -214,49 +218,170 @@ describe CanCan::Ability do
214
218
  end
215
219
 
216
220
  it "should use conditions as third parameter and determine abilities from it" do
217
- @ability.can :read, Array, :first => 1, :last => 3
218
- @ability.can?(:read, [1, 2, 3]).should be_true
219
- @ability.can?(:read, [1, 2, 3, 4]).should be_false
220
- @ability.can?(:read, Array).should be_true
221
+ @ability.can :read, Range, :begin => 1, :end => 3
222
+ @ability.can?(:read, 1..3).should be_true
223
+ @ability.can?(:read, 1..4).should be_false
224
+ @ability.can?(:read, Range).should be_true
221
225
  end
222
226
 
223
227
  it "should allow an array of options in conditions hash" do
224
- @ability.can :read, Array, :first => [1, 3, 5]
225
- @ability.can?(:read, [1, 2, 3]).should be_true
226
- @ability.can?(:read, [2, 3]).should be_false
227
- @ability.can?(:read, [3, 4]).should be_true
228
+ @ability.can :read, Range, :begin => [1, 3, 5]
229
+ @ability.can?(:read, 1..3).should be_true
230
+ @ability.can?(:read, 2..4).should be_false
231
+ @ability.can?(:read, 3..5).should be_true
228
232
  end
229
233
 
230
234
  it "should allow a range of options in conditions hash" do
231
- @ability.can :read, Array, :first => 1..3
232
- @ability.can?(:read, [1, 2, 3]).should be_true
233
- @ability.can?(:read, [3, 4]).should be_true
234
- @ability.can?(:read, [4, 5]).should be_false
235
+ @ability.can :read, Range, :begin => 1..3
236
+ @ability.can?(:read, 1..10).should be_true
237
+ @ability.can?(:read, 3..30).should be_true
238
+ @ability.can?(:read, 4..40).should be_false
235
239
  end
236
240
 
237
241
  it "should allow nested hashes in conditions hash" do
238
- @ability.can :read, Array, :first => { :length => 5 }
239
- @ability.can?(:read, ["foo", "bar"]).should be_false
240
- @ability.can?(:read, ["test1", "foo"]).should be_true
242
+ @ability.can :read, Range, :begin => { :to_i => 5 }
243
+ @ability.can?(:read, 5..7).should be_true
244
+ @ability.can?(:read, 6..8).should be_false
241
245
  end
242
246
 
243
- it "should allow nested hash of arrays and match any element" do
244
- @ability.can :read, Array, :first => { :to_i => 3 }
245
- @ability.can?(:read, [[1, 2, 3]]).should be_true
246
- @ability.can?(:read, [[4, 5, 6]]).should be_false
247
+ it "should match any element passed in to nesting if it's an array (for has_many associations)" do
248
+ @ability.can :read, Range, :to_a => { :to_i => 3 }
249
+ @ability.can?(:read, 1..5).should be_true
250
+ @ability.can?(:read, 4..6).should be_false
247
251
  end
248
252
 
249
253
  it "should not stop at cannot definition when comparing class" do
250
- @ability.can :read, Array
251
- @ability.cannot :read, Array, :first => 1
252
- @ability.can?(:read, [2, 3, 5]).should be_true
253
- @ability.can?(:read, [1, 3, 5]).should be_false
254
- @ability.can?(:read, Array).should be_true
254
+ @ability.can :read, Range
255
+ @ability.cannot :read, Range, :begin => 1
256
+ @ability.can?(:read, 2..5).should be_true
257
+ @ability.can?(:read, 1..5).should be_false
258
+ @ability.can?(:read, Range).should be_true
259
+ end
260
+
261
+ it "should stop at cannot definition when no hash is present" do
262
+ @ability.can :read, :all
263
+ @ability.cannot :read, Range
264
+ @ability.can?(:read, 1..5).should be_false
265
+ @ability.can?(:read, Range).should be_false
255
266
  end
256
267
 
257
- it "should has eated cheezburger" do
258
- lambda {
259
- @ability.can? :has, :cheezburger
260
- }.should raise_error(CanCan::Error, "Nom nom nom. I eated it.")
268
+ it "should allow to check ability for Module" do
269
+ module B; end
270
+ class A; include B; end
271
+ @ability.can :read, B
272
+ @ability.can?(:read, A).should be_true
273
+ @ability.can?(:read, A.new).should be_true
274
+ end
275
+
276
+ it "should pass nil to a block for ability on Module when no instance is passed" do
277
+ module B; end
278
+ class A; include B; end
279
+ @ability.can :read, B do |sym|
280
+ sym.should be_nil
281
+ true
282
+ end
283
+ @ability.can?(:read, B).should be_true
284
+ @ability.can?(:read, A).should be_true
285
+ end
286
+
287
+ it "passing a hash of subjects should check permissions through association" do
288
+ @ability.can :read, Range, :string => {:length => 3}
289
+ @ability.can?(:read, "foo" => Range).should be_true
290
+ @ability.can?(:read, "foobar" => Range).should be_false
291
+ @ability.can?(:read, 123 => Range).should be_true
292
+ end
293
+
294
+ it "should have initial attributes based on hash conditions of 'new' action" do
295
+ @ability.can :manage, Range, :foo => "foo", :hash => {:skip => "hashes"}
296
+ @ability.can :create, Range, :bar => 123, :array => %w[skip arrays]
297
+ @ability.can :new, Range, :baz => "baz", :range => 1..3
298
+ @ability.cannot :new, Range, :ignore => "me"
299
+ @ability.attributes_for(:new, Range).should == {:foo => "foo", :bar => 123, :baz => "baz"}
300
+ end
301
+
302
+ it "should raise access denied exception if ability us unauthorized to perform a certain action" do
303
+ begin
304
+ @ability.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!"
305
+ rescue CanCan::AccessDenied => e
306
+ e.message.should == "Access denied!"
307
+ e.action.should == :read
308
+ e.subject.should == :foo
309
+ else
310
+ fail "Expected CanCan::AccessDenied exception to be raised"
311
+ end
312
+ end
313
+
314
+ it "should not raise access denied exception if ability is authorized to perform an action" do
315
+ @ability.can :read, :foo
316
+ lambda { @ability.authorize!(:read, :foo) }.should_not raise_error
317
+ end
318
+
319
+ it "should know when block is used in conditions" do
320
+ @ability.can :read, :foo
321
+ @ability.should_not have_block(:read, :foo)
322
+ @ability.can :read, :foo do |foo|
323
+ false
324
+ end
325
+ @ability.should have_block(:read, :foo)
326
+ end
327
+
328
+ it "should know when raw sql is used in conditions" do
329
+ @ability.can :read, :foo
330
+ @ability.should_not have_raw_sql(:read, :foo)
331
+ @ability.can :read, :foo, 'false'
332
+ @ability.should have_raw_sql(:read, :foo)
333
+ end
334
+
335
+ it "should raise access denied exception with default message if not specified" do
336
+ begin
337
+ @ability.authorize! :read, :foo
338
+ rescue CanCan::AccessDenied => e
339
+ e.default_message = "Access denied!"
340
+ e.message.should == "Access denied!"
341
+ else
342
+ fail "Expected CanCan::AccessDenied exception to be raised"
343
+ end
344
+ end
345
+
346
+ describe "unauthorized message" do
347
+ after(:each) do
348
+ I18n.backend = nil
349
+ end
350
+
351
+ it "should use action/subject in i18n" do
352
+ I18n.backend.store_translations :en, :unauthorized => {:update => {:array => "foo"}}
353
+ @ability.unauthorized_message(:update, Array).should == "foo"
354
+ @ability.unauthorized_message(:update, [1, 2, 3]).should == "foo"
355
+ @ability.unauthorized_message(:update, :missing).should be_nil
356
+ end
357
+
358
+ it "should use symbol as subject directly" do
359
+ I18n.backend.store_translations :en, :unauthorized => {:has => {:cheezburger => "Nom nom nom. I eated it."}}
360
+ @ability.unauthorized_message(:has, :cheezburger).should == "Nom nom nom. I eated it."
361
+ end
362
+
363
+ it "should fall back to 'manage' and 'all'" do
364
+ I18n.backend.store_translations :en, :unauthorized => {
365
+ :manage => {:all => "manage all", :array => "manage array"},
366
+ :update => {:all => "update all", :array => "update array"}
367
+ }
368
+ @ability.unauthorized_message(:update, Array).should == "update array"
369
+ @ability.unauthorized_message(:update, Hash).should == "update all"
370
+ @ability.unauthorized_message(:foo, Array).should == "manage array"
371
+ @ability.unauthorized_message(:foo, Hash).should == "manage all"
372
+ end
373
+
374
+ it "should follow aliased actions" do
375
+ I18n.backend.store_translations :en, :unauthorized => {:modify => {:array => "modify array"}}
376
+ @ability.alias_action :update, :to => :modify
377
+ @ability.unauthorized_message(:update, Array).should == "modify array"
378
+ @ability.unauthorized_message(:edit, Array).should == "modify array"
379
+ end
380
+
381
+ it "should have variables for action and subject" do
382
+ I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
383
+ @ability.unauthorized_message(:update, Array).should == "update array"
384
+ @ability.unauthorized_message(:edit, 1..3).should == "edit range"
385
+ end
261
386
  end
262
387
  end