dm-core 0.9.5 → 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 (54) hide show
  1. data/Manifest.txt +3 -0
  2. data/lib/dm-core.rb +14 -20
  3. data/lib/dm-core/adapters.rb +18 -0
  4. data/lib/dm-core/adapters/abstract_adapter.rb +17 -10
  5. data/lib/dm-core/adapters/data_objects_adapter.rb +17 -22
  6. data/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
  7. data/lib/dm-core/adapters/mysql_adapter.rb +1 -1
  8. data/lib/dm-core/adapters/postgres_adapter.rb +2 -2
  9. data/lib/dm-core/adapters/sqlite3_adapter.rb +1 -1
  10. data/lib/dm-core/associations.rb +3 -2
  11. data/lib/dm-core/associations/many_to_many.rb +3 -3
  12. data/lib/dm-core/associations/one_to_many.rb +10 -2
  13. data/lib/dm-core/associations/relationship.rb +20 -16
  14. data/lib/dm-core/auto_migrations.rb +5 -4
  15. data/lib/dm-core/collection.rb +10 -6
  16. data/lib/dm-core/dependency_queue.rb +2 -1
  17. data/lib/dm-core/identity_map.rb +3 -6
  18. data/lib/dm-core/model.rb +48 -27
  19. data/lib/dm-core/property.rb +57 -37
  20. data/lib/dm-core/property_set.rb +29 -22
  21. data/lib/dm-core/query.rb +57 -49
  22. data/lib/dm-core/repository.rb +3 -3
  23. data/lib/dm-core/resource.rb +17 -15
  24. data/lib/dm-core/scope.rb +7 -7
  25. data/lib/dm-core/support/kernel.rb +6 -2
  26. data/lib/dm-core/transaction.rb +7 -7
  27. data/lib/dm-core/version.rb +1 -1
  28. data/script/performance.rb +114 -22
  29. data/spec/integration/association_spec.rb +31 -2
  30. data/spec/integration/association_through_spec.rb +2 -0
  31. data/spec/integration/associations/many_to_many_spec.rb +152 -0
  32. data/spec/integration/associations/one_to_many_spec.rb +40 -3
  33. data/spec/integration/dependency_queue_spec.rb +0 -12
  34. data/spec/integration/postgres_adapter_spec.rb +1 -1
  35. data/spec/integration/property_spec.rb +3 -3
  36. data/spec/integration/query_spec.rb +39 -8
  37. data/spec/integration/resource_spec.rb +10 -6
  38. data/spec/integration/sti_spec.rb +22 -0
  39. data/spec/integration/strategic_eager_loading_spec.rb +21 -6
  40. data/spec/integration/type_spec.rb +1 -0
  41. data/spec/lib/model_loader.rb +10 -1
  42. data/spec/models/content.rb +16 -0
  43. data/spec/spec_helper.rb +4 -1
  44. data/spec/unit/adapters/data_objects_adapter_spec.rb +11 -11
  45. data/spec/unit/adapters/in_memory_adapter_spec.rb +98 -0
  46. data/spec/unit/associations/many_to_many_spec.rb +16 -1
  47. data/spec/unit/model_spec.rb +0 -16
  48. data/spec/unit/property_set_spec.rb +8 -1
  49. data/spec/unit/property_spec.rb +476 -240
  50. data/spec/unit/query_spec.rb +41 -0
  51. data/spec/unit/resource_spec.rb +75 -56
  52. data/tasks/ci.rb +4 -36
  53. data/tasks/dm.rb +3 -3
  54. metadata +5 -2
@@ -103,6 +103,22 @@ describe "OneToMany" do
103
103
  end
104
104
  end
105
105
 
106
+ describe "parent initialized child" do
107
+ before(:each) do
108
+ @ajax = Team.create
109
+ @vandesar = @ajax.players.new
110
+ @vandesar.save
111
+ end
112
+
113
+ it "child association should return parent" do
114
+ @vandesar.team.should == @ajax
115
+ end
116
+
117
+ it "parent association should return children" do
118
+ @ajax.players.should == [@vandesar]
119
+ end
120
+ end
121
+
106
122
  it "unsaved parent model should accept array of hashes for association" do
