sam-dm-core 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. data/.autotest +26 -0
  2. data/CONTRIBUTING +51 -0
  3. data/FAQ +92 -0
  4. data/History.txt +145 -0
  5. data/MIT-LICENSE +22 -0
  6. data/Manifest.txt +125 -0
  7. data/QUICKLINKS +12 -0
  8. data/README.txt +143 -0
  9. data/Rakefile +30 -0
  10. data/SPECS +63 -0
  11. data/TODO +1 -0
  12. data/lib/dm-core.rb +224 -0
  13. data/lib/dm-core/adapters.rb +4 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  15. data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
  16. data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  19. data/lib/dm-core/associations.rb +199 -0
  20. data/lib/dm-core/associations/many_to_many.rb +147 -0
  21. data/lib/dm-core/associations/many_to_one.rb +107 -0
  22. data/lib/dm-core/associations/one_to_many.rb +309 -0
  23. data/lib/dm-core/associations/one_to_one.rb +61 -0
  24. data/lib/dm-core/associations/relationship.rb +218 -0
  25. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  26. data/lib/dm-core/auto_migrations.rb +113 -0
  27. data/lib/dm-core/collection.rb +638 -0
  28. data/lib/dm-core/dependency_queue.rb +31 -0
  29. data/lib/dm-core/hook.rb +11 -0
  30. data/lib/dm-core/identity_map.rb +45 -0
  31. data/lib/dm-core/is.rb +16 -0
  32. data/lib/dm-core/logger.rb +232 -0
  33. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/dm-core/migrator.rb +29 -0
  35. data/lib/dm-core/model.rb +471 -0
  36. data/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/dm-core/property.rb +673 -0
  38. data/lib/dm-core/property_set.rb +162 -0
  39. data/lib/dm-core/query.rb +625 -0
  40. data/lib/dm-core/repository.rb +159 -0
  41. data/lib/dm-core/resource.rb +637 -0
  42. data/lib/dm-core/scope.rb +58 -0
  43. data/lib/dm-core/support.rb +7 -0
  44. data/lib/dm-core/support/array.rb +13 -0
  45. data/lib/dm-core/support/assertions.rb +8 -0
  46. data/lib/dm-core/support/errors.rb +23 -0
  47. data/lib/dm-core/support/kernel.rb +7 -0
  48. data/lib/dm-core/support/symbol.rb +41 -0
  49. data/lib/dm-core/transaction.rb +267 -0
  50. data/lib/dm-core/type.rb +160 -0
  51. data/lib/dm-core/type_map.rb +80 -0
  52. data/lib/dm-core/types.rb +19 -0
  53. data/lib/dm-core/types/boolean.rb +7 -0
  54. data/lib/dm-core/types/discriminator.rb +34 -0
  55. data/lib/dm-core/types/object.rb +24 -0
  56. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  57. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  58. data/lib/dm-core/types/serial.rb +9 -0
  59. data/lib/dm-core/types/text.rb +10 -0
  60. data/lib/dm-core/version.rb +3 -0
  61. data/script/all +5 -0
  62. data/script/performance.rb +203 -0
  63. data/script/profile.rb +87 -0
  64. data/spec/integration/association_spec.rb +1371 -0
  65. data/spec/integration/association_through_spec.rb +203 -0
  66. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  67. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  68. data/spec/integration/associations/one_to_many_spec.rb +151 -0
  69. data/spec/integration/auto_migrations_spec.rb +398 -0
  70. data/spec/integration/collection_spec.rb +1069 -0
  71. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  72. data/spec/integration/dependency_queue_spec.rb +58 -0
  73. data/spec/integration/model_spec.rb +127 -0
  74. data/spec/integration/mysql_adapter_spec.rb +85 -0
  75. data/spec/integration/postgres_adapter_spec.rb +731 -0
  76. data/spec/integration/property_spec.rb +233 -0
  77. data/spec/integration/query_spec.rb +506 -0
  78. data/spec/integration/repository_spec.rb +57 -0
  79. data/spec/integration/resource_spec.rb +475 -0
  80. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  81. data/spec/integration/sti_spec.rb +208 -0
  82. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  83. data/spec/integration/transaction_spec.rb +75 -0
  84. data/spec/integration/type_spec.rb +271 -0
  85. data/spec/lib/logging_helper.rb +18 -0
  86. data/spec/lib/mock_adapter.rb +27 -0
  87. data/spec/lib/model_loader.rb +91 -0
  88. data/spec/lib/publicize_methods.rb +28 -0
  89. data/spec/models/vehicles.rb +34 -0
  90. data/spec/models/zoo.rb +47 -0
  91. data/spec/spec.opts +3 -0
  92. data/spec/spec_helper.rb +86 -0
  93. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  94. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  95. data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
  96. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  97. data/spec/unit/associations/many_to_many_spec.rb +17 -0
  98. data/spec/unit/associations/many_to_one_spec.rb +152 -0
  99. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  100. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  101. data/spec/unit/associations/relationship_spec.rb +71 -0
  102. data/spec/unit/associations_spec.rb +242 -0
  103. data/spec/unit/auto_migrations_spec.rb +111 -0
  104. data/spec/unit/collection_spec.rb +182 -0
  105. data/spec/unit/data_mapper_spec.rb +35 -0
  106. data/spec/unit/identity_map_spec.rb +126 -0
  107. data/spec/unit/is_spec.rb +80 -0
  108. data/spec/unit/migrator_spec.rb +33 -0
  109. data/spec/unit/model_spec.rb +339 -0
  110. data/spec/unit/naming_conventions_spec.rb +36 -0
  111. data/spec/unit/property_set_spec.rb +83 -0
  112. data/spec/unit/property_spec.rb +753 -0
  113. data/spec/unit/query_spec.rb +530 -0
  114. data/spec/unit/repository_spec.rb +93 -0
  115. data/spec/unit/resource_spec.rb +626 -0
  116. data/spec/unit/scope_spec.rb +142 -0
  117. data/spec/unit/transaction_spec.rb +493 -0
  118. data/spec/unit/type_map_spec.rb +114 -0
  119. data/spec/unit/type_spec.rb +119 -0
  120. data/tasks/ci.rb +68 -0
  121. data/tasks/dm.rb +63 -0
  122. data/tasks/doc.rb +20 -0
  123. data/tasks/gemspec.rb +23 -0
  124. data/tasks/hoe.rb +46 -0
  125. data/tasks/install.rb +20 -0
  126. metadata +216 -0
