cancan 1.6.4 → 1.6.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,21 @@
1
+ 1.6.5 (May 18, 2011)
2
+
3
+ * pass action and subject through AccessDenied exception when :through isn't found - issue #366
4
+
5
+ * many Mongoid adapter improvements (thanks rahearn, cardagin) - issues #363, #352, #343
6
+
7
+ * allow :through option to work with private controller methods - issue #360
8
+
9
+ * ensure Mongoid::Document is defined before loading Mongoid adapter - issue #359
10
+
11
+ * many DataMapper adapter improvements (thanks emmanuel) - issue #355
12
+
13
+ * handle checking nil attributes through associations (thanks thatothermitch) - issue #330
14
+
15
+ * improve scope merging - issue #328
16
+
17
+
18
+
1
19
  1.6.4 (March 29, 2011)
2
20
 
3
21
  * Fixed mongoid 'or' error - see issue #322
@@ -10,4 +10,4 @@ require 'cancan/model_adapters/abstract_adapter'
10
10
  require 'cancan/model_adapters/default_adapter'
11
11
  require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
12
12
  require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
13
- require 'cancan/model_adapters/mongoid_adapter' if defined? Mongoid
13
+ require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document)
@@ -286,7 +286,7 @@ module CanCan
286
286
 
287
287
  def self.included(base)
288
288
  base.extend ClassMethods
289
- base.helper_method :can?, :cannot?
289
+ base.helper_method :can?, :cannot?, :current_ability
290
290
  end
291
291
 
292
292
  # Raises a CanCan::AccessDenied exception if the current_ability cannot
@@ -159,7 +159,7 @@ module CanCan
159
159
  elsif @options[:shallow]
160
160
  resource_class
161
161
  else
162
- raise AccessDenied # maybe this should be a record not found error instead?
162
+ raise AccessDenied.new(nil, authorization_action, resource_class) # maybe this should be a record not found error instead?
163
163
  end
164
164
  else
165
165
  resource_class
@@ -178,7 +178,7 @@ module CanCan
178
178
  def fetch_parent(name)
179
179
  if @controller.instance_variable_defined? "@#{name}"
180
180
  @controller.instance_variable_get("@#{name}")
181
- elsif @controller.respond_to? name
181
+ elsif @controller.respond_to?(name, true)
182
182
  @controller.send(name)
183
183
  end
184
184
  end
@@ -87,7 +87,7 @@ module CanCan
87
87
 
88
88
  def database_records
89
89
  if override_scope
90
- override_scope
90
+ @model_class.scoped.merge(override_scope)
91
91
  elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
92
92
  @model_class.where(conditions).joins(joins)
93
93
  else
@@ -10,23 +10,22 @@ module CanCan
10
10
  end
11
11
 
12
12
  def self.matches_conditions_hash?(subject, conditions)
13
- subject.class.all(:conditions => conditions).include?(subject) # TODO don't use a database query here for performance and other instances
13
+ collection = DataMapper::Collection.new(subject.query, [ subject ])
14
+ !!collection.first(conditions)
14
15
  end
15
16
 
16
17
  def database_records
17
- scope = @model_class.all(:conditions => ["0=1"])
18
- conditions.each do |condition|
19
- scope += @model_class.all(:conditions => condition)
20
- end
18
+ scope = @model_class.all(:conditions => ["0 = 1"])
19
+ cans, cannots = @rules.partition { |r| r.base_behavior }
20
+ return scope if cans.empty?
21
+ # apply unions first, then differences. this mean cannot overrides can
22
+ cans.each { |r| scope += @model_class.all(:conditions => r.conditions) }
23
+ cannots.each { |r| scope -= @model_class.all(:conditions => r.conditions) }
21
24
  scope
22
25
  end
23
-
24
- def conditions
25
- @rules.map(&:conditions)
26
- end
27
- end
28
- end
29
- end
26
+ end # class DataMapper
27
+ end # module ModelAdapters
28
+ end # module CanCan
30
29
 
31
30
  DataMapper::Model.class_eval do
32
31
  include CanCan::ModelAdditions::ClassMethods