107
123
  players = [{ :name => "Brett Favre" }, { :name => "Reggie White" }]
108
124
 
@@ -139,12 +155,33 @@ describe "OneToMany" do
139
155
  end
140
156
 
141
157
  describe "STI" do
142
- it "should work" do
158
+ before(:all) do
159
+ repository(ADAPTER) do
160
+ @player = Player.create(:name => "Barry Bonds", :team => BaseballTeam.first)
161
+ end
162
+ end
163
+
164
+ it "should work for child.parent" do
143
165
  repository(ADAPTER) do
144
- Player.create(:name => "Barry Bonds", :team => BaseballTeam.first)
166
+ @player.team.should == BaseballTeam.first
145
167
  end
168
+ end
169
+
170
+ it "should work for parent.children" do
171
+ repository(ADAPTER) do
172
+ team = BaseballTeam.first
173
+
174
+ team.players.size.should > 0
175
+ team.players.each{|p| p.should be_an_instance_of(Player)}
176
+ end
177
+ end
178
+ end
179
+
180
+ describe "alone" do
181
+ it "should work for parent.children without any parents in IM" do
146
182
  repository(ADAPTER) do
147
- Player.first.team.should == BaseballTeam.first
183
+ team = BaseballTeam.first
184
+ team.players.each{|p| p.should be_an_instance_of(Player)}
148
185
  end
149
186
  end
150
187
  end
@@ -6,18 +6,6 @@ describe "DataMapper::DependencyQueue" do
6
6
  @dependencies = @q.instance_variable_get("@dependencies")
7
7
  end
8
8
 
9
- describe "#initialize" do
10
- describe "@dependencies" do
11
- it "should be a hash after initialize" do
12
- @dependencies.should be_a_kind_of(Hash)
13
- end
14
-
15
- it "should set value to [] when new key is accessed" do
16
- @dependencies['New Key'].should == []
17
- end
18
- end
19
- end
20
-
21
9
  describe "#add" do
22
10
  it "should store the supplied callback in @dependencies" do
23
11
  @q.add('MissingConstant') { true }
@@ -127,7 +127,7 @@ if HAS_POSTGRES
127
127
 
128
128
  class Voyager
129
129
  include DataMapper::Resource
130
- storage_names[:postgres] = 'sattellites.voyagers'
130
+ storage_names[:postgres] = 'voyagers'
131
131
 
132
132
  property :id, Serial
133
133
  property :age, Integer
@@ -61,7 +61,7 @@ if ADAPTER
61
61
  jon.original_values[:location].should == 'dallas'
62
62
 
63
63
  jon.dirty?.should be_false
64
- jon.save.should be_false
64
+ jon.save.should be_true
65
65
 
66
66
  jon.location.upcase!
67
67
  jon.location.should == 'DALLAS'
@@ -115,10 +115,10 @@ if ADAPTER
115
115
  tim = Actor.first(:name => 'tim')
116
116
  tim.notes # make sure they're loaded...
117
117
  tim.dirty?.should be_false
118
- tim.save.should be_false
118
+ tim.save.should be_true
119
119
  tim.notes = "Testing"
120
120
  tim.dirty?.should be_true
121
- tim.save
121
+ tim.save.should be_true
122
122
  end
123
123
  repository(ADAPTER) do
124
124
  tim = Actor.first(:name => 'tim')
@@ -128,7 +128,7 @@ if ADAPTER
128
128
  describe '#unique' do
129
129
  include LoggingHelper
130
130
 
131
- before do
131
+ before(:each) do
132
132
  QuerySpec::SailBoat.auto_migrate!
133
133
 
134
134
  QuerySpec::SailBoat.create(:name => 'A', :port => 'C')
@@ -203,7 +203,7 @@ if ADAPTER
203
203
  end
204
204
 
205
205
  describe 'when ordering' do
