datamapper-dm-core 0.9.11

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