@@ -0,0 +1,233 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ gem 'fastercsv', '>=1.2.3'
4
+ require 'fastercsv'
5
+
6
+ if ADAPTER
7
+ describe DataMapper::Property, "with #{ADAPTER}" do
8
+ describe " tracking strategies" do
9
+ before :all do
10
+ class Actor
11
+ include DataMapper::Resource
12
+
13
+ property :id, Serial
14
+ property :name, String, :track => :set # :track default is :get for mutable types
15
+ property :notes, DataMapper::Types::Text
16
+ property :age, Integer # :track default is :set for immutable types
17
+ property :rating, Integer
18
+ property :location, String
19
+ property :lead, TrueClass, :track => :load
20
+ property :cv, Object # :track should be :hash
21
+ property :agent, String, :track => :hash # :track only Object#hash value on :load.
22
+ # Potentially faster, but less safe, so use judiciously, when the odds of a hash-collision are low.
23
+ end
24
+ end
25
+
26
+ before do
27
+ Actor.auto_migrate!(ADAPTER)
28
+ end
29
+
30
+ it "should set up tracking information" do
31
+ Actor.properties[:name].track.should == :set
32
+ Actor.properties[:location].track.should == :get
33
+ Actor.properties[:rating].track.should == :set
34
+ Actor.properties[:lead].track.should == :load
35
+ Actor.properties[:cv].track.should == :hash
36
+ Actor.properties[:agent].track.should == :hash
37
+ end
38
+
39
+ it "should track on :set" do
40
+ repository(ADAPTER) do
41
+ bob = Actor.new(:name => 'bob')
42
+ bob.save
43
+
44
+ bob.original_values.should_not have_key(:name)
45
+ bob.dirty?.should == false
46
+
47
+ bob.name = "Bob"
48
+ bob.original_values.should have_key(:name)
49
+ bob.original_values[:name].should == 'bob'
50
+ bob.dirty?.should == true
51
+ end
52
+ end
53
+
54
+ it "should track on :get" do
55
+ repository(ADAPTER) do
56
+ jon = Actor.new(:name => 'jon', :location => 'dallas')
57
+ jon.save
58
+
59
+ jon.location
60
+ jon.original_values.should have_key(:location)
61
+ jon.original_values[:location].should == 'dallas'
62
+
63
+ jon.dirty?.should be_false
64
+ jon.save.should be_false
65
+
66
+ jon.location.upcase!
67
+ jon.location.should == 'DALLAS'
68
+ jon.original_values[:location].should == 'dallas'
69
+
70
+ jon.dirty?.should be_true
71
+ jon.save.should be_true
72
+
73
+ jon.location << '!'
74
+ jon.original_values[:location].should == 'DALLAS'
75
+ jon.dirty?.should be_true
76
+ end
77
+ end
78
+
79
+ it "should track on :load" do
80
+ repository(ADAPTER) do
81
+ jan = Actor.create(:name => 'jan', :lead => true)
82
+ jan.lead = false
83
+ jan.original_values[:lead].should be_true
84
+ jan.dirty?.should == true
85
+ end
86
+ repository(ADAPTER) do
87
+ jan = Actor.first
88
+ jan.original_values.should have_key(:lead)
89
+ jan.original_values[:lead].should be_true
90
+ jan.dirty?.should == false
91
+ end
92
+ end
93
+
94
+ it "should track on :hash" do
95
+ cv = { 2005 => "Othello" }
96
+ repository(ADAPTER) do
97
+ tom = Actor.create(:name => 'tom', :cv => cv)
98
+ end
99
+ repository(ADAPTER) do
100
+ tom = Actor.first(:name => 'tom')
101
+ tom.cv.merge!({2006 => "Macbeth"})
102
+
103
+ tom.original_values.should have_key(:cv)
104
+ # tom.original_values[:cv].should == cv.hash
105
+ tom.cv.should == { 2005 => "Othello", 2006 => "Macbeth" }
106
+ tom.dirty?.should == true
107
+ end
108
+ end
109
+
110
+ it "should track with lazy text fields (#342)" do
111
+ repository(ADAPTER) do
112
+ tim = Actor.create(:name => 'tim')
113
+ end
114
+ repository(ADAPTER) do
115
+ tim = Actor.first(:name => 'tim')
116
+ tim.notes # make sure they're loaded...
117
+ tim.dirty?.should be_false
118
+ tim.save.should be_false
119
+ tim.notes = "Testing"
120
+ tim.dirty?.should be_true
121
+ tim.save
122
+ end
123
+ repository(ADAPTER) do
124
+ tim = Actor.first(:name => 'tim')
125
+ tim.notes.should == "Testing"
126
+ end
127
+ end
128
+ end
129
+
130
+ describe "lazy loading" do
131
+ before :all do
132
+ class RowBoat
133
+ include DataMapper::Resource
134
+ property :id, Serial
135
+ property :notes, String, :lazy => [:notes]
136
+ property :trip_report, String, :lazy => [:notes,:trip]
137
+ property :miles, Integer, :lazy => [:trip]
138
+ end
139
+ end
140
+
141
+ before do
142
+ RowBoat.auto_migrate!(ADAPTER)
143
+
144
+ repository(ADAPTER) do
145
+ RowBoat.create(:id => 1, :notes=>'Note',:trip_report=>'Report',:miles=>23)
146
+ RowBoat.create(:id => 2, :notes=>'Note',:trip_report=>'Report',:miles=>23)
147
+ RowBoat.create(:id => 3, :notes=>'Note',:trip_report=>'Report',:miles=>23)
148
+ end
149
+ end
150
+
151
+ it "should lazy load in context" do
152
+ result = repository(ADAPTER) { RowBoat.all.to_a }
153
+
154
+ result[0].attribute_loaded?(:notes).should be_false
155
+ result[0].attribute_loaded?(:trip_report).should be_false
156
+ result[1].attribute_loaded?(:notes).should be_false
157
+
158
+ result[0].notes.should_not be_nil
159
+
160
+ result[1].attribute_loaded?(:notes).should be_true
161
+ result[1].attribute_loaded?(:trip_report).should be_true
162
+ result[1].attribute_loaded?(:miles).should be_false
163
+
164
+ result = repository(ADAPTER) { RowBoat.all.to_a }
165
+
166
+ result[0].attribute_loaded?(:trip_report).should be_false
167
+ result[0].attribute_loaded?(:miles).should be_false
168
+
169
+ result[1].trip_report.should_not be_nil
170
+ result[2].attribute_loaded?(:miles).should be_true
171
+ end
172
+
173
+ it "should lazy load on Property#set" do
174
+ repository(ADAPTER) do
175
+ boat = RowBoat.first
176
+ boat.attribute_loaded?(:notes).should be_false
177
+ boat.notes = 'New Note'
178
+ boat.original_values[:notes].should == "Note"
179
+ end
180
+ end
181
+ end
182
+
183
+ describe 'defaults' do
184
+ before :all do
185
+ class Catamaran
186
+ include DataMapper::Resource
187
+ property :id, Serial
188
+ property :name, String
189
+
190
+ # Boolean
191
+ property :could_be_bool0, TrueClass, :default => true
192
+ property :could_be_bool1, TrueClass, :default => false
193
+ end
194
+
195
+ repository(ADAPTER){ Catamaran.auto_migrate!(ADAPTER) }
196
+ end
197
+
198
+ before :each do
199
+ @cat = Catamaran.new
200
+ end
201
+
202
+ it "should have defaults" do
203
+ @cat.could_be_bool0.should == true
204
+ @cat.could_be_bool1.should_not be_nil
205
+ @cat.could_be_bool1.should == false
206
+
207
+ @cat.name = 'Mary Mayweather'
208
+
209
+ repository(ADAPTER) do
210
+ @cat.save
211
+
212
+ cat = Catamaran.first
213
+ cat.could_be_bool0.should == true
214
+ cat.could_be_bool1.should_not be_nil
215
+ cat.could_be_bool1.should == false
216
+ cat.destroy
217
+ end
218
+
219
+ end
220
+
221
+ it "should have defaults even with creates" do
222
+ repository(ADAPTER) do
223
+ Catamaran.create(:name => 'Jingle All The Way')
224
+ cat = Catamaran.first
225
+ cat.name.should == 'Jingle All The Way'
226
+ cat.could_be_bool0.should == true
227
+ cat.could_be_bool1.should_not be_nil
228
+ cat.could_be_bool1.should == false
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,506 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ if ADAPTER
4
+ module QuerySpec
5
+ class SailBoat
6
+ include DataMapper::Resource
7
+
8
+ property :id, Serial
9
+ property :name, String
10
+ property :port, String
11
+ property :captain, String
12
+
13
+ def self.default_repository_name
14
+ ADAPTER
15
+ end
16
+ end
17
+
18
+ class Permission
19
+ include DataMapper::Resource
20
+
21
+ property :id, Serial
22
+ property :user_id, Integer
23
+ property :resource_id, Integer
24
+ property :resource_type, String
25
+ property :token, String
26
+
27
+ def self.default_repository_name
28
+ ADAPTER
29
+ end
30
+ end
31
+
32
+ class Region
33
+ include DataMapper::Resource
34
+
35
+ property :id, Serial
36
+ property :name, String
37
+ property :type, String
38
+
39
+ def self.default_repository_name
40
+ ADAPTER
41
+ end
42
+ end
43
+
44
+ class Factory
45
+ include DataMapper::Resource
46
+
47
+ property :id, Serial
48
+ property :region_id, Integer
49
+ property :name, String
50
+
51
+ repository(:mock) do
52
+ property :land, String
53
+ end
54
+
55
+ belongs_to :region
56
+
57
+ def self.default_repository_name
58
+ ADAPTER
59
+ end
60
+ end
61
+
62
+ class Vehicle
63
+ include DataMapper::Resource
64
+
65
+ property :id, Serial
66
+ property :factory_id, Integer
67
+ property :name, String
68
+
69
+ belongs_to :factory
70
+
71
+ def self.default_repository_name
72
+ ADAPTER
73
+ end
74
+ end
75
+
76
+ class Group
77
+ include DataMapper::Resource
78
+ property :id, Serial
79
+ property :name, String
80
+ end
81
+ end
82
+
83
+ module Namespace
84
+ class Region
85
+ include DataMapper::Resource
86
+
87
+ property :id, Serial
88
+ property :name, String
89
+
90
+ def self.default_repository_name
91
+ ADAPTER
92
+ end
93
+ end
94
+
95
+ class Factory
96
+ include DataMapper::Resource
97
+
98
+ property :id, Serial
99
+ property :region_id, Integer
100
+ property :name, String
101
+
102
+ repository(:mock) do
103
+ property :land, String
104
+ end
105
+
106
+ belongs_to :region
107
+
108
+ def self.default_repository_name
109
+ ADAPTER
110
+ end
111
+ end
112
+
113
+ class Vehicle
114
+ include DataMapper::Resource
115
+ property :id, Serial
116
+ property :factory_id, Integer
117
+ property :name, String
118
+
119
+ belongs_to :factory
120
+
121
+ def self.default_repository_name
122
+ ADAPTER
123
+ end
124
+ end
125
+ end
126
+
127
+ describe DataMapper::Query, "with #{ADAPTER}" do
128
+ describe '#unique' do
129
+ include LoggingHelper
130
+
131
+ before(:each) do
132
+ QuerySpec::SailBoat.auto_migrate!
133
+
134
+ QuerySpec::SailBoat.create(:name => 'A', :port => 'C')
135
+ QuerySpec::SailBoat.create(:name => 'B', :port => 'B')
136
+ QuerySpec::SailBoat.create(:name => 'C', :port => 'A')
137
+ end
138
+
139
+ def parse_statement(log)
140
+ log.readlines.join.chomp.split(' ~ ').last
141
+ end
142
+
143
+ describe 'when true' do
144
+ if [ :postgres, :sqlite3, :mysql ].include?(ADAPTER)
145
+ it 'should add a GROUP BY to the SQL query' do
146
+ logger do |log|
147
+ QuerySpec::SailBoat.all(:unique => true, :fields => [ :id ]).to_a
148
+
149
+ case ADAPTER
150
+ when :postgres, :sqlite3
151
+ parse_statement(log).should == 'SELECT "id" FROM "query_spec_sail_boats" GROUP BY "id" ORDER BY "id"'
152
+ when :mysql
153
+ parse_statement(log).should == 'SELECT `id` FROM `query_spec_sail_boats` GROUP BY `id` ORDER BY `id`'
154
+ end
155
+ end
156
+ end
157
+
158
+ it 'should not add a GROUP BY to the SQL query if no field is a Property' do
159
+ operator = DataMapper::Query::Operator.new(:thing, :test)
160
+
161
+ # make the operator act like a Property
162
+ class << operator
163
+ property = QuerySpec::SailBoat.properties[:id]
164
+ (property.methods - (public_instance_methods - %w[ type ])).each do |method|
165
+ define_method(method) do |*args|
166
+ property.send(method, *args)
167
+ end
168
+ end
169
+ end
170
+
171
+ operator.should_not be_kind_of(DataMapper::Property)
172
+
173
+ logger do |log|
174
+ QuerySpec::SailBoat.all(:unique => true, :fields => [ operator ]).to_a
175
+
176
+ case ADAPTER
177
+ when :postgres, :sqlite3
178
+ parse_statement(log).should == 'SELECT "id" FROM "query_spec_sail_boats" ORDER BY "id"'
179
+ when :mysql
180
+ parse_statement(log).should == 'SELECT `id` FROM `query_spec_sail_boats` ORDER BY `id`'
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ describe 'when false' do
188
+ if [ :postgres, :sqlite3, :mysql ].include?(ADAPTER)
189
+ it 'should not add a GROUP BY to the SQL query' do
190
+ logger do |log|
191
+ QuerySpec::SailBoat.all(:unique => false, :fields => [ :id ]).to_a
192
+
193
+ case ADAPTER
194
+ when :postgres, :sqlite3
195
+ parse_statement(log).should == 'SELECT "id" FROM "query_spec_sail_boats" ORDER BY "id"'
196
+ when :mysql
197
+ parse_statement(log).should == 'SELECT `id` FROM `query_spec_sail_boats` ORDER BY `id`'
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ describe 'when ordering' do
206
+ before(:each) do
207
+ QuerySpec::SailBoat.auto_migrate!
208
+
209
+ QuerySpec::SailBoat.create(:name => 'A', :port => 'C')
210
+ QuerySpec::SailBoat.create(:name => 'B', :port => 'B')
211
+ QuerySpec::SailBoat.create(:name => 'C', :port => 'A')
212
+ end
213
+
214
+ it "should find by conditions" do
215
+ lambda do
216
+ repository(ADAPTER) do
217
+ QuerySpec::SailBoat.first(:conditions => [ 'name = ?', 'B' ])
218
+ end
219
+ end.should_not raise_error
220
+
221
+ lambda do
222
+ repository(ADAPTER) do
223
+ QuerySpec::SailBoat.first(:conditions => [ 'name = ?', 'A' ])
224
+ end
225
+ end.should_not raise_error
226
+ end
227
+
228
+ it "should find by conditions passed in as hash" do
229
+ repository(ADAPTER) do
230
+ QuerySpec::SailBoat.create(:name => "couldbe@email.com", :port => 'wee')
231
+
232
+ find = QuerySpec::SailBoat.first(:name => 'couldbe@email.com')
233
+ find.name.should == 'couldbe@email.com'
234
+
235
+ find = QuerySpec::SailBoat.first(:name => 'couldbe@email.com', :port.not => nil)
236
+ find.should_not be_nil
237
+ find.port.should_not be_nil
238
+ find.name.should == 'couldbe@email.com'
239
+ end
240
+ end
241
+
242
+ it "should find by conditions passed in a range" do
243
+ repository(ADAPTER) do
244
+ find = QuerySpec::SailBoat.all(:id => 0..2)
245
+ find.should_not be_nil
246
+ find.should have(2).entries
247
+
248
+ find = QuerySpec::SailBoat.all(:id.not => 0..2)
249
+ find.should have(1).entries
250
+ end
251
+ end
252
+
253
+ it "should find by conditions passed in as an array" do
254
+ repository(ADAPTER) do
255
+ find = QuerySpec::SailBoat.all(:id => [1,2])
256
+ find.should_not be_nil
257
+ find.should have(2).entries
258
+
259
+ find = QuerySpec::SailBoat.all(:id.not => [1,2])
260
+ find.should have(1).entries
261
+ end
262
+ end
263
+
264
+ describe "conditions passed in as an empty array" do
265
+ it "should work when id is an empty Array" do
266
+ repository(ADAPTER) do
267
+ find = QuerySpec::SailBoat.all(:id => [])
268
+ find.should have(0).entries
269
+ end
270
+ end
271
+
272
+ it "should work when id is NOT an empty Array" do
273
+ repository(ADAPTER) do
274
+ find = QuerySpec::SailBoat.all(:id.not => [])
275
+ find.should have(3).entries
276
+ end
277
+ end
278
+
279
+ it "should work when id is an empty Array and other conditions are specified" do
280
+ repository(ADAPTER) do
281
+ find = QuerySpec::SailBoat.all(:id => [], :name => "A")
282
+ find.should have(0).entries
283
+ end
284
+ end
285
+
286
+ it "should work when id is NOT an empty Array and other conditions are specified" do
287
+ repository(ADAPTER) do
288
+ find = QuerySpec::SailBoat.all(:id.not => [], :name => "A")
289
+ find.should have(1).entries
290
+ end
291
+ end
292
+
293
+ it "should work when id is NOT an empty Array and other Array conditions are specified" do
294
+ repository(ADAPTER) do
295
+ find = QuerySpec::SailBoat.all(:id.not => [], :name => ["A", "B"])
296
+ find.should have(2).entries
297
+ end
298
+ end
299
+ end
300
+
301
+ it "should order results" do
302
+ repository(ADAPTER) do
303
+ result = QuerySpec::SailBoat.all(:order => [
304
+ DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:name], :asc)
305
+ ])
306
+ result[0].id.should == 1
307
+
308
+ result = QuerySpec::SailBoat.all(:order => [
309
+ DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:port], :asc)
310
+ ])
311
+ result[0].id.should == 3
312
+
313
+ result = QuerySpec::SailBoat.all(:order => [
314
+ DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:name], :asc),
315
+ DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:port], :asc)
316
+ ])
317
+ result[0].id.should == 1
318
+
319
+ result = QuerySpec::SailBoat.all(:order => [
320
+ QuerySpec::SailBoat.properties[:name],
321
+ DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:port], :asc)
322
+ ])
323
+ result[0].id.should == 1
324
+
325
+ result = QuerySpec::SailBoat.all(:order => [ :name ])
326
+ result[0].id.should == 1
327
+
328
+ result = QuerySpec::SailBoat.all(:order => [ :name.desc ])
329
+ result[0].id.should == 3
330
+ end
331
+ end
332
+ end
333
+
334
+ describe 'when sub-selecting' do
335
+ before(:each) do
336
+ [ QuerySpec::SailBoat, QuerySpec::Permission ].each { |m| m.auto_migrate! }
337
+
338
+ QuerySpec::SailBoat.create(:id => 1, :name => "Fantasy I", :port => "Cape Town", :captain => 'Joe')
339
+ QuerySpec::SailBoat.create(:id => 2, :name => "Royal Flush II", :port => "Cape Town", :captain => 'James')
340
+ QuerySpec::SailBoat.create(:id => 3, :name => "Infringer III", :port => "Cape Town", :captain => 'Jason')
341
+
342
+ #User 1 permission -- read boat 1 & 2
343
+ QuerySpec::Permission.create(:id => 1, :user_id => 1, :resource_id => 1, :resource_type => 'SailBoat', :token => 'READ')
344
+ QuerySpec::Permission.create(:id => 2, :user_id => 1, :resource_id => 2, :resource_type => 'SailBoat', :token => 'READ')
345
+
346
+ #User 2 permission -- read boat 2 & 3
347
+ QuerySpec::Permission.create(:id => 3, :user_id => 2, :resource_id => 2, :resource_type => 'SailBoat', :token => 'READ')
348
+ QuerySpec::Permission.create(:id => 4, :user_id => 2, :resource_id => 3, :resource_type => 'SailBoat', :token => 'READ')
349
+ end
350
+
351
+ it 'should accept a DM::Query as a value of a condition' do
352
+ # User 1
353
+ acl = DataMapper::Query.new(repository(ADAPTER), QuerySpec::Permission, :user_id => 1, :resource_type => 'SailBoat', :token => 'READ', :fields => [ :resource_id ])
354
+ query = { :port => 'Cape Town', :id => acl, :captain.like => 'J%', :order => [ :id ] }
355
+ boats = repository(ADAPTER) { QuerySpec::SailBoat.all(query) }
356
+ boats.should have(2).entries
357
+ boats.entries[0].id.should == 1
358
+ boats.entries[1].id.should == 2
359
+
360
+ # User 2
361
+ acl = DataMapper::Query.new(repository(ADAPTER), QuerySpec::Permission, :user_id => 2, :resource_type => 'SailBoat', :token => 'READ', :fields => [ :resource_id ])
362
+ query = { :port => 'Cape Town', :id => acl, :captain.like => 'J%', :order => [ :id ] }
363
+ boats = repository(ADAPTER) { QuerySpec::SailBoat.all(query) }
364
+
365
+ boats.should have(2).entries
366
+ boats.entries[0].id.should == 2
367
+ boats.entries[1].id.should == 3
368
+ end
369
+
370
+ it 'when value is NOT IN another query' do
371
+ # Boats that User 1 Cannot see
372
+ acl = DataMapper::Query.new(repository(ADAPTER), QuerySpec::Permission, :user_id => 1, :resource_type => 'SailBoat', :token => 'READ', :fields => [ :resource_id ])
373
+ query = { :port => 'Cape Town', :id.not => acl, :captain.like => 'J%' }
374
+ boats = repository(ADAPTER) { QuerySpec::SailBoat.all(query) }
375
+ boats.should have(1).entries
376
+ boats.entries[0].id.should == 3
377
+ end
378
+ end # describe sub-selecting
379
+
380
+ describe 'when linking associated objects' do
381
+ before(:each) do
382
+ [ QuerySpec::Region, QuerySpec::Factory, QuerySpec::Vehicle ].each { |m| m.auto_migrate! }
383
+
384
+ QuerySpec::Region.create(:id => 1, :name => 'North West', :type => 'commercial')
385
+ QuerySpec::Factory.create(:id => 2000, :region_id => 1, :name => 'North West Plant')
386
+ QuerySpec::Vehicle.create(:id => 1, :factory_id => 2000, :name => '10 ton delivery truck')
387
+
388
+ Namespace::Region.auto_migrate!
389
+ Namespace::Factory.auto_migrate!
390
+ Namespace::Vehicle.auto_migrate!
391
+
392
+ Namespace::Region.create(:id => 1, :name => 'North West')
393
+ Namespace::Factory.create(:id => 2000, :region_id => 1, :name => 'North West Plant')
394
+ Namespace::Vehicle.create(:id => 1, :factory_id => 2000, :name => '10 ton delivery truck')
395
+ end
396
+
397
+ it 'should require that all properties in :fields and all :links come from the same repository' #do
398
+ # land = QuerySpec::Factory.properties(:mock)[:land]
399
+ # fields = []
400
+ # QuerySpec::Vehicle.properties(ADAPTER).map do |property|
401
+ # fields << property
402
+ # end
403
+ # fields << land
404
+ #
405
+ # lambda{
406
+ # begin
407
+ # results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ :factory ], :fields => fields) }
408
+ # rescue RuntimeError
409
+ # $!.message.should == "Property QuerySpec::Factory.land not available in repository #{ADAPTER}"
410
+ # raise $!
411
+ # end
412
+ # }.should raise_error(RuntimeError)
413
+ #end
414
+
415
+ it 'should accept a DM::Assoc::Relationship as a link' do
416
+ factory = DataMapper::Associations::Relationship.new(
417
+ :factory,
418
+ ADAPTER,
419
+ 'QuerySpec::Vehicle',
420
+ 'QuerySpec::Factory',
421
+ { :child_key => [ :factory_id ], :parent_key => [ :id ] }
422
+ )
423
+ results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ factory ]) }
424
+ results.should have(1).entries
425
+ end
426
+
427
+ it 'should accept a symbol of an association name as a link' do
428
+ results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ :factory ]) }
429
+ results.should have(1).entries
430
+ end
431
+
432
+ it 'should accept a string of an association name as a link' do
433
+ results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ 'factory' ]) }
434
+ results.should have(1).entries
435
+ end
436
+
437
+ it 'should accept a mixture of items as a set of links' do
438
+ region = DataMapper::Associations::Relationship.new(
439
+ :region,
440
+ ADAPTER,
441
+ 'QuerySpec::Factory',
442
+ 'QuerySpec::Region',
443
+ { :child_key => [ :region_id ], :parent_key => [ :id ] }
444
+ )
445
+ results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ 'factory', region ]) }
446
+ results.should have(1).entries
447
+ end
448
+
449
+ it 'should only accept a DM::Assoc::Relationship, String & Symbol as a link' do
450
+ lambda{
451
+ DataMapper::Query.new(repository(ADAPTER), QuerySpec::Vehicle, :links => [1])
452
+ }.should raise_error(ArgumentError)
453
+ end
454
+
455
+ it 'should have a association by the name of the Symbol or String' do
456
+ lambda{
457
+ DataMapper::Query.new(repository(ADAPTER), QuerySpec::Vehicle, :links => [ 'Sailing' ])
458
+ }.should raise_error(ArgumentError)
459
+
460
+ lambda{
461
+ DataMapper::Query.new(repository(ADAPTER), QuerySpec::Vehicle, :links => [ :sailing ])
462
+ }.should raise_error(ArgumentError)
463
+ end
464
+
465
+ it 'should create an n-level query path' do
466
+ QuerySpec::Vehicle.factory.region.model.should == QuerySpec::Region
467
+ QuerySpec::Vehicle.factory.region.name.property.should == QuerySpec::Region.properties(QuerySpec::Region.repository.name)[ :name ]
468
+ end
469
+
470
+ it 'should accept a DM::QueryPath as the key to a condition' do
471
+ vehicle = QuerySpec::Vehicle.first(QuerySpec::Vehicle.factory.region.name => 'North West')
472
+ vehicle.name.should == '10 ton delivery truck'
473
+
474
+ vehicle = Namespace::Vehicle.first(Namespace::Vehicle.factory.region.name => 'North West')
475
+ vehicle.name.should == '10 ton delivery truck'
476
+ end
477
+
478
+ it "should accept a string representing a DM::QueryPath as they key to a condition" do
479
+ vehicle = QuerySpec::Vehicle.first("factory.region.name" => 'North West')
480
+ vehicle.name.should == '10 ton delivery truck'
481
+ end
482
+
483
+ it "should accept 'id' and 'type' as endpoints on ah DM::QueryPath" do
484
+ vehicle = QuerySpec::Vehicle.first( QuerySpec::Vehicle.factory.region.type => 'commercial' )
485
+ vehicle.name.should == '10 ton delivery truck'
486
+ vehicle = QuerySpec::Vehicle.first( QuerySpec::Vehicle.factory.region.id => 1 )
487
+ vehicle.name.should == '10 ton delivery truck'
488
+ end
489
+
490
+ it 'should auto generate the link if a DM::Property from a different resource is in the :fields option'
491
+
492
+ it 'should create links with composite keys'
493
+
494
+ it 'should eager load associations' do
495
+ repository(ADAPTER) do
496
+ vehicle = QuerySpec::Vehicle.first(:includes => [ QuerySpec::Vehicle.factory ])
497
+ end
498
+ end
499
+
500
+ it "should behave when using mocks" do
501
+ QuerySpec::Group.should_receive(:all).with(:order => [ :id.asc ])
502
+ QuerySpec::Group.all(:order => [ :id.asc ])
503
+ end
504
+ end # describe links
505
+ end # DM::Query
506
+ end