206
- before do
206
+ before(:each) do
207
207
  QuerySpec::SailBoat.auto_migrate!
208
208
 
209
209
  QuerySpec::SailBoat.create(:name => 'A', :port => 'C')
@@ -258,12 +258,43 @@ if ADAPTER
258
258
 
259
259
  find = QuerySpec::SailBoat.all(:id.not => [1,2])
260
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
261
271
 
262
- find = QuerySpec::SailBoat.all(:id => [])
263
- find.should have(0).entries
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
264
278
 
265
- find = QuerySpec::SailBoat.all(:id.not => [])
266
- find.should have(3).entries
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
267
298
  end
268
299
  end
269
300
 
@@ -301,7 +332,7 @@ if ADAPTER
301
332
  end
302
333
 
303
334
  describe 'when sub-selecting' do
304
- before do
335
+ before(:each) do
305
336
  [ QuerySpec::SailBoat, QuerySpec::Permission ].each { |m| m.auto_migrate! }
306
337
 
307
338
  QuerySpec::SailBoat.create(:id => 1, :name => "Fantasy I", :port => "Cape Town", :captain => 'Joe')
@@ -347,7 +378,7 @@ if ADAPTER
347
378
  end # describe sub-selecting
348
379
 
349
380
  describe 'when linking associated objects' do
350
- before do
381
+ before(:each) do
351
382
  [ QuerySpec::Region, QuerySpec::Factory, QuerySpec::Vehicle ].each { |m| m.auto_migrate! }
352
383
 
353
384
  QuerySpec::Region.create(:id => 1, :name => 'North West', :type => 'commercial')
@@ -77,9 +77,11 @@ if ADAPTER
77
77
  end
78
78
 
79
79
  it "should return a different hash value for different objects of the same type" do
80
- e1 = Employee.create(:name => "John")
81
- e2 = Employee.create(:name => "Dan")
82
- e1.hash.should_not == e2.hash
80
+ repository(ADAPTER) do
81
+ e1 = Employee.create(:name => "John")
82
+ e2 = Employee.create(:name => "Dan")
83
+ e1.hash.should_not == e2.hash
84
+ end
83
85
  end
84
86
 
85
87
  it "should return a different hash value for different types of objects with the same key"
@@ -96,9 +98,11 @@ if ADAPTER
96
98
  describe '#key' do
97
99
  describe "original_value[:key]" do
98
100
  it "should be used when an existing resource's key changes" do
99
- employee = Employee.create(:name => "John")
100
- employee.name = "Jon"
101
- employee.key.should == ["John"]
101
+ repository(ADAPTER) do
102
+ employee = Employee.create(:name => "John")
103
+ employee.name = "Jon"
104
+ employee.key.should == ["John"]
105
+ end
102
106
  end
103
107
 
104
108
  it "should be used when saving an existing resource" do
@@ -204,5 +204,27 @@ if HAS_SQLITE3
204
204
  SpaceWestern.properties[:title].should_not be_nil
205
205
  end
206
206
  end
207
+
208
+ describe "with a child class" do
209
+ before :all do
210
+ Book.auto_migrate!(:sqlite3)
211
+ repository(:sqlite3) do
212
+ ShortStory.create(
213
+ :title => "The Science of Happiness",
214
+ :isbn => "129038",
215
+ :moral => "Bullshit might get you to the top, but it won't keep you there.")
216
+ end
217
+ end
218
+
219
+ it "should be able to access the properties from the parent collection" do
220
+ repository(:sqlite3) do
221
+ Book.all.each do |book|
222
+ book.title.should_not be_nil
223
+ book.isbn.should_not be_nil
224
+ book.moral.should_not be_nil
225
+ end
226
+ end
227
+ end
228
+ end
207
229
  end
208
230
  end
@@ -64,8 +64,12 @@ describe "Strategic Eager Loading" do
64
64
  zoos = Zoo.all.entries # load all zoos
65
65
  dallas = zoos.find { |z| z.name == 'Dallas Zoo' }
66
66
 
