cancan 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,24 @@
1
+ 1.6.0 (March 11, 2011)
2
+
3
+ * Added MetaWhere support - see issue #194 and #261
4
+
5
+ * Allow Active Record scopes in Ability conditions - see issue #257
6
+
7
+ * Added :if and :unless options to check_authorization - see issue #284
8
+
9
+ * Several Inherited Resources fixes (thanks aq1018, tanordheim and stefanoverna)
10
+
11
+ * Pass action name to accessible_by call when loading a collection (thanks amw)
12
+
13
+ * Added :prepend option to load_and_authorize_resource to load before other filters - see issue #290
14
+
15
+ * Fixed spacing issue in I18n message for multi-word model names - see issue #292
16
+
17
+ * Load resource collection for any action which doesn't have an "id" parameter - see issue #296
18
+
19
+ * Raise an exception when trying to make a Ability condition with both a hash of conditions and a block - see issue #269
20
+
21
+
1
22
  1.5.1 (January 20, 2011)
2
23
 
3
24
  * Fixing deeply nested conditions in Active Record adapter - see issue #246
data/Gemfile CHANGED
@@ -2,9 +2,10 @@ source "http://rubygems.org"
2
2
 
3
3
  case ENV["MODEL_ADAPTER"]
4
4
  when nil, "active_record"
5
- gem "sqlite3-ruby", :require => "sqlite3"
5
+ gem "sqlite3"
6
6
  gem "activerecord", :require => "active_record"
7
7
  gem "with_model"
8
+ gem "meta_where"
8
9
  when "data_mapper"
9
10
  gem "dm-core", "~> 1.0.2"
10
11
  gem "dm-sqlite-adapter", "~> 1.0.2"
@@ -70,17 +70,27 @@ If the user authorization fails, a <tt>CanCan::AccessDenied</tt> exception will
70
70
 
71
71
  class ApplicationController < ActionController::Base
72
72
  rescue_from CanCan::AccessDenied do |exception|
73
- flash[:alert] = exception.message
74
- redirect_to root_url
73
+ redirect_to root_url, :alert => exception.message
75
74
  end
76
75
  end
77
76
 
78
77
  See {Exception Handling}[https://github.com/ryanb/cancan/wiki/exception-handling] for more information.
79
78
 
80
79
 
80
+ === 4. Lock It Down
81
+
82
+ If you want to ensure authorization happens on every action in your application, add +check_authorization+ to your ApplicationController.
83
+
84
+ class ApplicationController < ActionController::Base
85
+ check_authorization
86
+ end
87
+
88
+ This will raise an exception if authorization is not performed in an action. If you want to skip this add +skip_authorization_check+ to a controller subclass. See {Ensure Authorization}[https://github.com/ryanb/cancan/wiki/Ensure-Authorization] for more information.
89
+
90
+
81
91
  == Wiki Docs
82
92
 
83
- * {Upgrading to 1.5}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.5]
93
+ * {Upgrading to 1.6}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.6]
84
94
  * {Defining Abilities}[https://github.com/ryanb/cancan/wiki/Defining-Abilities]
85
95
  * {Checking Abilities}[https://github.com/ryanb/cancan/wiki/Checking-Abilities]
86
96
  * {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions]
@@ -206,7 +206,7 @@ module CanCan
206
206
  def unauthorized_message(action, subject)
207
207
  keys = unauthorized_message_keys(action, subject)
208
208
  variables = {:action => action.to_s}
209
- variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.downcase
209
+ variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
210
210
  message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
211
211
  message.blank? ? nil : message
212
212
  end
@@ -109,6 +109,9 @@ module CanCan
109
109
  #
110
110
  # load_resource :new => :build
111
111
  #
112
+ # [:+prepend+]
113
+ # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
114
+ #
112
115
  def load_resource(*args)
113
116
  cancan_resource_class.add_before_filter(self, :load_resource, *args)
114
117
  end
@@ -162,6 +165,9 @@ module CanCan
162
165
  # [:+through+]
163
166
  # Authorize conditions on this parent resource when instance isn't available.
164
167
  #
168
+ # [:+prepend+]
169
+ # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
170
+ #
165
171
  def authorize_resource(*args)
166
172
  cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
167
173
  end
@@ -220,14 +226,31 @@ module CanCan
220
226
  # check_authorization
221
227
  # end
222
228
  #
223
- # Any arguments are passed to the +after_filter+ it triggers.
224
- #
225
229
  # See skip_authorization_check to bypass this check on specific controller actions.
226
- def check_authorization(*args)
227
- self.after_filter(*args) do |controller|
228
- unless controller.instance_variable_defined?(:@_authorized)
229
- raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
230
- end
230
+ #
231
+ # Options:
232
+ # [:+only+]
233
+ # Only applies to given actions.
234
+ #
235
+ # [:+except+]
236
+ # Does not apply to given actions.
237
+ #
238
+ # [:+if+]
239
+ # Supply the name of a controller method to be called. The authorization check only takes place if this returns true.
240
+ #
241
+ # check_authorization :if => :admin_controller?
242
+ #
243
+ # [:+unless+]
244
+ # Supply the name of a controller method to be called. The authorization check only takes place if this returns false.
245
+ #
246
+ # check_authorization :unless => :devise_controller?
247
+ #
248
+ def check_authorization(options = {})
249
+ self.after_filter(options.slice(:only, :except)) do |controller|
250
+ return if controller.instance_variable_defined?(:@_authorized)
251
+ return if options[:if] && !controller.send(options[:if])
252
+ return if options[:unless] && controller.send(options[:unless])
253
+ raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
231
254
  end
232
255
  end
233
256
 
@@ -294,8 +317,7 @@ module CanCan
294
317
  #
295
318
  # class ApplicationController < ActionController::Base
296
319
  # rescue_from CanCan::AccessDenied do |exception|
297
- # flash[:alert] = exception.message
298
- # redirect_to root_url
320
+ # redirect_to root_url, :alert => exception.message
299
321
  # end
300
322
  # end
301
323
  #
@@ -5,7 +5,8 @@ module CanCan
5
5
  def self.add_before_filter(controller_class, method, *args)
6
6
  options = args.extract_options!
7
7
  resource_name = args.first
8
- controller_class.before_filter(options.slice(:only, :except)) do |controller|
8
+ before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
9
+ controller_class.send(before_filter_method, options.slice(:only, :except)) do |controller|
9
10
  controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except)).send(method)