@@ -6,7 +6,14 @@ module CanCan
6
6
  end
7
7
 
8
8
  def self.override_conditions_hash_matching?(subject, conditions)
9
- conditions.any? { |k,v| !k.kind_of?(Symbol) }
9
+ conditions.any? do |k,v|
10
+ key_is_not_symbol = lambda { !k.kind_of?(Symbol) }
11
+ subject_value_is_array = lambda do
12
+ subject.respond_to?(k) && subject.send(k).is_a?(Array)
13
+ end
14
+
15
+ key_is_not_symbol.call || subject_value_is_array.call
16
+ end
10
17
  end
11
18
 
12
19
  def self.matches_conditions_hash?(subject, conditions)
@@ -16,16 +23,22 @@ module CanCan
16
23
  end
17
24
 
18
25
  def database_records
19
- if @rules.size == 0
26
+ if @rules.size == 0
20
27
  @model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid
28
+ elsif @rules.size == 1 && @rules[0].conditions.is_a?(Mongoid::Criteria)
29
+ @rules[0].conditions
21
30
  else
22
- @rules.inject(@model_class.all) do |records, rule|
23
- if rule.conditions.empty?
24
- records
25
- elsif rule.base_behavior
26
- records.or(rule.conditions)
31
+ # we only need to process can rules if
32
+ # there are no rules with empty conditions
33
+ rules = @rules.reject { |rule| rule.conditions.empty? }
34
+ process_can_rules = @rules.count == rules.count
35
+ rules.inject(@model_class.all) do |records, rule|
36
+ if process_can_rules && rule.base_behavior
37
+ records.or rule.conditions
38
+ elsif !rule.base_behavior
39
+ records.excludes rule.conditions
27
40
  else
28
- records.excludes(rule.conditions)
41
+ records
29
42
  end
30
43
  end
31
44
  end
@@ -37,4 +50,4 @@ end
37
50
  # simplest way to add `accessible_by` to all Mongoid Documents
38
51
  module Mongoid::Document::ClassMethods
39
52
  include CanCan::ModelAdditions::ClassMethods
40
- end
53
+ end
@@ -109,7 +109,7 @@ module CanCan
109
109
  if attribute.kind_of? Array
110
110
  attribute.any? { |element| matches_conditions_hash? element, value }
111
111
  else
112
- matches_conditions_hash? attribute, value
112
+ !attribute.nil? && matches_conditions_hash?(attribute, value)
113
113
  end
114
114
  elsif value.kind_of?(Array) || value.kind_of?(Range)
115
115
  value.include? attribute
@@ -249,6 +249,13 @@ describe CanCan::Ability do
249
249
  @ability.can?(:read, 1..5).should be_true
250
250
  @ability.can?(:read, 4..6).should be_false
251
251
  end
252
+
253
+ it "should not match subjects return nil for methods that must match nested a nested conditions hash" do
254
+ mock(object_with_foo = Object.new).foo { :bar }
255
+ @ability.can :read, Array, :first => { :foo => :bar }
256
+ @ability.can?(:read, [object_with_foo]).should be_true
257
+ @ability.can?(:read, []).should be_false
258
+ end
252
259
 
253
260
  it "should not stop at cannot definition when comparing class" do
254
261
  @ability.can :read, Range
@@ -6,7 +6,7 @@ describe CanCan::ControllerAdditions do
6
6
  @controller = @controller_class.new
7
7
  stub(@controller).params { {} }
8
8
  stub(@controller).current_user { :current_user }
9
- mock(@controller_class).helper_method(:can?, :cannot?)
9
+ mock(@controller_class).helper_method(:can?, :cannot?, :current_ability)
10
10
  @controller_class.send(:include, CanCan::ControllerAdditions)
11
11
  end
12
12
 
@@ -235,7 +235,10 @@ describe CanCan::ControllerResource do
235
235
  resource = CanCan::ControllerResource.new(@controller, :through => :category)
