dm-core 0.9.2 → 0.9.3

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 (84) hide show
  1. data/.autotest +26 -0
  2. data/{CHANGELOG → History.txt} +78 -77
  3. data/Manifest.txt +123 -0
  4. data/{README → README.txt} +0 -0
  5. data/Rakefile +29 -0
  6. data/SPECS +63 -0
  7. data/TODO +1 -0
  8. data/lib/dm-core.rb +6 -1
  9. data/lib/dm-core/adapters/data_objects_adapter.rb +29 -32
  10. data/lib/dm-core/adapters/mysql_adapter.rb +1 -1
  11. data/lib/dm-core/adapters/postgres_adapter.rb +1 -1
  12. data/lib/dm-core/adapters/sqlite3_adapter.rb +2 -2
  13. data/lib/dm-core/associations.rb +26 -0
  14. data/lib/dm-core/associations/many_to_many.rb +34 -25
  15. data/lib/dm-core/associations/many_to_one.rb +4 -4
  16. data/lib/dm-core/associations/one_to_many.rb +48 -13
  17. data/lib/dm-core/associations/one_to_one.rb +4 -4
  18. data/lib/dm-core/associations/relationship.rb +144 -42
  19. data/lib/dm-core/associations/relationship_chain.rb +31 -24
  20. data/lib/dm-core/auto_migrations.rb +0 -4
  21. data/lib/dm-core/collection.rb +40 -7
  22. data/lib/dm-core/dependency_queue.rb +31 -0
  23. data/lib/dm-core/hook.rb +2 -2
  24. data/lib/dm-core/is.rb +2 -2
  25. data/lib/dm-core/logger.rb +10 -10
  26. data/lib/dm-core/model.rb +94 -41
  27. data/lib/dm-core/property.rb +72 -41
  28. data/lib/dm-core/property_set.rb +8 -14
  29. data/lib/dm-core/query.rb +34 -9
  30. data/lib/dm-core/repository.rb +0 -0
  31. data/lib/dm-core/resource.rb +13 -13
  32. data/lib/dm-core/scope.rb +25 -2
  33. data/lib/dm-core/type.rb +3 -3
  34. data/lib/dm-core/types/discriminator.rb +10 -8
  35. data/lib/dm-core/types/object.rb +4 -0
  36. data/lib/dm-core/types/paranoid_boolean.rb +15 -4
  37. data/lib/dm-core/types/paranoid_datetime.rb +15 -4
  38. data/lib/dm-core/version.rb +3 -0
  39. data/script/all +5 -0
  40. data/script/performance.rb +191 -0
  41. data/script/profile.rb +86 -0
  42. data/spec/integration/association_spec.rb +288 -204
  43. data/spec/integration/association_through_spec.rb +9 -3
  44. data/spec/integration/associations/many_to_many_spec.rb +97 -31
  45. data/spec/integration/associations/many_to_one_spec.rb +41 -6
  46. data/spec/integration/associations/one_to_many_spec.rb +18 -2
  47. data/spec/integration/auto_migrations_spec.rb +0 -0
  48. data/spec/integration/collection_spec.rb +89 -42
  49. data/spec/integration/dependency_queue_spec.rb +58 -0
  50. data/spec/integration/model_spec.rb +67 -8
  51. data/spec/integration/postgres_adapter_spec.rb +19 -20
  52. data/spec/integration/property_spec.rb +17 -8
  53. data/spec/integration/query_spec.rb +273 -191
  54. data/spec/integration/resource_spec.rb +108 -10
  55. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  56. data/spec/integration/transaction_spec.rb +3 -3
  57. data/spec/integration/type_spec.rb +121 -0
  58. data/spec/lib/logging_helper.rb +18 -0
  59. data/spec/lib/model_loader.rb +91 -0
  60. data/spec/lib/publicize_methods.rb +28 -0
  61. data/spec/models/vehicles.rb +34 -0
  62. data/spec/models/zoo.rb +48 -0
  63. data/spec/spec.opts +3 -0
  64. data/spec/spec_helper.rb +25 -62
  65. data/spec/unit/adapters/data_objects_adapter_spec.rb +1 -0
  66. data/spec/unit/associations/many_to_many_spec.rb +3 -0
  67. data/spec/unit/associations/many_to_one_spec.rb +9 -1
  68. data/spec/unit/associations/one_to_many_spec.rb +12 -4
  69. data/spec/unit/associations/relationship_spec.rb +19 -15
  70. data/spec/unit/associations_spec.rb +37 -0
  71. data/spec/unit/collection_spec.rb +8 -0
  72. data/spec/unit/data_mapper_spec.rb +14 -0
  73. data/spec/unit/model_spec.rb +2 -2
  74. data/spec/unit/property_set_spec.rb +0 -13
  75. data/spec/unit/property_spec.rb +92 -21
  76. data/spec/unit/query_spec.rb +49 -4
  77. data/spec/unit/resource_spec.rb +122 -60
  78. data/spec/unit/scope_spec.rb +11 -0
  79. data/tasks/ci.rb +68 -0
  80. data/tasks/dm.rb +63 -0
  81. data/tasks/doc.rb +20 -0
  82. data/tasks/hoe.rb +38 -0
  83. data/tasks/install.rb +20 -0
  84. metadata +63 -22
