cancan 1.4.1 → 1.5.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/CHANGELOG.rdoc +21 -0
  2. data/Gemfile +17 -0
  3. data/LICENSE +1 -1
  4. data/README.rdoc +16 -77
  5. data/Rakefile +8 -0
  6. data/lib/cancan.rb +8 -3
  7. data/lib/cancan/ability.rb +24 -26
  8. data/lib/cancan/controller_additions.rb +50 -0
  9. data/lib/cancan/controller_resource.rb +33 -15
  10. data/lib/cancan/exceptions.rb +3 -0
  11. data/lib/cancan/model_adapters/abstract_adapter.rb +40 -0
  12. data/lib/cancan/model_adapters/active_record_adapter.rb +119 -0
  13. data/lib/cancan/model_adapters/data_mapper_adapter.rb +33 -0
  14. data/lib/cancan/model_adapters/default_adapter.rb +7 -0
  15. data/lib/cancan/model_adapters/mongoid_adapter.rb +41 -0
  16. data/lib/cancan/{active_record_additions.rb → model_additions.rb} +5 -16
  17. data/lib/cancan/{can_definition.rb → rule.rb} +29 -25
  18. data/lib/generators/cancan/ability/USAGE +4 -0
  19. data/lib/generators/cancan/ability/ability_generator.rb +11 -0
  20. data/lib/generators/cancan/ability/templates/ability.rb +28 -0
  21. data/spec/README.rdoc +28 -0
  22. data/spec/cancan/ability_spec.rb +11 -3
  23. data/spec/cancan/controller_additions_spec.rb +30 -0
  24. data/spec/cancan/controller_resource_spec.rb +68 -2
  25. data/spec/cancan/inherited_resource_spec.rb +3 -1
  26. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +185 -0
  27. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +115 -0
  28. data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
  29. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +168 -0
  30. data/spec/cancan/rule_spec.rb +39 -0
  31. data/spec/spec_helper.rb +2 -24
  32. metadata +26 -17
  33. data/lib/cancan/query.rb +0 -97
  34. data/spec/cancan/active_record_additions_spec.rb +0 -75
  35. data/spec/cancan/can_definition_spec.rb +0 -57
  36. data/spec/cancan/query_spec.rb +0 -107