10
11
  end
11
12
  end
@@ -77,7 +78,7 @@ module CanCan
77
78
  end
78
79
 
79
80
  def load_collection
80
- resource_base.accessible_by(current_ability)
81
+ resource_base.accessible_by(current_ability, authorization_action)
81
82
  end
82
83
 
83
84
  def build_resource
@@ -112,7 +113,7 @@ module CanCan
112
113
  end
113
114
 
114
115
  def member_action?
115
- !collection_actions.include? @params[:action].to_sym
116
+ new_actions.include?(@params[:action].to_sym) || (@params[:id] && !collection_actions.include?(@params[:action].to_sym))
116
117
  end
117
118
 
118
119
  # Returns the class used for this resource. This can be overriden by the :class option.
@@ -3,7 +3,8 @@ module CanCan
3
3
  class InheritedResource < ControllerResource # :nodoc:
4
4
  def load_resource_instance
5
5
  if parent?
6
- @controller.send :parent
6
+ @controller.send :association_chain
7
+ @controller.instance_variable_get("@#{instance_name}")
7
8
  elsif new_actions.include? @params[:action].to_sym
8
9
  @controller.send :build_resource
9
10
  else
@@ -12,7 +13,7 @@ module CanCan
12
13
  end
13
14
 
14
15
  def resource_base
15
- @controller.send :end_of_association_chain
16
+ @controller.send :collection
16
17
  end
17
18
  end
18
19
  end
@@ -26,6 +26,17 @@ module CanCan
26
26
  raise NotImplemented, "This model adapter does not support matching on a conditions hash."
27
27
  end
28
28
 
29
+ # Used to determine if this model adapter will override the matching behavior for a specific condition.
30
+ # If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash
31
+ def self.override_condition_matching?(subject, name, value)
32
+ false
33
+ end
34
+
35
+ # Override if override_condition_matching? returns true
36
+ def self.matches_condition?(subject, name, value)
37
+ raise NotImplemented, "This model adapter does not support matching on a specific condition."
38
+ end
39
+
29
40
  def initialize(model_class, rules)
30
41
  @model_class = model_class
31
42
  @rules = rules
