sam-dm-core 0.9.6

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