@@ -0,0 +1,7 @@
1
+ require "spec_helper"
2
+
3
+ describe CanCan::ModelAdapters::DefaultAdapter do
4
+ it "should be default for generic classes" do
5
+ CanCan::ModelAdapters::AbstractAdapter.adapter_class(Object).should == CanCan::ModelAdapters::DefaultAdapter
6
+ end
7
+ end
@@ -0,0 +1,168 @@
1
+ if ENV["MODEL_ADAPTER"] == "mongoid"
2
+ require "spec_helper"
3
+
4
+ class MongoidCategory
5
+ include Mongoid::Document
6
+
7
+ references_many :mongoid_projects
8
+ end
9
+
10
+ class MongoidProject
11
+ include Mongoid::Document
12
+
13
+ referenced_in :mongoid_category
14
+ end
15
+
16
+ Mongoid.configure do |config|
17
+ config.master = Mongo::Connection.new('127.0.0.1', 27017).db("cancan_mongoid_spec")
18
+ end
19
+
20
+ describe CanCan::ModelAdapters::MongoidAdapter do
21
+ context "Mongoid defined" do
22
+ before(:each) do
23
+ @ability = Object.new
24
+ @ability.extend(CanCan::Ability)
25
+ end
26
+
27
+ after(:each) do
28
+ Mongoid.master.collections.select do |collection|
29
+ collection.name !~ /system/
30
+ end.each(&:drop)
31
+ end
32
+
33
+ it "should be for only Mongoid classes" do
34
+ CanCan::ModelAdapters::MongoidAdapter.should_not be_for_class(Object)
35
+ CanCan::ModelAdapters::MongoidAdapter.should be_for_class(MongoidProject)
36
+ CanCan::ModelAdapters::AbstractAdapter.adapter_class(MongoidProject).should == CanCan::ModelAdapters::MongoidAdapter
37
+ end
38
+
39
+ it "should compare properties on mongoid documents with the conditions hash" do
40
+ model = MongoidProject.new
41
+ @ability.can :read, MongoidProject, :id => model.id
42
+ @ability.should be_able_to(:read, model)
43
+ end
44
+
45
+ it "should return [] when no ability is defined so no records are found" do
46
+ MongoidProject.create(:title => 'Sir')
47
+ MongoidProject.create(:title => 'Lord')
48
+ MongoidProject.create(:title => 'Dude')
49
+
50
+ MongoidProject.accessible_by(@ability, :read).entries.should == []
51
+ end
52
+
53
+ it "should return the correct records based on the defined ability" do
54
+ @ability.can :read, MongoidProject, :title => "Sir"
55
+ sir = MongoidProject.create(:title => 'Sir')
56
+ lord = MongoidProject.create(:title => 'Lord')
57
+ dude = MongoidProject.create(:title => 'Dude')
58
+
59
+ MongoidProject.accessible_by(@ability, :read).should == [sir]
60
+ end
61
+
62
+ it "should return everything when the defined ability is manage all" do
63
+ @ability.can :manage, :all
64
+ sir = MongoidProject.create(:title => 'Sir')
65
+ lord = MongoidProject.create(:title => 'Lord')
66
+ dude = MongoidProject.create(:title => 'Dude')
67
+
68
+ MongoidProject.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
69
+ end
70
+
71
+
72
+ describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do
73
+ it "should handle :field.in" do
74
+ obj = MongoidProject.create(:title => 'Sir')
75
+ @ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"]
76
+ @ability.can?(:read, obj).should == true
77
+ MongoidProject.accessible_by(@ability, :read).should == [obj]
78
+
79
+ obj2 = MongoidProject.create(:title => 'Lord')
80
+ @ability.can?(:read, obj2).should == false
81
+ end
82
+
83
+ describe "activates only when there are Criteria in the hash" do
84
+ it "Calls where on the model class when there are criteria" do
85
+ obj = MongoidProject.create(:title => 'Bird')
86
+ @conditions = {:title.nin => ["Fork", "Spoon"]}
87
+
88
+ @ability.can :read, MongoidProject, @conditions
89
+ @ability.should be_able_to(:read, obj)
90
+ end
91
+ it "Calls the base version if there are no mongoid criteria" do
92
+ obj = MongoidProject.new(:title => 'Bird')
93
+ @conditions = {:id => obj.id}
94
+ @ability.can :read, MongoidProject, @conditions
95
+ @ability.should be_able_to(:read, obj)
96
+ end
97
+ end
98
+
99
+ it "should handle :field.nin" do
100
+ obj = MongoidProject.create(:title => 'Sir')
101
+ @ability.can :read, MongoidProject, :title.nin => ["Lord", "Madam"]
102
+ @ability.can?(:read, obj).should == true
103
+ MongoidProject.accessible_by(@ability, :read).should == [obj]
104
+
105
+ obj2 = MongoidProject.create(:title => 'Lord')
106
+ @ability.can?(:read, obj2).should == false
107
+ end
108
+
109
+ it "should handle :field.size" do
110
+ obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
111
+ @ability.can :read, MongoidProject, :titles.size => 2
112
+ @ability.can?(:read, obj).should == true
113
+ MongoidProject.accessible_by(@ability, :read).should == [obj]
114
+
115
+ obj2 = MongoidProject.create(:titles => ['Palatin', 'Margrave', 'Marquis'])
116
+ @ability.can?(:read, obj2).should == false
117
+ end
118
+
119
+ it "should handle :field.exists" do
120
+ obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
121
+ @ability.can :read, MongoidProject, :titles.exists => true
122
+ @ability.can?(:read, obj).should == true
123
+ MongoidProject.accessible_by(@ability, :read).should == [obj]
124
+
125
+ obj2 = MongoidProject.create
126
+ @ability.can?(:read, obj2).should == false
127
+ end
128
+
129
+ it "should handle :field.gt" do
130
+ obj = MongoidProject.create(:age => 50)
131
+ @ability.can :read, MongoidProject, :age.gt => 45
132
+ @ability.can?(:read, obj).should == true
133
+ MongoidProject.accessible_by(@ability, :read).should == [obj]
134
+
135
+ obj2 = MongoidProject.create(:age => 40)
136
+ @ability.can?(:read, obj2).should == false
137
+ end
138
+
139
+ it "should handle instance not saved to database" do
140
+ obj = MongoidProject.new(:title => 'Sir')
141
+ @ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"]
142
+ @ability.can?(:read, obj).should == true
143
+
144
+ # accessible_by only returns saved records
145
+ MongoidProject.accessible_by(@ability, :read).entries.should == []
146
+
147
+ obj2 = MongoidProject.new(:title => 'Lord')
148
+ @ability.can?(:read, obj2).should == false
149
+ end
150
+ end
151
+
152
+ it "should call where with matching ability conditions" do
153
+ obj = MongoidProject.create(:foo => {:bar => 1})
154
+ @ability.can :read, MongoidProject, :foo => {:bar => 1}
155
+ MongoidProject.accessible_by(@ability, :read).entries.first.should == obj
156
+ end
157
+
158
+ it "should not allow to fetch records when ability with just block present" do
159
+ @ability.can :read, MongoidProject do
160
+ false
161
+ end
162
+ lambda {
163
+ MongoidProject.accessible_by(@ability)
164
+ }.should raise_error(CanCan::Error)
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,39 @@
1
+ require "spec_helper"
2
+
3
+ # Most of Rule functionality is tested in Ability specs
4
+ describe CanCan::Rule do
5
+ before(:each) do
6
+ @conditions = {}
7
+ @rule = CanCan::Rule.new(true, :read, Integer, @conditions, nil)
8
+ end
9
+
10
+ it "should return no association joins if none exist" do
11
+ @rule.associations_hash.should == {}
12
+ end
13
+
14
+ it "should return no association for joins if just attributes" do
15
+ @conditions[:foo] = :bar
16
+ @rule.associations_hash.should == {}
17
+ end
18
+
19
+ it "should return single association for joins" do
20
+ @conditions[:foo] = {:bar => 1}
21
+ @rule.associations_hash.should == {:foo => {}}
22
+ end
23
+
24
+ it "should return multiple associations for joins" do
25
+ @conditions[:foo] = {:bar => 1}
26
+ @conditions[:test] = {1 => 2}
27
+ @rule.associations_hash.should == {:foo => {}, :test => {}}
28
+ end
29
+
30
+ it "should return nested associations for joins" do
31
+ @conditions[:foo] = {:bar => {1 => 2}}
32
+ @rule.associations_hash.should == {:foo => {:bar => {}}}
33
+ end
34
+
35
+ it "should return no association joins if conditions is nil" do
36
+ rule = CanCan::Rule.new(true, :read, Integer, nil, nil)
37
+ rule.associations_hash.should == {}
38
+ end
39
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
+
3
4
  Bundler.require(:default)