@@ -1,5 +1,103 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
2
 
3
+ # ------------------------------------------------------------
4
+ # ----- Read SPECS for information about how to read -----
5
+ # ----- and contribute to the DataMapper specs. -----
6
+ # ------------------------------------------------------------
7
+
8
+ if ADAPTER
9
+ describe "DataMapper::Resource with #{ADAPTER}" do
10
+
11
+ load_models_for_metaphor :zoo
12
+
13
+ before(:each) do
14
+ DataMapper.auto_migrate!(ADAPTER)
15
+ @zoo = Zoo.new(:name => "San Francisco")
16
+ repository(ADAPTER) { @zoo.save }
17
+ end
18
+
19
+ # --- Move somewhere ----
20
+ it "should be able to destroy objects" do
21
+ lambda { @zoo.destroy.should be_true }.should_not raise_error
22
+ end
23
+
24
+ describe '#attribute_get' do
25
+ it 'should provide #attribute_get' do
26
+ Zoo.new.should respond_to(:attribute_get)
27
+ end
28
+
29
+ it 'should delegate to Property#get' do
30
+ Zoo.properties[:name].should_receive(:get).with(zoo = Zoo.new)
31
+ zoo.name
32
+ end
33
+
34
+ it "should return Property#get's return value" do
35
+ Zoo.properties[:name].should_receive(:get).and_return("San Francisco")
36
+ Zoo.new.name.should == "San Francisco"
37
+ end
38
+ end
39
+
40
+ describe '#attribute_set' do
41
+ it "should provide #attribute_set" do
42
+ Zoo.new.should respond_to(:attribute_set)
43
+ end
44
+
45
+ it 'should delegate to Property#set' do
46
+ Zoo.properties[:name].should_receive(:set).with(zoo = Zoo.new, "San Francisco")
47
+ zoo.name = "San Francisco"
48
+ end
49
+ end
50
+
51
+ describe '#eql?' do
52
+ it "should return true if the objects are the same instances"
53
+ it "should return false if the other object is not an instance of the same model"
54
+ it "should return false if the other object is a different class"
55
+ it "should return true if the repositories are the same and the primary key is the same"
56
+ it "should return true if all the properties are the same"
57
+ it "should return false if any of the properties are different"
58
+ end
59
+
60
+ describe '#id' do
61
+ it "should be awesome"
62
+ end
63
+
64
+ describe '#inspect' do
65
+ it "should return a string representing the object"
66
+ end
67
+
68
+ describe '#key' do
69
+ it "should be awesome"
70
+ end
71
+
72
+ describe '#pretty_print' do
73
+ it "should display a pretty version of inspect"
74
+ end
75
+
76
+ describe '#save' do
77
+
78
+ describe 'with a new resource' do
79
+ it 'should set defaults before create'
80
+ it 'should create when dirty'
81
+ it 'should create when non-dirty, and it has a serial key'
82
+ end
83
+
84
+ describe 'with an existing resource' do
85
+ it 'should update'
86
+ end
87
+
88
+ end
89
+
90
+ describe '#repository' do
91
+ it "should return the repository associated with the object if there is one"
92
+ it "should return the repository associated with the model if the object doesn't have one"
93
+ end
94
+ end
95
+ end
96
+
97
+
98
+
99
+
100
+ # ---------- Old specs... BOOOOOOOOOO ---------------
3
101
  if ADAPTER
4
102
  class Orange
5
103
  include DataMapper::Resource
@@ -133,13 +231,13 @@ if ADAPTER
133
231
 
134
232
  it "should be able to overwrite Resource#to_s" do
135
233
  repository(ADAPTER) do
136
- ted = FortunePig.create!(:name => "Ted")
234
+ ted = FortunePig.create(:name => "Ted")
137
235
  FortunePig.get!(ted.id).to_s.should == 'Ted'
138
236
  end
139
237
  end
140
238
 
