dm-core 0.9.5 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
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