236
236
  lambda {
237
237
  resource.load_resource
238
- }.should raise_error(CanCan::AccessDenied)
238
+ }.should raise_error(CanCan::AccessDenied) { |exception|
239
+ exception.action.should == :show
240
+ exception.subject.should == Project
241
+ }
239
242
  @controller.instance_variable_get(:@project).should be_nil
240
243
  end
241
244
 
@@ -125,6 +125,15 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
125
125
  Article.accessible_by(@ability).should == [article1]
126
126
  end
127
127
 
128
+ it "should fetch only associated records when using with a scope for conditions" do
129
+ @ability.can :read, Article, Article.where(:secret => true)
130
+ category1 = Category.create!(:visible => false)
131
+ category2 = Category.create!(:visible => true)
132
+ article1 = Article.create!(:secret => true, :category => category1)
133
+ article2 = Article.create!(:secret => true, :category => category2)
134
+ category1.articles.accessible_by(@ability).should == [article1]
135
+ end
136
+
128
137
  it "should raise an exception when trying to merge scope with other conditions" do
129
138
  @ability.can :read, Article, :published => true
130
139
  @ability.can :read, Article, Article.where(:secret => true)
@@ -65,7 +65,6 @@ if ENV["MODEL_ADAPTER"] == "data_mapper"
65
65
  end
66
66
 
67
67
  it "should fetch only the articles that are published and not secret" do
68
- pending "the `cannot` may require some custom SQL, maybe abstract out from Active Record adapter"
69
68
  @ability.can :read, Article, :published => true
70
69
  @ability.cannot :read, Article, :secret => true
71
70
  article1 = Article.create(:published => true, :secret => false)
@@ -42,6 +42,15 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
42
42
  @ability.should be_able_to(:read, model)
43
43
  end
44
44
 
45
+ it "should be able to read hashes when field is array" do
46
+ one_to_three = MongoidProject.create(:numbers => ['one', 'two', 'three'])
47
+ two_to_five = MongoidProject.create(:numbers => ['two', 'three', 'four', 'five'])
48
+
49
+ @ability.can :foo, MongoidProject, :numbers => 'one'
50
+ @ability.should be_able_to(:foo, one_to_three)
51
+ @ability.should_not be_able_to(:foo, two_to_five)
52
+ end
53
+
45
54
  it "should return [] when no ability is defined so no records are found" do
46
55
  MongoidProject.create(:title => 'Sir')
47
56
  MongoidProject.create(:title => 'Lord')
@@ -59,6 +68,15 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
59
68
  MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
60
69
  end
61
70
 
71
+ it "should be able to mix empty conditions and hashes" do
72
+ @ability.can :read, MongoidProject
73
+ @ability.can :read, MongoidProject, :title => 'Sir'
74
+ sir = MongoidProject.create(:title => 'Sir')
75
+ lord = MongoidProject.create(:title => 'Lord')
76
+
77
+ MongoidProject.accessible_by(@ability, :read).count.should == 2
78
+ end
79
+
62
80
  it "should return everything when the defined ability is manage all" do
63
81
  @ability.can :manage, :all
64
82
  sir = MongoidProject.create(:title => 'Sir')
@@ -68,6 +86,14 @@ if ENV["MODEL_ADAPTER"] == "mongoid"
68
86
  MongoidProject.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
69
87
  end
70
88
 
89
+ it "should allow a scope for conditions" do
90
+ @ability.can :read, MongoidProject, MongoidProject.where(:title => 'Sir')
91
+ sir = MongoidProject.create(:title => 'Sir')
92
+ lord = MongoidProject.create(:title => 'Lord')
93
+ dude = MongoidProject.create(:title => 'Dude')
94
+
95
+ MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
96
+ end
71
97
 
72
98
  describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do
73
99
  it "should handle :field.in" do
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: 7
4
+ hash: 5
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 6
9
- - 4
10
- version: 1.6.4
9
+ - 5
10
+ version: 1.6.5
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-03-29 00:00:00 -07:00
18
+ date: 2011-05-18 00:00:00 -04: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.4.2
164
+ rubygems_version: 1.5.3
165
165
  signing_key:
166
166
  specification_version: 3
167
167
  summary: Simple authorization solution for Rails.