cancan 1.6.4 → 1.6.5

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,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.