@@ -5,6 +5,37 @@ module CanCan
5
5
  model_class <= ActiveRecord::Base
6
6
  end
7
7
 
8
+ def self.override_condition_matching?(subject, name, value)
9
+ name.kind_of?(MetaWhere::Column) if defined? MetaWhere
10
+ end
11
+
12
+ def self.matches_condition?(subject, name, value)
13
+ subject_value = subject.send(name.column)
14
+ if name.method.to_s.ends_with? "_any"
15
+ value.any? { |v| meta_where_match? subject_value, name.method.to_s.sub("_any", ""), v }
16
+ elsif name.method.to_s.ends_with? "_all"
17
+ value.all? { |v| meta_where_match? subject_value, name.method.to_s.sub("_all", ""), v }
18
+ else
19
+ meta_where_match? subject_value, name.method, value
20
+ end
21
+ end
22
+
23
+ def self.meta_where_match?(subject_value, method, value)
24
+ case method.to_sym
25
+ when :eq then subject_value == value
26
+ when :not_eq then subject_value != value
27
+ when :in then value.include?(subject_value)
28
+ when :not_in then !value.include?(subject_value)
29
+ when :lt then subject_value < value
30
+ when :lteq then subject_value <= value
31
+ when :gt then subject_value > value
32
+ when :gteq then subject_value >= value
33
+ when :matches then subject_value =~ Regexp.new("^" + Regexp.escape(value).gsub("%", ".*") + "$", true)
34
+ when :does_not_match then !meta_where_match?(subject_value, :matches, value)
35
+ else raise NotImplemented, "The #{method} MetaWhere condition is not supported."
36
+ end
37
+ end
38
+
8
39
  # Returns conditions intended to be used inside a database query. Normally you will not call this
9
40
  # method directly, but instead go through ModelAdditions#accessible_by.
10
41
  #
@@ -36,7 +67,7 @@ module CanCan
36
67
  conditions.inject({}) do |result_hash, (name, value)|
37
68
  if value.kind_of? Hash
38
69
  association_class = model_class.reflect_on_association(name).class_name.constantize
39
- name = model_class.reflect_on_association(name).table_name
70
+ name = model_class.reflect_on_association(name).table_name.to_sym
40
71
  value = tableized_conditions(value, association_class)
41
72
  end
42
73
  result_hash[name] = value
@@ -55,7 +86,9 @@ module CanCan
55
86
  end
56
87
 
57
88
  def database_records
58
- if @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
89
+ if override_scope
90
+ override_scope
91
+ elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
59
92
  @model_class.where(conditions).joins(joins)
60
93
  else
61
94
  @model_class.scoped(:conditions => conditions, :joins => joins)
@@ -64,6 +97,18 @@ module CanCan
64
97
 
65
98
  private
66
99
 
100
+ def override_scope
101
+ conditions = @rules.map(&:conditions).compact
102
+ if conditions.any? { |c| c.kind_of?(ActiveRecord::Relation) }
103
+ if conditions.size == 1
104
+ conditions.first
105
+ else
106
+ rule = @rules.detect { |rule| rule.conditions.kind_of?(ActiveRecord::Relation) }
107
+ raise Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for #{rule.actions.first} #{rule.subjects.first} ability."
108
+ end
109
+ end
110
+ end
111
+
67
112
  def merge_conditions(sql, conditions_hash, behavior)
68
113
  if conditions_hash.blank?
69
114
  behavior ? true_sql : false_sql
@@ -3,7 +3,7 @@ module CanCan
3
3
  # it holds the information about a "can" call made on Ability and provides
4
4
  # helpful methods to determine permission checking and conditions hash generation.
5
5
  class Rule # :nodoc:
6
- attr_reader :base_behavior, :actions, :conditions
6
+ attr_reader :base_behavior, :subjects, :actions, :conditions
7
7
  attr_writer :expanded_actions
8
8
 
9
9
  # The first argument when initializing is the base_behavior which is a true/false
@@ -11,6 +11,7 @@ module CanCan
11
11
  # and subject respectively (such as :read, @project). The third argument is a hash
12
12
  # of conditions and the last one is the block passed to the "can" call.
13
13
  def initialize(base_behavior, action, subject, conditions, block)
14
+ raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil?
14
15
  @match_all = action.nil? && subject.nil?
15
16
  @base_behavior = base_behavior