5
+
4
6
  require 'supermodel' # shouldn't Bundler do this already?
5
7
  require 'active_support/all'
6
8
  require 'matchers'
@@ -27,28 +29,4 @@ end
27
29
 
28
30
  class Project < SuperModel::Base
29
31
  belongs_to :category
30
-
31
- class << self
32
- protected
33
-
34
- def sanitize_sql(hash_cond)
35
- case hash_cond
36
- when Hash
37
- sanitize_hash(hash_cond).join(' AND ')
38
- when Array
39
- hash_cond.shift.gsub('?'){"#{hash_cond.shift.inspect}"}
40
- when String then hash_cond
41
- end
42
- end
43
-
44
- def sanitize_hash(hash)
45
- hash.map do |name, value|
46
- if Hash === value
47
- sanitize_hash(value).map{|cond| "#{name}.#{cond}"}
48
- else
49
- "#{name}=#{value}"
50
- end
51
- end.flatten
52
- end
53
- end
54
32
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cancan
3
3
  version: !ruby/object:Gem::Version
4
- hash: 5
5
- prerelease: false
4
+ hash: -1848230027
5
+ prerelease: true
6
6
  segments:
7
7
  - 1
8
- - 4
9
- - 1
10
- version: 1.4.1
8
+ - 5
9
+ - 0
10
+ - beta1
11
+ version: 1.5.0.beta1
11
12
  platform: ruby
12
13
  authors:
13
14
  - Ryan Bates
@@ -15,7 +16,7 @@ autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2010-11-12 00:00:00 -08:00
19
+ date: 2011-01-08 00:00:00 -08:00
19
20
  default_executable:
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
@@ -26,14 +27,12 @@ dependencies:
26
27
  requirements:
27
28
  - - ~>
28
29
  - !ruby/object:Gem::Version
29
- hash: 62196431
30
+ hash: 11
30
31
  segments:
31
32
  - 2
33
+ - 1
32
34
  - 0
33
- - 0
34
- - beta
35
- - 22
36
- version: 2.0.0.beta.22
35
+ version: 2.1.0
37
36
  type: :development
38
37
  version_requirements: *id001
39
38
  - !ruby/object:Gem::Dependency
@@ -94,25 +93,35 @@ extra_rdoc_files: []
94
93
 
95
94
  files:
96
95
  - lib/cancan/ability.rb
97
- - lib/cancan/active_record_additions.rb
98
- - lib/cancan/can_definition.rb
99
96
  - lib/cancan/controller_additions.rb
100
97
  - lib/cancan/controller_resource.rb
101
98
  - lib/cancan/exceptions.rb
102
99
  - lib/cancan/inherited_resource.rb