141
239
  it "should be able to destroy objects" do
142
- apple = Apple.create!(:color => 'Green')
240
+ apple = Apple.create(:color => 'Green')
143
241
  lambda do
144
242
  apple.destroy.should be_true
145
243
  end.should_not raise_error
@@ -250,14 +348,14 @@ if ADAPTER
250
348
  Geek.auto_migrate!(ADAPTER)
251
349
 
252
350
  repository(ADAPTER) do
253
- Male.create!(:name => 'John Dorian')
254
- Bully.create!(:name => 'Bob', :iq => 69)
255
- Geek.create!(:name => 'Steve', :awkward => false, :iq => 132)
256
- Geek.create!(:name => 'Bill', :iq => 150)
257
- Bully.create!(:name => 'Johnson')
258
- Mugger.create!(:name => 'Frank')
259
- Maniac.create!(:name => 'William')
260
- Psycho.create!(:name => 'Norman')
351
+ Male.create(:name => 'John Dorian')
352
+ Bully.create(:name => 'Bob', :iq => 69)
353
+ Geek.create(:name => 'Steve', :awkward => false, :iq => 132)
354
+ Geek.create(:name => 'Bill', :iq => 150)
355
+ Bully.create(:name => 'Johnson')
356
+ Mugger.create(:name => 'Frank')
357
+ Maniac.create(:name => 'William')
358
+ Psycho.create(:name => 'Norman')
261
359
  end
262
360
 
263
361
  Flanimal.auto_migrate!(ADAPTER)
@@ -0,0 +1,138 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe "Strategic Eager Loading" do
4
+ include LoggingHelper
5
+
6
+ before :all do
7
+ class Zoo
8
+ include DataMapper::Resource
9
+ def self.default_repository_name; ADAPTER end
10
+
11
+ property :id, Serial
12
+ property :name, String
13
+
14
+ has n, :exhibits
15
+ end
16
+
17
+ class Exhibit
18
+ include DataMapper::Resource
19
+ def self.default_repository_name; ADAPTER end
20
+
21
+ property :id, Serial
22
+ property :name, String
23
+ property :zoo_id, Integer
24
+
25
+ belongs_to :zoo
26
+ has n, :animals
27
+ end
28
+
29
+ class Animal
30
+ include DataMapper::Resource
31
+ def self.default_repository_name; ADAPTER end
32
+
33
+ property :id, Serial
34
+ property :name, String
35
+ property :exhibit_id, Integer
36
+
37
+ belongs_to :exhibit
38
+ end
39
+
40
+ [Zoo, Exhibit, Animal].each { |k| k.auto_migrate!(ADAPTER) }
41
+
42
+ repository(ADAPTER) do
43
+ Zoo.create(:name => "Dallas Zoo")
44
+ Exhibit.create(:name => "Primates", :zoo_id => 1)
45
+ Animal.create(:name => "Chimpanzee", :exhibit_id => 1)
46
+ Animal.create(:name => "Orangutan", :exhibit_id => 1)
47
+
48
+ Zoo.create(:name => "San Diego")
49
+ Exhibit.create(:name => "Aviary", :zoo_id => 2)
50
+ Exhibit.create(:name => "Insectorium", :zoo_id => 2)
51
+ Exhibit.create(:name => "Bears", :zoo_id => 2)
52
+ Animal.create(:name => "Bald Eagle", :exhibit_id => 2)
53
+ Animal.create(:name => "Parakeet", :exhibit_id => 2)
54
+ Animal.create(:name => "Roach", :exhibit_id => 3)
55
+ Animal.create(:name => "Brown Bear", :exhibit_id => 4)
56
+ end
57
+ end
58
+
59
+ it "should eager load children" do
60
+ zoo_ids = Zoo.all.map { |z| z.key }
61
+ exhibit_ids = Exhibit.all.map { |e| e.key }
62
+
63
+ repository(ADAPTER) do
64
+ zoos = Zoo.all.entries # load all zoos
65
+ dallas = zoos.find { |z| z.name == 'Dallas Zoo' }
66
+
67
+ dallas.exhibits.entries # load all exhibits for zoos in identity_map
68
+ dallas.exhibits.size.should == 1
69
+ repository.identity_map(Zoo).keys.sort.should == zoo_ids
70
+ repository.identity_map(Exhibit).keys.sort.should == exhibit_ids
71
+
72
+ logger do |log|
73
+ zoos.each { |zoo| zoo.exhibits.entries } # issues no queries
74
+ log.readlines.should be_empty
75
+ end
76
+
77
+ dallas.exhibits << Exhibit.new(:name => "Reptiles")
78
+ dallas.exhibits.size.should == 2
79
+ dallas.save
80
+ end
81
+ repository(ADAPTER) do
82
+ Zoo.first.exhibits.size.should == 2
83
+ end
84
+ end
85
+
86
+ it "should not eager load children when a query is provided" do
87
+ repository(ADAPTER) do
88
+ dallas = Zoo.all.entries.find { |z| z.name == 'Dallas Zoo' }
89
+ exhibits = dallas.exhibits.entries # load all exhibits
90
+
91
+ logger do |log|
92
+ reptiles = dallas.exhibits(:name => 'Reptiles')
93
+ reptiles.size.should == 1
94
+
95
+ primates = dallas.exhibits(:name => 'Primates')
96
+ primates.size.should == 1
97
+ primates.should_not == reptiles
98
+
99
+ log.readlines.size.should == 2
100
+ end
101
+ end
102
+ end
103
+
104
+ it "should eager load parents" do
105
+ animal_ids = Animal.all.map { |a| a.key }
106
+ exhibit_ids = Exhibit.all.map { |e| e.key }.sort
107
+ exhibit_ids.pop # remove Reptile exhibit, which has no Animals
108
+
109
+ repository(ADAPTER) do
110
+ animals = Animal.all.entries
111
+ bear = animals.find { |a| a.name == 'Brown Bear' }
112
+ bear.exhibit
113
+ repository.identity_map(Animal).keys.sort.should == animal_ids
114
+ repository.identity_map(Exhibit).keys.sort.should == exhibit_ids
115
+ end
116
+ end
117
+
118
+ it "should not eager load parents when parent is in IM" do
119
+ repository(ADAPTER) do
120
+ animal = Animal.first
121
+ exhibit = Exhibit.get(1) # load exhibit into IM
122
+
123
+ logger do |log|
124
+ animal.exhibit # load exhibit from IM
125
+ log.readlines.should be_empty
126
+ end
127
+ repository.identity_map(Exhibit).keys.should == [exhibit.key]
128
+ end
129
+ end
130
+
131
+ it "should return a Collection when no children" do
132
+ Zoo.create(:name => 'Portland')
133
+
134
+ Zoo.all.each do |zoo|
135
+ zoo.exhibits.should be_kind_of(DataMapper::Collection)
136
+ end
137
+ end
138
+ end
@@ -32,7 +32,7 @@ if ADAPTERS.any?
32
32
  it "should commit changes to all involved adapters on a two phase commit" do