16
17
  @actions = [action].flatten
@@ -21,7 +22,7 @@ module CanCan
21
22
 
22
23
  # Matches both the subject and action, not necessarily the conditions
23
24
  def relevant?(action, subject)
24
- subject = subject.values.first if subject.kind_of? Hash
25
+ subject = subject.values.first if subject.class == Hash
25
26
  @match_all || (matches_action?(action) && matches_subject?(subject))
26
27
  end
27
28
 
@@ -31,7 +32,7 @@ module CanCan
31
32
  call_block_with_all(action, subject, extra_args)
32
33
  elsif @block && !subject_class?(subject)
33
34
  @block.call(subject, *extra_args)
34
- elsif @conditions.kind_of?(Hash) && subject.kind_of?(Hash)
35
+ elsif @conditions.kind_of?(Hash) && subject.class == Hash
35
36
  nested_subject_matches_conditions?(subject)
36
37
  elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
37
38
  matches_conditions_hash?(subject)
@@ -100,17 +101,21 @@ module CanCan
100
101
  model_adapter(subject).matches_conditions_hash? subject, conditions
101
102
  else
102
103
  conditions.all? do |name, value|
103
- attribute = subject.send(name)
104
- if value.kind_of?(Hash)
105
- if attribute.kind_of? Array
106
- attribute.any? { |element| matches_conditions_hash? element, value }
104
+ if model_adapter(subject).override_condition_matching? subject, name, value
105
+ model_adapter(subject).matches_condition? subject, name, value
106
+ else
107
+ attribute = subject.send(name)
108
+ if value.kind_of?(Hash)
109
+ if attribute.kind_of? Array
110
+ attribute.any? { |element| matches_conditions_hash? element, value }
111
+ else
112
+ matches_conditions_hash? attribute, value
113
+ end
114
+ elsif value.kind_of?(Array) || value.kind_of?(Range)
115
+ value.include? attribute
107
116
  else
108
- matches_conditions_hash? attribute, value
117
+ attribute == value
109
118
  end
110
- elsif value.kind_of?(Array) || value.kind_of?(Range)
111
- value.include? attribute
112
- else
113
- attribute == value
114
119
  end
115
120
  end
116
121
  end
@@ -290,6 +290,12 @@ describe CanCan::Ability do
290
290
  @ability.can?(:read, "foobar" => Range).should be_false
291
291
  @ability.can?(:read, 123 => Range).should be_true
292
292
  end
293
+
294
+ it "should allow to check ability on Hash-like object" do
295
+ class Container < Hash; end
296
+ @ability.can :read, Container
297
+ @ability.can?(:read, Container.new).should be_true
298
+ end
293
299
 
294
300
  it "should have initial attributes based on hash conditions of 'new' action" do
295
301
  @ability.can :manage, Range, :foo => "foo", :hash => {:skip => "hashes"}
@@ -351,6 +357,14 @@ describe CanCan::Ability do
351
357
  @ability.model_adapter(model_class, :read).should == :adapter_instance
352
358
  end
353
359
 
360
+ it "should raise an error when attempting to use a block with a hash condition since it's not likely what they want" do
361
+ lambda {
362
+ @ability.can :read, Array, :published => true do
363
+ false
364
+ end
365
+ }.should raise_error(CanCan::Error, "You are not able to supply a block with a hash of conditions in read Array ability. Use either one.")
366
+ end
367
+
354
368
  describe "unauthorized message" do
355
369
  after(:each) do
356
370
  I18n.backend = nil
@@ -389,6 +403,7 @@ describe CanCan::Ability do
389
403
  it "should have variables for action and subject" do
390
404
  I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
391
405
  @ability.unauthorized_message(:update, Array).should == "update array"
406
+ @ability.unauthorized_message(:update, ArgumentError).should == "update argument error"
392
407
  @ability.unauthorized_message(:edit, 1..3).should == "edit range"
393
408
  end
394
409
  end
@@ -42,6 +42,11 @@ describe CanCan::ControllerAdditions do
42
42
  @controller_class.load_and_authorize_resource :project, :foo => :bar
43
43
  end
44
44
 
45
+ it "load_and_authorize_resource with :prepend should prepend the before filter" do
46
+ mock(@controller_class).prepend_before_filter({})
47
+ @controller_class.load_and_authorize_resource :foo => :bar, :prepend => true
48
+ end
49
+
45
50
  it "authorize_resource should setup a before filter which passes call to ControllerResource" do