103
100
  - lib/cancan/matchers.rb
104
- - lib/cancan/query.rb
101
+ - lib/cancan/model_adapters/abstract_adapter.rb
102
+ - lib/cancan/model_adapters/active_record_adapter.rb
103
+ - lib/cancan/model_adapters/data_mapper_adapter.rb
104
+ - lib/cancan/model_adapters/default_adapter.rb
105
+ - lib/cancan/model_adapters/mongoid_adapter.rb
106
+ - lib/cancan/model_additions.rb
107
+ - lib/cancan/rule.rb
105
108
  - lib/cancan.rb
109
+ - lib/generators/cancan/ability/ability_generator.rb
110
+ - lib/generators/cancan/ability/templates/ability.rb
111
+ - lib/generators/cancan/ability/USAGE
106
112
  - spec/cancan/ability_spec.rb
107
- - spec/cancan/active_record_additions_spec.rb
108
- - spec/cancan/can_definition_spec.rb
109
113
  - spec/cancan/controller_additions_spec.rb
110
114
  - spec/cancan/controller_resource_spec.rb
111
115
  - spec/cancan/exceptions_spec.rb
112
116
  - spec/cancan/inherited_resource_spec.rb
113
117
  - spec/cancan/matchers_spec.rb
114
- - spec/cancan/query_spec.rb
118
+ - spec/cancan/model_adapters/active_record_adapter_spec.rb
119
+ - spec/cancan/model_adapters/data_mapper_adapter_spec.rb
120
+ - spec/cancan/model_adapters/default_adapter_spec.rb
121
+ - spec/cancan/model_adapters/mongoid_adapter_spec.rb
122
+ - spec/cancan/rule_spec.rb
115
123
  - spec/matchers.rb
124
+ - spec/README.rdoc
116
125
  - spec/spec.opts
117
126
  - spec/spec_helper.rb
118
127
  - CHANGELOG.rdoc
