cancan 1.5.1 → 1.6.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,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.