33
33
  DataMapper::Transaction.new(*@repositories) do
34
34
  ADAPTERS.each do |name|
35
- repository(name) { Sputnik.create!(:name => 'hepp') }
35
+ repository(name) { Sputnik.create(:name => 'hepp') }
36
36
  end
37
37
  end
38
38
 
@@ -45,7 +45,7 @@ if ADAPTERS.any?
45
45
  lambda do
46
46
  DataMapper::Transaction.new(*@repositories) do
47
47
  ADAPTERS.each do |name|
48
- repository(name) { Sputnik.create!(:name => 'hepp') }
48
+ repository(name) { Sputnik.create(:name => 'hepp') }
49
49
  end
50
50
  raise "plur"
51
51
  end
@@ -60,7 +60,7 @@ if ADAPTERS.any?
60
60
  lambda do
61
61
  DataMapper::Transaction.new(*@repositories) do |transaction|
62
62
  ADAPTERS.each do |name|
63
- repository(name) { Sputnik.create!(:name => 'hepp') }
63
+ repository(name) { Sputnik.create(:name => 'hepp') }
64
64
  end
65
65
 
66
66
  transaction.primitive_for(@repositories.last.adapter).should_receive(:prepare).and_throw(Exception.new("I am the famous test exception"))
@@ -125,6 +125,37 @@ if ADAPTER
125
125
  end
126
126
  end
127
127
 
128
+ it "should provide access to paranoid items with DateTime" do
129
+ Lemon.auto_migrate!(ADAPTER)
130
+
131
+ lemon = nil
132
+
133
+ repository(ADAPTER) do |repository|
134
+ %w(red green yellow blue).each do |color|
135
+ Lemon.create(:color => color)
136
+ end
137
+
138
+ Lemon.all.size.should == 4
139
+ Lemon.first.destroy
140
+ Lemon.all.size.should == 3
141
+ Lemon.with_deleted{Lemon.all.size.should == 1}
142
+ end
143
+ end
144
+
145
+ it "should set paranoid datetime to a date time" do
146
+ tmp = (DateTime.now - 0.5)
147
+ dt = DateTime.now
148
+ DateTime.stub!(:now).and_return(tmp)
149
+
150
+ repository(ADAPTER) do |repository|
151
+ lemon = Lemon.new
152
+ lemon.color = 'green'
153
+ lemon.save
154
+ lemon.destroy
155
+ lemon.deleted_at.should == tmp
156
+ end
157
+ end
158
+
128
159
  it "should respect paranoia with a boolean" do