46
51
  stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.authorize_resource
47
52
  mock(@controller_class).before_filter(:except => :show) { |options, block| block.call(@controller) }
@@ -61,17 +66,33 @@ describe CanCan::ControllerAdditions do
61
66
  end
62
67
 
63
68
  it "check_authorization should trigger AuthorizationNotPerformed in after filter" do
64
- mock(@controller_class).after_filter(:some_options) { |options, block| block.call(@controller) }
69
+ mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
65
70
  lambda {
66
- @controller_class.check_authorization(:some_options)
71
+ @controller_class.check_authorization(:only => [:test])
67
72
  }.should raise_error(CanCan::AuthorizationNotPerformed)
68
73
  end
69
74
 
75
+ it "check_authorization should not trigger AuthorizationNotPerformed when :if is false" do
76
+ stub(@controller).check_auth? { false }
77
+ mock(@controller_class).after_filter({}) { |options, block| block.call(@controller) }
78
+ lambda {
79
+ @controller_class.check_authorization(:if => :check_auth?)
80
+ }.should_not raise_error(CanCan::AuthorizationNotPerformed)
81
+ end
82
+
83
+ it "check_authorization should not trigger AuthorizationNotPerformed when :unless is true" do
84
+ stub(@controller).engine_controller? { true }
85
+ mock(@controller_class).after_filter({}) { |options, block| block.call(@controller) }
86
+ lambda {
87
+ @controller_class.check_authorization(:unless => :engine_controller?)
88
+ }.should_not raise_error(CanCan::AuthorizationNotPerformed)
89
+ end
90
+
70
91
  it "check_authorization should not raise error when @_authorized is set" do
71
92
  @controller.instance_variable_set(:@_authorized, true)
72
- mock(@controller_class).after_filter(:some_options) { |options, block| block.call(@controller) }
93
+ mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
73
94
  lambda {
74
- @controller_class.check_authorization(:some_options)
95
+ @controller_class.check_authorization(:only => [:test])
75
96
  }.should_not raise_error(CanCan::AuthorizationNotPerformed)
76
97
  end
77
98
 
@@ -67,7 +67,7 @@ describe CanCan::ControllerResource do
67
67
  end
68
68
 
69
69
  it "should build a collection when on index action when class responds to accessible_by" do
70
- stub(Project).accessible_by(@ability) { :found_projects }
70
+ stub(Project).accessible_by(@ability, :index) { :found_projects }
71
71
  @params[:action] = "index"
72
72
  resource = CanCan::ControllerResource.new(@controller, :project)
73
73
  resource.load_resource
@@ -110,7 +110,7 @@ describe CanCan::ControllerResource do
110
110
  end
111
111
 
112
112
  it "should perform authorization using controller action and loaded model" do
113
- @params[:action] = "show"
113
+ @params.merge!(:action => "show", :id => 123)
114
114
  @controller.instance_variable_set(:@project, :some_project)
115
115
  stub(@controller).authorize!(:show, :some_project) { raise CanCan::AccessDenied }
116
116
  resource = CanCan::ControllerResource.new(@controller)
@@ -118,27 +118,36 @@ describe CanCan::ControllerResource do
118
118
  end
119
119
 
120
120
  it "should perform authorization using controller action and non loaded model" do
121
- @params[:action] = "show"
121
+ @params.merge!(:action => "show", :id => 123)
122
122
  stub(@controller).authorize!(:show, Project) { raise CanCan::AccessDenied }
123
123
  resource = CanCan::ControllerResource.new(@controller)
124
124
  lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
125
125
  end
126
126
 
127
127
  it "should call load_resource and authorize_resource for load_and_authorize_resource" do
128
- @params[:action] = "show"
128
+ @params.merge!(:action => "show", :id => 123)
129
129
  resource = CanCan::ControllerResource.new(@controller)
130
130
  mock(resource).load_resource
131
131
  mock(resource).authorize_resource
132
132
  resource.load_and_authorize_resource
133
133
  end
134
134
 
135
- it "should not build a resource when on custom collection action" do
136
- @params[:action] = "sort"
135
+ it "should not build a single resource when on custom collection action even with id" do
136
+ @params.merge!(:action => "sort", :id => 123)
137
137
  resource = CanCan::ControllerResource.new(@controller, :collection => [:sort, :list])
