cancan 1.4.1 → 1.5.0.beta1

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