67
- dallas.exhibits.entries # load all exhibits for zoos in identity_map
68
- dallas.exhibits.size.should == 1
67
+ logger do |log|
68
+ dallas.exhibits.entries # load all exhibits for zoos in identity_map
69
+ dallas.exhibits.size.should == 1
70
+ log.readlines.size.should == 1
71
+ end
72
+
69
73
  repository.identity_map(Zoo).keys.sort.should == zoo_ids
70
74
  repository.identity_map(Exhibit).keys.sort.should == exhibit_ids
71
75
 
@@ -88,16 +92,21 @@ describe "Strategic Eager Loading" do
88
92
  dallas = Zoo.all.entries.find { |z| z.name == 'Dallas Zoo' }
89
93
  exhibits = dallas.exhibits.entries # load all exhibits
90
94
 
95
+ reptiles, primates = nil, nil
96
+
91
97
  logger do |log|
92
98
  reptiles = dallas.exhibits(:name => 'Reptiles')
93
99
  reptiles.size.should == 1
100
+ log.readlines.size.should == 1
101
+ end
94
102
 
103
+ logger do |log|
95
104
  primates = dallas.exhibits(:name => 'Primates')
96
105
  primates.size.should == 1
97
- primates.should_not == reptiles
98
-
99
- log.readlines.size.should == 2
106
+ log.readlines.size.should == 1
100
107
  end
108
+
109
+ primates.should_not == reptiles
101
110
  end
102
111
  end
103
112
 
@@ -109,7 +118,12 @@ describe "Strategic Eager Loading" do
109
118
  repository(ADAPTER) do
110
119
  animals = Animal.all.entries
111
120
  bear = animals.find { |a| a.name == 'Brown Bear' }
112
- bear.exhibit
121
+
122
+ logger do |log|
123
+ bear.exhibit
124
+ log.readlines.size.should == 1
125
+ end
126
+
113
127
  repository.identity_map(Animal).keys.sort.should == animal_ids
114
128
  repository.identity_map(Exhibit).keys.sort.should == exhibit_ids
115
129
  end
@@ -124,6 +138,7 @@ describe "Strategic Eager Loading" do
124
138
  animal.exhibit # load exhibit from IM
125
139
  log.readlines.should be_empty
126
140
  end
141
+
127
142
  repository.identity_map(Exhibit).keys.should == [exhibit.key]
128
143
  end
129
144
  end
@@ -198,6 +198,7 @@ if ADAPTER
198
198
  before(:all) do
199
199
  DataMapper::Repository.adapters[:alternate_paranoid] = repository(ADAPTER).adapter.dup
200
200
 
201
+ Object.send(:remove_const, :Orange) if defined?(Orange)
201
202
  class Orange
202
203
  include DataMapper::Resource
203
204
 
@@ -81,7 +81,16 @@ module ModelLoader
81
81
 
82
82
  def remove_model(klass)
83
83
  DataMapper::Resource.descendants.delete(klass)
84
- Object.module_eval { remove_const klass.to_s }
84
+ # Check to see if the model is living inside a module
85
+ klass_name = klass.to_s
86
+ if klass_name.index("::")
87
+ mod = klass_name.match(/(\S+)::/)[1]
88
+ child_class = klass_name.match(/\S+::(\S+)/)[1]
89
+
90
+ Object.const_get(mod).module_eval { remove_const child_class }
91
+ else
92
+ Object.module_eval { remove_const klass.to_s }
93
+ end
85
94
  end
86
95
  end
87
96
  end
@@ -0,0 +1,16 @@
1
+ module Content
2
+ class Dialect
3
+ include DataMapper::Resource
4
+
5
+ property :id, Serial
6
+ property :name, String
7
+ property :code, String
8
+ end
9
+
10
+ class Locale
11
+ include DataMapper::Resource
12
+
13
+ property :id, Serial
14
+ property :name, String
15
+ end
16
+ end
data/spec/spec_helper.rb CHANGED
@@ -12,7 +12,10 @@ Dir[DataMapper.root / 'spec' / 'lib' / '*.rb'].each do |file|
12
12
  end