138
138
  resource.load_resource
139
139
  @controller.instance_variable_get(:@project).should be_nil
140
140
  end
141
141
 
142
+ it "should load a collection resource when on custom action with no id param" do
143
+ stub(Project).accessible_by(@ability, :sort) { :found_projects }
144
+ @params[:action] = "sort"
145
+ resource = CanCan::ControllerResource.new(@controller)
146
+ resource.load_resource
147
+ @controller.instance_variable_get(:@project).should be_nil
148
+ @controller.instance_variable_get(:@projects).should == :found_projects
149
+ end
150
+
142
151
  it "should build a resource when on custom new action even when params[:id] exists" do
143
152
  @params.merge!(:action => "build", :id => 123)
144
153
  stub(Project).new { :some_project }
@@ -250,7 +259,7 @@ describe CanCan::ControllerResource do
250
259
  end
251
260
 
252
261
  it "should find record through has_one association with :singleton option" do
253
- @params.merge!(:action => "show")
262
+ @params.merge!(:action => "show", :id => 123)
254
263
  category = Object.new
255
264
  @controller.instance_variable_set(:@category, category)
256
265
  stub(category).project { :some_project }
@@ -12,7 +12,7 @@ describe CanCan::InheritedResource do
12
12
  end
13
13
 
14
14
  it "show should load resource through @controller.resource" do
15
- @params[:action] = "show"
15
+ @params.merge!(:action => "show", :id => 123)
16
16
  stub(@controller).resource { :project_resource }
17
17
  CanCan::InheritedResource.new(@controller).load_resource
18
18
  @controller.instance_variable_get(:@project).should == :project_resource
@@ -25,17 +25,17 @@ describe CanCan::InheritedResource do
25
25
  @controller.instance_variable_get(:@project).should == :project_resource
26
26
  end
27
27
 
28
- it "index should load through @controller.parent when parent" do
28
+ it "index should load through @controller.association_chain when parent" do
29
29
  @params[:action] = "index"
30
- stub(@controller).parent { :project_resource }
30
+ stub(@controller).association_chain { @controller.instance_variable_set(:@project, :project_resource) }
31
31
  CanCan::InheritedResource.new(@controller, :parent => true).load_resource
32
32
  @controller.instance_variable_get(:@project).should == :project_resource
33
33
  end
34
34
 
35
- it "index should load through @controller.end_of_association_chain" do
35
+ it "index should load through @controller.collection" do
36
36
  @params[:action] = "index"
37
- stub(Project).accessible_by(@ability) { :projects }
38
- stub(@controller).end_of_association_chain { Project }
37
+ stub(Project).accessible_by(@ability, :index) { :projects }
38
+ stub(@controller).collection { Project }
39
39
  CanCan::InheritedResource.new(@controller).load_resource
40
40
  @controller.instance_variable_get(:@projects).should == :projects
41
41
  end
@@ -19,8 +19,10 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
19
19
 
20
20
  with_model :article do
21
21
  table do |t|
22
+ t.string "name"
22
23
  t.boolean "published"
23
24
  t.boolean "secret"
25
+ t.integer "priority"
24
26
  t.integer "category_id"
25
27
  end
26
28
  model do
@@ -110,10 +112,25 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
110
112
  @ability.can :read, Article, :published => true
111
113
  @ability.can :read, Article, ["secret=?", true]
112
114
  article1 = Article.create!(:published => true, :secret => false)
115
+ article2 = Article.create!(:published => true, :secret => true)
116
+ article3 = Article.create!(:published => false, :secret => true)
113
117
  article4 = Article.create!(:published => false, :secret => false)
118
+ Article.accessible_by(@ability).should == [article1, article2, article3]
119
+ end
120
+
121
+ it "should allow a scope for conditions" do
122
+ @ability.can :read, Article, Article.where(:secret => true)
123
+ article1 = Article.create!(:secret => true)
124
+ article2 = Article.create!(:secret => false)
114
125
  Article.accessible_by(@ability).should == [article1]
115
126
  end
116
127
 
128
+ it "should raise an exception when trying to merge scope with other conditions" do
129
+ @ability.can :read, Article, :published => true
130
+ @ability.can :read, Article, Article.where(:secret => true)
131
+ lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for read Article ability.")
132
+ end
133
+
117
134
  it "should not allow to fetch records when ability with just block present" do