data/lib/cancan/query.rb DELETED
@@ -1,97 +0,0 @@
1
- module CanCan
2
-
3
- # Generates the sql conditions and association joins for use in ActiveRecord queries.
4
- # Normally you will not use this class directly, but instead through ActiveRecordAdditions#accessible_by.
5
- class Query
6
- def initialize(sanitizer, can_definitions)
7
- @sanitizer = sanitizer
8
- @can_definitions = can_definitions
9
- end
10
-
11
- # Returns conditions intended to be used inside a database query. Normally you will not call this
12
- # method directly, but instead go through ActiveRecordAdditions#accessible_by.
13
- #
14
- # If there is only one "can" definition, a hash of conditions will be returned matching the one defined.
15
- #
16
- # can :manage, User, :id => 1
17
- # query(:manage, User).conditions # => { :id => 1 }
18
- #
19
- # If there are multiple "can" definitions, a SQL string will be returned to handle complex cases.
20
- #
21
- # can :manage, User, :id => 1
22
- # can :manage, User, :manager_id => 1
23
- # cannot :manage, User, :self_managed => true
24
- # query(:manage, User).conditions # => "not (self_managed = 't') AND ((manager_id = 1) OR (id = 1))"
25
- #
26
- def conditions
27
- if @can_definitions.size == 1 && @can_definitions.first.base_behavior
28
- # Return the conditions directly if there's just one definition
29
- @can_definitions.first.tableized_conditions
30
- else
31
- @can_definitions.reverse.inject(false_sql) do |sql, can_definition|
32
- merge_conditions(sql, can_definition.tableized_conditions, can_definition.base_behavior)
33
- end
34
- end
35
- end
36
-
37
- # Returns the associations used in conditions for the :joins option of a search.
38
- # See ActiveRecordAdditions#accessible_by for use in Active Record.
39
- def joins
40
- joins_hash = {}
41
- @can_definitions.each do |can_definition|
42
- merge_joins(joins_hash, can_definition.associations_hash)
43
- end
44
- clean_joins(joins_hash) unless joins_hash.empty?
45
- end
46
-
47
- private
48
-
49
- def merge_conditions(sql, conditions_hash, behavior)
50
- if conditions_hash.blank?
51
- behavior ? true_sql : false_sql
52
- else
53
- conditions = sanitize_sql(conditions_hash)
54
- case sql
55
- when true_sql
56
- behavior ? true_sql : "not (#{conditions})"
57
- when false_sql
58
- behavior ? conditions : false_sql
59
- else
60
- behavior ? "(#{conditions}) OR (#{sql})" : "not (#{conditions}) AND (#{sql})"
61
- end
62
- end
63
- end
64
-
65
- def false_sql
66
- sanitize_sql(['?=?', true, false])
67
- end
68
-
69
- def true_sql
70
- sanitize_sql(['?=?', true, true])
71
- end
72
-
73
- def sanitize_sql(conditions)
74
- @sanitizer.send(:sanitize_sql, conditions)
75
- end
76
-
77
- # Takes two hashes and does a deep merge.
78
- def merge_joins(base, add)
79
- add.each do |name, nested|
80
- if base[name].is_a?(Hash) && !nested.empty?
81
- merge_joins(base[name], nested)
82
- else
83
- base[name] = nested
84
- end
85
- end
86
- end
87
-
88
- # Removes empty hashes and moves everything into arrays.
89
- def clean_joins(joins_hash)
90
- joins = []
91
- joins_hash.each do |name, nested|
92
- joins << (nested.empty? ? name : {name => clean_joins(nested)})
93
- end
94
- joins
95
- end
96
- end
97
- end
@@ -1,75 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe CanCan::ActiveRecordAdditions do
4
- before(:each) do
5
- @model_class = Class.new(Project)
6
- stub(@model_class).scoped { :scoped_stub }
7
- @model_class.send(:include, CanCan::ActiveRecordAdditions)
8
- @ability = Object.new
9
- @ability.extend(CanCan::Ability)
10
- end
11
-
12
- it "should call where('true=false') when no ability is defined so no records are found" do
13
- stub(@model_class).joins { true } # just so it responds to .joins as well
14
- stub(@model_class).where('true=false').stub!.joins(nil) { :no_match }
15
- @model_class.accessible_by(@ability, :read).should == :no_match
16
- end
17
-
18
- it "should call where with matching ability conditions" do
19
- @ability.can :read, @model_class, :foo => {:bar => 1}
20
- stub(@model_class).joins { true } # just so it responds to .joins as well
21
- stub(@model_class).where(:foos => { :bar => 1 }).stub!.joins([:foo]) { :found_records }
22
- @model_class.accessible_by(@ability, :read).should == :found_records
23
- end
24
-
25
- it "should default to :read ability and use scoped when where isn't available" do
26
- @ability.can :read, @model_class, :foo => {:bar => 1}
27
- stub(@model_class).scoped(:conditions => {:foos => {:bar => 1}}, :joins => [:foo]) { :found_records }
28
- @model_class.accessible_by(@ability).should == :found_records
29
- end
30
-
31
- it "should merge association joins and sanitize conditions" do
32
- @ability.can :read, @model_class, :foo => {:bar => 1}
33
- @ability.can :read, @model_class, :too => {:car => 1, :far => {:bar => 1}}
34
-
35
- condition_variants = [
36
- '(toos.fars.bar=1 AND toos.car=1) OR (foos.bar=1)', # faked sql sanitizer is stupid ;-)
37
- '(toos.car=1 AND toos.fars.bar=1) OR (foos.bar=1)'
38
- ]
39
- joins_variants = [
40
- [:foo, {:too => [:far]}],
41
- [{:too => [:far]}, :foo]
42
- ]
43
-
44
- condition_variants.each do |condition|
45
- joins_variants.each do |joins|
46
- stub(@model_class).scoped( :conditions => condition, :joins => joins ) { :found_records }
47
- end
48
- end
49
- # @ability.conditions(:read, @model_class).should == '(too.car=1 AND too.far.bar=1) OR (foo.bar=1)'
50
- # @ability.associations_hash(:read, @model_class).should == [{:too => [:far]}, :foo]
51
- @model_class.accessible_by(@ability).should == :found_records
52
- end
53
-
54
- it "should allow to define sql conditions by not hash" do
55
- @ability.can :read, @model_class, :foo => 1
56
- @ability.can :read, @model_class, ['bar = ?', 1]
57
- stub(@model_class).scoped( :conditions => '(bar = 1) OR (foo=1)', :joins => nil ) { :found_records }
58
- stub(@model_class).scoped{|*args| args.inspect}
59
- @model_class.accessible_by(@ability).should == :found_records
60
- end
61
-
62
- it "should not allow to fetch records when ability with just block present" do
63
- @ability.can :read, @model_class do false end
64
- lambda {
65
- @model_class.accessible_by(@ability)
66
- }.should raise_error(CanCan::Error)
67
- end
68
-
69
- it "should not allow to check ability on object when nonhash sql ability definition without block present" do
70
- @ability.can :read, @model_class, ['bar = ?', 1]
71
- lambda {
72
- @ability.can? :read, @model_class.new
73
- }.should raise_error(CanCan::Error)
74
- end
75
- end