13
13
 
14
14
  # setup mock adapters
15
- [ :default, :mock, :legacy, :west_coast, :east_coast ].each do |repository_name|
15
+ DataMapper.setup(:default, "sqlite3::memory:")
16
+ DataMapper.setup(:default2, "sqlite3::memory:")
17
+
18
+ [ :mock, :legacy, :west_coast, :east_coast ].each do |repository_name|
16
19
  DataMapper.setup(repository_name, "mock://localhost/#{repository_name}")
17
20
  end
18
21
 
@@ -73,7 +73,7 @@ describe DataMapper::Adapters::DataObjectsAdapter do
73
73
  end
74
74
 
75
75
  it "should accept a Query argument with or without options hash" do
76
- @connection.should_receive(:create_command).twice.with('SELECT "name" FROM "plupps" WHERE "name" = ? ORDER BY "id"').and_return(@command)
76
+ @connection.should_receive(:create_command).twice.with('SELECT "name" FROM "plupps" WHERE ("name" = ?) ORDER BY "id"').and_return(@command)
77
77
  @command.should_receive(:execute_reader).twice.with('my pretty plur').and_return(@reader)
78
78
  Plupp.should_receive(:repository).any_number_of_times.and_return(@repository)
79
79
  Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(@repository)
@@ -116,7 +116,7 @@ describe DataMapper::Adapters::DataObjectsAdapter do
116
116
 
117
117
  adapter = DataMapper::Adapters::DataObjectsAdapter.new(:spec, options)
118
118
  adapter.uri.should ==
119
- Addressable::URI.parse("mysql://me:mypass@davidleal.com:5000/you_can_call_me_al?socket=nosock")
119
+ DataObjects::URI.parse("mysql://me:mypass@davidleal.com:5000/you_can_call_me_al?socket=nosock")
120
120
  end
121
121
 
122
122
  it 'should transform a minimal options hash into a URI' do
@@ -126,12 +126,12 @@ describe DataMapper::Adapters::DataObjectsAdapter do
126
126
  }
127
127
 
128
128
  adapter = DataMapper::Adapters::DataObjectsAdapter.new(:spec, options)
129
- adapter.uri.should == Addressable::URI.parse("mysql:you_can_call_me_al")
129
+ adapter.uri.should == DataObjects::URI.parse("mysql:you_can_call_me_al")
130
130
  end
131
131
 
132
132
  it 'should accept the uri when no overrides exist' do
133
133
  uri = Addressable::URI.parse("protocol:///")
134
- DataMapper::Adapters::DataObjectsAdapter.new(:spec, uri).uri.should == uri
134
+ DataMapper::Adapters::DataObjectsAdapter.new(:spec, uri).uri.should == DataObjects::URI.parse(uri)
135
135
  end
136
136
  end
137
137
 
@@ -271,7 +271,7 @@ describe DataMapper::Adapters::DataObjectsAdapter do
271
271
 
272
272
  @model.should_receive(:load).with(@values, @query).and_return(@resource)
273
273
 
274
- @statement = 'SELECT "models"."property" FROM "models" INNER JOIN "parents" ON "parents"."id" = "models"."property" WHERE "models"."property" = ? ORDER BY "models"."property" DESC LIMIT 1 OFFSET 222'
274
+ @statement = 'SELECT "models"."property" FROM "models" INNER JOIN "parents" ON ("parents"."id" = "models"."property") WHERE ("models"."property" = ?) ORDER BY "models"."property" DESC LIMIT 1 OFFSET 222'
275
275
  end
276
276
 
277
277
  define_method(:do_read) do
@@ -281,7 +281,7 @@ describe DataMapper::Adapters::DataObjectsAdapter do
281
281
  end
282
282
  elsif method == :read_many
283
283
  before do