118
135
  @ability.can :read, Article do
119
136
  false
@@ -199,5 +216,48 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
199
216
  @ability.can :read, Article, :project => { :admin => true }
200
217
  @ability.model_adapter(Article, :read).joins.should == [:project]
201
218
  end
219
+
220
+ it "should restrict articles given a MetaWhere condition" do
221
+ @ability.can :read, Article, :priority.lt => 2
222
+ article1 = Article.create!(:priority => 1)
223
+ article2 = Article.create!(:priority => 3)
224
+ Article.accessible_by(@ability).should == [article1]
225
+ @ability.should be_able_to(:read, article1)
226
+ @ability.should_not be_able_to(:read, article2)
227
+ end
228
+
229
+ it "should match any MetaWhere condition" do
230
+ adapter = CanCan::ModelAdapters::ActiveRecordAdapter
231
+ article1 = Article.new(:priority => 1, :name => "Hello World")
232
+ adapter.matches_condition?(article1, :priority.eq, 1).should be_true
233
+ adapter.matches_condition?(article1, :priority.eq, 2).should be_false
234
+ adapter.matches_condition?(article1, :priority.eq_any, [1, 2]).should be_true
235
+ adapter.matches_condition?(article1, :priority.eq_any, [2, 3]).should be_false
236
+ adapter.matches_condition?(article1, :priority.eq_all, [1, 1]).should be_true
237
+ adapter.matches_condition?(article1, :priority.eq_all, [1, 2]).should be_false
238
+ adapter.matches_condition?(article1, :priority.ne, 2).should be_true
239
+ adapter.matches_condition?(article1, :priority.ne, 1).should be_false
240
+ adapter.matches_condition?(article1, :priority.in, [1, 2]).should be_true
241
+ adapter.matches_condition?(article1, :priority.in, [2, 3]).should be_false
242
+ adapter.matches_condition?(article1, :priority.nin, [2, 3]).should be_true
243
+ adapter.matches_condition?(article1, :priority.nin, [1, 2]).should be_false
244
+ adapter.matches_condition?(article1, :priority.lt, 2).should be_true
245
+ adapter.matches_condition?(article1, :priority.lt, 1).should be_false
246
+ adapter.matches_condition?(article1, :priority.lteq, 1).should be_true
247
+ adapter.matches_condition?(article1, :priority.lteq, 0).should be_false
248
+ adapter.matches_condition?(article1, :priority.gt, 0).should be_true
249
+ adapter.matches_condition?(article1, :priority.gt, 1).should be_false
250
+ adapter.matches_condition?(article1, :priority.gteq, 1).should be_true
251
+ adapter.matches_condition?(article1, :priority.gteq, 2).should be_false
252
+ adapter.matches_condition?(article1, :name.like, "%ello worl%").should be_true
253
+ adapter.matches_condition?(article1, :name.like, "hello world").should be_true
254
+ adapter.matches_condition?(article1, :name.like, "hello%").should be_true
255
+ adapter.matches_condition?(article1, :name.like, "h%d").should be_true
256
+ adapter.matches_condition?(article1, :name.like, "%helo%").should be_false
257
+ adapter.matches_condition?(article1, :name.like, "hello").should be_false
258
+ adapter.matches_condition?(article1, :name.like, "hello.world").should be_false
259
+ adapter.matches_condition?(article1, :name.nlike, "%helo%").should be_true
260
+ adapter.matches_condition?(article1, :name.nlike, "%ello worl%").should be_false
261
+ end
202
262
  end
203
263
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cancan
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1
5
- prerelease: false
4
+ hash: 15
5
+ prerelease:
6
6
  segments:
7
7
  - 1
8
- - 5
9
- - 1
10
- version: 1.5.1
8
+ - 6
9
+ - 0
10
+ version: 1.6.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ryan Bates
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-20 00:00:00 -08:00
18
+ date: 2011-03-10 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -161,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
161
  requirements: []
162
162
 
163
163
  rubyforge_project: cancan
164
- rubygems_version: 1.3.7
164
+ rubygems_version: 1.4.2
165
165
  signing_key:
166
166
  specification_version: 3
167
167
  summary: Simple authorization solution for Rails.