129
160
  Lime.auto_migrate!(ADAPTER)
130
161
 
@@ -145,5 +176,95 @@ if ADAPTER
145
176
  Lime.get(lime.id).should be_nil
146
177
  end
147
178
  end
179
+
180
+ it "should provide access to paranoid items with Boolean" do
181
+ Lime.auto_migrate!(ADAPTER)
182
+
183
+ lemon = nil
184
+
185
+ repository(ADAPTER) do |repository|
186
+ %w(red green yellow blue).each do |color|
187
+ Lime.create(:color => color)
188
+ end
189
+
190
+ Lime.all.size.should == 4
191
+ Lime.first.destroy
192
+ Lime.all.size.should == 3
193
+ Lime.with_deleted{Lime.all.size.should == 1}
194
+ end
195
+ end
196
+
197
+ describe "paranoid types across repositories" do
198
+ before(:all) do
199
+ DataMapper::Repository.adapters[:alternate_paranoid] = repository(ADAPTER).adapter.dup
200
+
201
+ class Orange
202
+ include DataMapper::Resource
203
+
204
+ def self.default_repository_name
205
+ ADAPTER
206
+ end
207
+
208
+ property :id, Serial
209
+ property :color, String
210
+
211
+ repository(:alternate_paranoid) do
212
+ property :deleted, DataMapper::Types::ParanoidBoolean
213
+ property :deleted_at, DataMapper::Types::ParanoidDateTime
214
+ end
215
+ end
216
+
217
+ repository(:alternate_paranoid){Orange.auto_migrate!}
218
+ end
219
+
220
+ before(:each) do
221
+ %w(red orange blue green).each{|color| o = Orange.create(:color => color)}
222
+ end
223
+
224
+ after(:each) do
225
+ Orange.repository.adapter.execute("DELETE FROM oranges")
226
+ end
227
+
228
+ it "should setup the correct objects for the spec" do
229
+ repository(:alternate_paranoid){Orange.all.should have(4).items}
230
+ end
231
+
232
+ it "should allow access the the default repository" do
233
+ Orange.all.should have(4).items
234
+ end
235
+
236
+ it "should mark the objects as deleted in the alternate_paranoid repository" do
237
+ repository(:alternate_paranoid) do
238
+ Orange.first.destroy
239
+ Orange.all.should have(3).items
240
+ Orange.find_by_sql("SELECT * FROM oranges").should have(4).items
241
+ end
242
+ end
243
+
244
+ it "should mark the objects as deleted in the alternate_paranoid repository but ignore it in the #{ADAPTER} repository" do
245
+ repository(:alternate_paranoid) do
246
+ Orange.first.destroy
247
+ end
248
+ Orange.all.should have(4).items
249
+ end
250
+
251
+ it "should raise an error when trying to destroy from a repository that is not paranoid" do
252
+ lambda do
253
+ Orange.first.destroy
254
+ end.should raise_error(ArgumentError)
255
+ end
256
+
257
+ it "should set all paranoid attributes on delete" do
258
+ repository(:alternate_paranoid) do
259
+ orange = Orange.first
260
+ orange.deleted.should be_false
261
+ orange.deleted_at.should be_nil
262
+ orange.destroy
263
+
264
+ orange.deleted.should be_true
265
+ orange.deleted_at.should be_a_kind_of(DateTime)
266
+ end
267
+ end
268
+ end
148
269
  end
149
270
  end
@@ -0,0 +1,18 @@
1
+ module LoggingHelper
2
+ def logger(adapter = ADAPTER, &block)
3
+ current_adapter = DataObjects.const_get(repository(adapter).adapter.uri.scheme.capitalize)
4
+ old_logger = current_adapter.logger
5
+
6
+ log_path = File.join(SPEC_ROOT, "tmp.log")
7
+ handle = File.open(log_path, "a+")
8
+ current_adapter.logger = DataObjects::Logger.new(log_path, 0)
9
+ begin
10
+ yield(handle)
11
+ ensure
12
+ handle.truncate(0)
13
+ handle.close
14
+ current_adapter.logger = old_logger
15
+ File.delete(log_path)
16
+ end
17
+ end
18
+ end