284
- @statement = 'SELECT "models"."property" FROM "models" INNER JOIN "parents" ON "parents"."id" = "models"."property" WHERE "models"."property" = ? ORDER BY "models"."property" DESC LIMIT 111 OFFSET 222'
284
+ @statement = 'SELECT "models"."property" FROM "models" INNER JOIN "parents" ON ("parents"."id" = "models"."property") WHERE ("models"."property" = ?) ORDER BY "models"."property" DESC LIMIT 111 OFFSET 222'
285
285
  end
286
286
 
287
287
  define_method(:do_read) do
@@ -310,7 +310,7 @@ describe DataMapper::Adapters::DataObjectsAdapter do
310
310
  @bind_values << other_value
311
311
  @conditions << [ :eql, other_property, other_value ]
312
312
 
313
- @statement = %[SELECT "models"."property" FROM "models" INNER JOIN "parents" ON "parents"."id" = "models"."property" WHERE "models"."property" = ? AND "models"."other" = ? ORDER BY "models"."property" DESC LIMIT #{method == :read_one ? '1' : '111'} OFFSET 222]
313
+ @statement = %[SELECT "models"."property" FROM "models" INNER JOIN "parents" ON ("parents"."id" = "models"."property") WHERE ("models"."property" = ?) AND ("models"."other" = ?) ORDER BY "models"."property" DESC LIMIT #{method == :read_one ? '1' : '111'} OFFSET 222]
314
314
  @query.should_receive(:conditions).with(no_args).twice.and_return(@conditions)
315
315
 
316
316
  @connection.should_receive(:create_command).with(@statement).and_return(@command)
@@ -358,7 +358,7 @@ describe DataMapper::Adapters::DataObjectsAdapter do
358
358
  @conditions = [ [ :eql, @property, @bind_values[0] ] ]
359
359
  @attributes = mock('attributes', :kind_of? => true, :empty? => false, :keys => [ @property ], :values => @values)
360
360
  @query = mock('query', :kind_of? => true, :model => @model, :links => [], :conditions => @conditions, :bind_values => @bind_values)
361
- @statement = 'UPDATE "models" SET "property" = ? WHERE "property" = ?'
361
+ @statement = 'UPDATE "models" SET "property" = ? WHERE ("property" = ?)'
362
362
  end
363
363
 
364
364
  def do_update
@@ -385,7 +385,7 @@ describe DataMapper::Adapters::DataObjectsAdapter do
385
385
 
386
386
  @query.should_receive(:conditions).with(no_args).twice.and_return(@conditions)
387
387
 
388
- @statement = 'UPDATE "models" SET "property" = ? WHERE "property" = ? AND "other" = ?'
388
+ @statement = 'UPDATE "models" SET "property" = ? WHERE ("property" = ?) AND ("other" = ?)'
389
389
  @adapter.should_receive(:execute).with(@statement, *%w[ new ] + @bind_values).and_return(@result)
390
390
 
391
391
  do_update.should == 1
@@ -414,7 +414,7 @@ describe DataMapper::Adapters::DataObjectsAdapter do
414
414
  @conditions = [ [ :eql, @property, @bind_values[0] ] ]
415
415
  @query = mock('query', :kind_of? => true, :model => @model, :links => [], :conditions => @conditions, :bind_values => @bind_values)
416
416
  @resource = mock('resource', :to_query => @query)
417
- @statement = 'DELETE FROM "models" WHERE "property" = ?'
417
+ @statement = 'DELETE FROM "models" WHERE ("property" = ?)'
418
418
  end
419
419
 
420
420
  def do_delete
@@ -440,7 +440,7 @@ describe DataMapper::Adapters::DataObjectsAdapter do
440
440
 
441
441
  @query.should_receive(:conditions).with(no_args).twice.and_return(@conditions)
442
442
 
443
- @statement = 'DELETE FROM "models" WHERE "property" = ? AND "other" = ?'
443
+ @statement = 'DELETE FROM "models" WHERE ("property" = ?) AND ("other" = ?)'
444
444
  @adapter.should_receive(:execute).with(@statement, *@bind_values).and_return(@result)
445
445
 
446
446
  do_delete.should == 1