datamapper 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/CHANGELOG +5 -1
  2. data/FAQ +96 -0
  3. data/QUICKLINKS +12 -0
  4. data/README +57 -155
  5. data/environment.rb +61 -43
  6. data/example.rb +30 -12
  7. data/lib/data_mapper.rb +6 -1
  8. data/lib/data_mapper/adapters/abstract_adapter.rb +0 -57
  9. data/lib/data_mapper/adapters/data_object_adapter.rb +203 -97
  10. data/lib/data_mapper/adapters/mysql_adapter.rb +4 -0
  11. data/lib/data_mapper/adapters/postgresql_adapter.rb +7 -1
  12. data/lib/data_mapper/adapters/sql/coersion.rb +3 -2
  13. data/lib/data_mapper/adapters/sql/commands/load_command.rb +29 -10
  14. data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +4 -0
  15. data/lib/data_mapper/adapters/sql/mappings/column.rb +13 -9
  16. data/lib/data_mapper/adapters/sql/mappings/conditions.rb +172 -0
  17. data/lib/data_mapper/adapters/sql/mappings/table.rb +43 -17
  18. data/lib/data_mapper/adapters/sqlite3_adapter.rb +9 -2
  19. data/lib/data_mapper/associations.rb +75 -3
  20. data/lib/data_mapper/associations/belongs_to_association.rb +70 -36
  21. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +195 -86
  22. data/lib/data_mapper/associations/has_many_association.rb +168 -61
  23. data/lib/data_mapper/associations/has_n_association.rb +23 -3
  24. data/lib/data_mapper/attributes.rb +73 -0
  25. data/lib/data_mapper/auto_migrations.rb +2 -6
  26. data/lib/data_mapper/base.rb +5 -9
  27. data/lib/data_mapper/database.rb +4 -3
  28. data/lib/data_mapper/embedded_value.rb +66 -30
  29. data/lib/data_mapper/identity_map.rb +1 -3
  30. data/lib/data_mapper/is/tree.rb +121 -0
  31. data/lib/data_mapper/migration.rb +155 -0
  32. data/lib/data_mapper/persistence.rb +532 -218
  33. data/lib/data_mapper/property.rb +306 -0
  34. data/lib/data_mapper/query.rb +164 -0
  35. data/lib/data_mapper/support/blank.rb +2 -2
  36. data/lib/data_mapper/support/connection_pool.rb +5 -6
  37. data/lib/data_mapper/support/enumerable.rb +3 -3
  38. data/lib/data_mapper/support/errors.rb +10 -1
  39. data/lib/data_mapper/support/inflector.rb +174 -238
  40. data/lib/data_mapper/support/object.rb +54 -0
  41. data/lib/data_mapper/support/serialization.rb +19 -1
  42. data/lib/data_mapper/support/string.rb +7 -16
  43. data/lib/data_mapper/support/symbol.rb +3 -15
  44. data/lib/data_mapper/support/typed_set.rb +68 -0
  45. data/lib/data_mapper/types/base.rb +44 -0
  46. data/lib/data_mapper/types/string.rb +34 -0
  47. data/lib/data_mapper/validations/number_validator.rb +40 -0
  48. data/lib/data_mapper/validations/string_validator.rb +20 -0
  49. data/lib/data_mapper/validations/validator.rb +13 -0
  50. data/performance.rb +26 -1
  51. data/profile_data_mapper.rb +1 -1
  52. data/rakefile.rb +42 -2
  53. data/spec/acts_as_tree_spec.rb +11 -3
  54. data/spec/adapters/data_object_adapter_spec.rb +31 -0
  55. data/spec/associations/belongs_to_association_spec.rb +98 -0
  56. data/spec/associations/has_and_belongs_to_many_association_spec.rb +377 -0
  57. data/spec/associations/has_many_association_spec.rb +337 -0
  58. data/spec/attributes_spec.rb +23 -1
  59. data/spec/auto_migrations_spec.rb +86 -29
  60. data/spec/callbacks_spec.rb +107 -0
  61. data/spec/column_spec.rb +5 -2
  62. data/spec/count_command_spec.rb +33 -1
  63. data/spec/database_spec.rb +18 -0
  64. data/spec/dependency_spec.rb +4 -2
  65. data/spec/embedded_value_spec.rb +8 -8
  66. data/spec/fixtures/people.yaml +1 -1
  67. data/spec/fixtures/projects.yaml +10 -1
  68. data/spec/fixtures/tasks.yaml +6 -0
  69. data/spec/fixtures/tasks_tasks.yaml +2 -0
  70. data/spec/fixtures/tomatoes.yaml +1 -0
  71. data/spec/is_a_tree_spec.rb +149 -0
  72. data/spec/load_command_spec.rb +71 -9
  73. data/spec/magic_columns_spec.rb +17 -2
  74. data/spec/migration_spec.rb +267 -0
  75. data/spec/models/animal.rb +1 -1
  76. data/spec/models/candidate.rb +8 -0
  77. data/spec/models/career.rb +1 -1
  78. data/spec/models/chain.rb +8 -0
  79. data/spec/models/comment.rb +1 -1
  80. data/spec/models/exhibit.rb +1 -1
  81. data/spec/models/fence.rb +7 -0
  82. data/spec/models/fruit.rb +2 -2
  83. data/spec/models/job.rb +8 -0
  84. data/spec/models/person.rb +2 -3
  85. data/spec/models/post.rb +1 -1
  86. data/spec/models/project.rb +21 -1
  87. data/spec/models/section.rb +1 -1
  88. data/spec/models/serializer.rb +1 -1
  89. data/spec/models/task.rb +9 -0
  90. data/spec/models/tomato.rb +27 -0
  91. data/spec/models/user.rb +8 -2
  92. data/spec/models/zoo.rb +2 -7
  93. data/spec/paranoia_spec.rb +1 -1
  94. data/spec/{base_spec.rb → persistence_spec.rb} +207 -18
  95. data/spec/postgres_spec.rb +48 -6
  96. data/spec/property_spec.rb +90 -9
  97. data/spec/query_spec.rb +71 -5
  98. data/spec/save_command_spec.rb +11 -0
  99. data/spec/spec_helper.rb +14 -11
  100. data/spec/support/blank_spec.rb +8 -0
  101. data/spec/support/inflector_spec.rb +41 -0
  102. data/spec/support/object_spec.rb +9 -0
  103. data/spec/{serialization_spec.rb → support/serialization_spec.rb} +1 -1
  104. data/spec/support/silence_spec.rb +15 -0
  105. data/spec/{support_spec.rb → support/string_spec.rb} +3 -3
  106. data/spec/support/struct_spec.rb +12 -0
  107. data/spec/support/typed_set_spec.rb +66 -0
  108. data/spec/table_spec.rb +3 -3
  109. data/spec/types/string.rb +81 -0
  110. data/spec/validates_uniqueness_of_spec.rb +17 -0
  111. data/spec/validations/number_validator.rb +59 -0
  112. data/spec/validations/string_validator.rb +14 -0
  113. metadata +59 -17
  114. data/do_performance.rb +0 -153
  115. data/lib/data_mapper/support/active_record_impersonation.rb +0 -103
  116. data/lib/data_mapper/support/weak_hash.rb +0 -46
  117. data/spec/active_record_impersonation_spec.rb +0 -129
  118. data/spec/associations_spec.rb +0 -232
  119. data/spec/conditions_spec.rb +0 -49
  120. data/spec/has_many_association_spec.rb +0 -173
  121. data/spec/models/animals_exhibit.rb +0 -8
@@ -1,6 +1,6 @@
1
- class Fruit #< DataMapper::Base
1
+ class Fruit #< DataMapper::Base # please do not remove this
2
2
  include DataMapper::Persistence
3
-
3
+
4
4
  set_table_name 'fruit'
5
5
  property :name, :string, :column => 'fruit_name'
6
6
 
@@ -0,0 +1,8 @@
1
+ class Job #< DataMapper::Base # please do not remove this
2
+ include DataMapper::Persistence
3
+
4
+ property :name, :string
5
+ property :hours, :days, :integer, :default => 0
6
+
7
+ has_and_belongs_to_many :candidates, :join_table => "applications_candidates"
8
+ end
@@ -1,9 +1,8 @@
1
- class Person #< DataMapper::Base
1
+ class Person #< DataMapper::Base # please do not remove this
2
2
  include DataMapper::Persistence
3
3
 
4
- property :name, :string
4
+ property [:name, :occupation], :string
5
5
  property :age, :integer
6
- property :occupation, :string
7
6
  property :type, :class
8
7
  property :notes, :text
9
8
  property :date_of_birth, :date
@@ -1,4 +1,4 @@
1
- class Post #< DataMapper::Base
1
+ class Post #< DataMapper::Base # please do not remove this
2
2
  include DataMapper::Persistence
3
3
 
4
4
  property :title, :string
@@ -1,8 +1,9 @@
1
- class Project #< DataMapper::Base
1
+ class Project #< DataMapper::Base # please do not remove this
2
2
  include DataMapper::Persistence
3
3
 
4
4
  property :title, :string
5
5
  property :description, :string
6
+ property :deleted_at, :datetime
6
7
 
7
8
  has_many :sections
8
9
 
@@ -13,9 +14,28 @@ class Project #< DataMapper::Base
13
14
  sections.map { |section| section.tickets }
14
15
  end
15
16
 
17
+
18
+ def set_us_up_the_bomb=(val)
19
+ @set_us_up_the_bomb = !val.blank?
20
+ end
21
+
22
+ def set_up_for_bomb?
23
+ @set_us_up_the_bomb
24
+ end
25
+
26
+ def wery_sneaky?
27
+ @be_wery_sneaky
28
+ end
29
+
30
+
16
31
  private
17
32
 
18
33
  def create_main_section
19
34
  sections << Section.find_or_create(:title => "Main") if sections.empty?
20
35
  end
36
+
37
+ def be_wery_sneaky=(val)
38
+ @be_wery_sneaky = val
39
+ end
40
+
21
41
  end
@@ -1,4 +1,4 @@
1
- class Section #< DataMapper::Base
1
+ class Section #< DataMapper::Base # please do not remove this
2
2
  include DataMapper::Persistence
3
3
 
4
4
  property :title, :string
@@ -1,4 +1,4 @@
1
- class Serializer #< DataMapper::Base
1
+ class Serializer #< DataMapper::Base # please do not remove this
2
2
  include DataMapper::Persistence
3
3
 
4
4
  property :content, :object, :lazy => false
@@ -0,0 +1,9 @@
1
+ class Task #< DataMapper::Base # please do not remove this
2
+ include DataMapper::Persistence
3
+
4
+ property :name, :string, :default => "No Name", :index => :unique
5
+ property :notes, :string
6
+ property :completed, :boolean, :default => false
7
+
8
+ has_and_belongs_to_many :tasks
9
+ end
@@ -0,0 +1,27 @@
1
+ class Tomato
2
+ include DataMapper::Persistence
3
+
4
+ ATTRIBUTES << :bruised
5
+
6
+ def initialize(details = nil)
7
+ super
8
+
9
+ @name = 'Ugly'
10
+ @init_run = true
11
+ @bruised = true
12
+ end
13
+
14
+ def initialized?
15
+ @init_run
16
+ end
17
+
18
+ property :name, :string
19
+
20
+ def heal!
21
+ @bruised = false
22
+ end
23
+
24
+ def bruised?
25
+ @bruised
26
+ end
27
+ end
@@ -1,6 +1,12 @@
1
- class User #< DataMapper::Base
2
- include DataMapper::Persistence
1
+ # This uses the inheritance method of declaring
2
+ # a model to boost spec coverage and ensure that the to
3
+ # ways of declaring a model are compatible within the
4
+ # same project and/or object graphs.
5
+ class User < DataMapper::Base
3
6
 
4
7
  property :name, :string
8
+ property :email, :string, :format => :email_address
5
9
  has_many :comments, :class => 'Comment', :foreign_key => 'user_id'
10
+
11
+ after_create { false }
6
12
  end
@@ -1,16 +1,11 @@
1
- class Zoo #< DataMapper::Base
1
+ class Zoo #< DataMapper::Base # please do not remove this
2
2
  include DataMapper::Persistence
3
3
 
4
- property :name, :string
4
+ property :name, :string, :nullable => false, :default => "Zoo"
5
5
  property :notes, :text
6
6
  property :updated_at, :datetime
7
7
 
8
8
  has_many :exhibits
9
- begin
10
- validates_presence_of :name
11
- rescue ArgumentError => e
12
- throw e unless e.message =~ /specify a unique key/
13
- end
14
9
 
15
10
  def name=(val)
16
11
  @name = (val == "Colorado Springs") ? "Cheyenne Mountain" : val
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + "/spec_helper"
3
3
  describe 'Paranoia' do
4
4
 
5
5
  before(:all) do
6
- class Scared
6
+ class Scared #< DataMapper::Base # please do not remove this
7
7
  include DataMapper::Persistence
8
8
 
9
9
  property :name, :string
@@ -1,6 +1,146 @@
1
1
  require File.dirname(__FILE__) + "/spec_helper"
2
2
 
3
- describe DataMapper::Persistence do
3
+ describe "DataMapper::Persistence::ConvenienceMethods::ClassMethods" do
4
+
5
+ before(:all) do
6
+ fixtures(:animals)
7
+ fixtures(:exhibits)
8
+ end
9
+
10
+ describe 'A record' do
11
+ it 'should save and return true if validations pass' do
12
+ count = Exhibit.count
13
+ bugs_and_more_bugs = Exhibit.new(:name => 'Bugs And More Bugs')
14
+ bugs_and_more_bugs.save.should be_true
15
+ Exhibit.count.should == count + 1
16
+ end
17
+
18
+ it 'should return false on save if validations fail' do
19
+ count = Exhibit.count
20
+ bugs_and_more_bugs = Exhibit.new
21
+ bugs_and_more_bugs.save.should be_false
22
+ Exhibit.count.should == count
23
+ end
24
+
25
+ it 'should reload its attributes' do
26
+ frog = Animal.first(:name => 'Frog')
27
+ frog.name = 'MegaFrog'
28
+ frog.name.should == 'MegaFrog'
29
+ frog.reload!
30
+ frog.name.should == 'Frog'
31
+ end
32
+
33
+ it "should prepare it's associations for reload" do
34
+ chippy = Animal.first(:name => 'Cup')
35
+ amazonia = Exhibit.first(:name => 'Amazonia')
36
+ amazonia.animals << chippy
37
+ amazonia.animals.should include(chippy)
38
+ amazonia.reload!
39
+ amazonia.animals.should_not include(chippy)
40
+ end
41
+
42
+ it 'should be destroyed!' do
43
+ capybara = Animal.create(:name => 'Capybara')
44
+ count = Animal.count
45
+ capybara.destroy!
46
+ Animal.first(:name => 'Capybara').should be_nil
47
+ Animal.count.should == count - 1
48
+ end
49
+ end
50
+
51
+ it 'should return the first match using find_or_create' do
52
+ count = Animal.count
53
+ frog = Animal.find_or_create(:name => 'Frog')
54
+ frog.name.should == 'Frog'
55
+ Animal.count.should == count
56
+ end
57
+
58
+ it 'should create a record if a match is not found with find_or_create' do
59
+ count = Animal.count
60
+ capybara = Animal.find_or_create(:name => 'Capybara')
61
+ capybara.name.should == 'Capybara'
62
+ Animal.count.should == count + 1
63
+ end
64
+
65
+ it 'should return all records' do
66
+ all_animals = Animal.all
67
+ all_animals.length.should == Animal.count
68
+ all_animals.each do |animal|
69
+ animal.class.should == Animal
70
+ end
71
+ end
72
+
73
+ it 'should return the first record' do
74
+ Animal.first.should == Animal.find(:first)
75
+ end
76
+
77
+ it 'should return a count of the records' do
78
+ Animal.count.should == Animal.find_by_sql("SELECT COUNT(*) FROM animals")[0]
79
+ end
80
+
81
+ it 'should delete all records' do
82
+ Animal.delete_all
83
+ Animal.count.should == 0
84
+
85
+ fixtures(:animals)
86
+ end
87
+
88
+ #it 'should be truncated' do
89
+ # Animal.truncate!
90
+ # Animal.count.should == 0
91
+ #
92
+ # fixtures(:animals)
93
+ #end
94
+
95
+ it 'should find a matching record given an id' do
96
+ Animal.find(1).name.should == 'Frog'
97
+ end
98
+
99
+ it 'should find all records' do
100
+ Animal.find(:all).length.should == Animal.count
101
+ end
102
+
103
+ it 'should find all matching records given some condition' do
104
+ Animal.find(:all, :conditions => ["name = ?", "Frog"])[0].name.should == 'Frog'
105
+ end
106
+
107
+ it 'should find the first matching record' do
108
+ Animal.find(:first).name.should == 'Frog'
109
+ end
110
+
111
+ it 'should find the first matching record given some condition' do
112
+ Animal.find(:first, :conditions => ["name = ?", "Frog"]).name.should == 'Frog'
113
+ end
114
+
115
+ it 'should select records using the supplied sql fragment' do
116
+ Animal.find_by_sql("SELECT name FROM animals WHERE name='Frog'")[0].should == 'Frog'
117
+ end
118
+
119
+ it 'should retrieve the indexed element' do
120
+ Animal[1].id.should == 1
121
+ end
122
+
123
+ it 'should create a new record' do
124
+ count = Animal.count
125
+ capybara = Animal.create(:name => 'Capybara')
126
+ capybara.name.should == 'Capybara'
127
+ Animal.count.should == count + 1
128
+ end
129
+ end
130
+
131
+
132
+ # Can't describe DataMapper::Persistence because
133
+ # rspec will include it for some crazy reason!
134
+ describe "DataMapper::Persistence" do
135
+
136
+ it "should raise ObjectNotFoundError for missing objects with the indexer finder" do
137
+ # pending "http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/111-should-raise-objectnotfounderror-on-the-indexer-finder"
138
+ lambda { Zoo[900] }.should raise_error(DataMapper::ObjectNotFoundError)
139
+ end
140
+
141
+ it "should raise IncompleteModelDefinitionError for a model with no properties" do
142
+ lambda { class IncompleteZoo; include DataMapper::Persistence; end; IncompleteZoo.new }.should raise_error(DataMapper::Persistence::IncompleteModelDefinitionError)
143
+ end
4
144
 
5
145
  it "attributes method should load all lazy-loaded values" do
6
146
  Animal.first(:name => 'Cup').attributes[:notes].should == 'I am a Cup!'
@@ -69,13 +209,14 @@ describe DataMapper::Persistence do
69
209
  p1.should == p2
70
210
  end
71
211
 
72
- it "should not be equal if attributes have changed" do
73
- p1 = Person.create(:name => 'Sam')
74
- p2 = Person[p1.id]
75
- p2.name = "Paul"
76
-
77
- p1.should_not == p2
78
- end
212
+ # This is unnecessary. Use #dirty? to check for changes.
213
+ # it "should not be equal if attributes have changed" do
214
+ # p1 = Person.create(:name => 'Sam')
215
+ # p2 = Person[p1.id]
216
+ # p2.name = "Paul"
217
+ #
218
+ # p1.should_not == p2
219
+ # end
79
220
 
80
221
  end
81
222
 
@@ -165,7 +306,7 @@ end
165
306
  describe 'Properties' do
166
307
 
167
308
  it 'should default to public method visibility' do
168
- class SoftwareEngineer
309
+ class SoftwareEngineer #< DataMapper::Base # please do not remove this
169
310
  include DataMapper::Persistence
170
311
 
171
312
  set_table_name 'people'
@@ -177,7 +318,7 @@ describe 'Properties' do
177
318
  end
178
319
 
179
320
  it 'should respect protected property options' do
180
- class SanitationEngineer
321
+ class SanitationEngineer #< DataMapper::Base # please do not remove this
181
322
  include DataMapper::Persistence
182
323
 
183
324
  set_table_name 'people'
@@ -190,7 +331,7 @@ describe 'Properties' do
190
331
  end
191
332
 
192
333
  it 'should respect private property options' do
193
- class ElectricalEngineer
334
+ class ElectricalEngineer #< DataMapper::Base # please do not remove this
194
335
  include DataMapper::Persistence
195
336
 
196
337
  set_table_name 'people'
@@ -203,7 +344,7 @@ describe 'Properties' do
203
344
  end
204
345
 
205
346
  it 'should set both reader and writer visibiliy when accessor option is passed' do
206
- class TrainEngineer
347
+ class TrainEngineer #< DataMapper::Base # please do not remove this
207
348
  include DataMapper::Persistence
208
349
 
209
350
  property :name, :string, :accessor => :private
@@ -214,7 +355,7 @@ describe 'Properties' do
214
355
  end
215
356
 
216
357
  it 'should only be listed in attributes if they have public getters' do
217
- class SalesEngineer
358
+ class SalesEngineer #< DataMapper::Base # please do not remove this
218
359
  include DataMapper::Persistence
219
360
 
220
361
  set_table_name 'people'
@@ -228,7 +369,7 @@ describe 'Properties' do
228
369
  end
229
370
 
230
371
  it 'should not allow mass assignment if private or protected' do
231
- class ChemicalEngineer
372
+ class ChemicalEngineer #< DataMapper::Base # please do not remove this
232
373
  include DataMapper::Persistence
233
374
 
234
375
  set_table_name 'people'
@@ -243,7 +384,7 @@ describe 'Properties' do
243
384
  end
244
385
 
245
386
  it 'should allow :protected to be passed as an alias for a public reader, protected writer' do
246
- class CivilEngineer
387
+ class CivilEngineer #< DataMapper::Base # please do not remove this
247
388
  include DataMapper::Persistence
248
389
 
249
390
  set_table_name 'people'
@@ -255,7 +396,7 @@ describe 'Properties' do
255
396
  end
256
397
 
257
398
  it 'should allow :private to be passed as an alias for a public reader, private writer' do
258
- class AudioEngineer
399
+ class AudioEngineer #< DataMapper::Base # please do not remove this
259
400
  include DataMapper::Persistence
260
401
 
261
402
  set_table_name 'people'
@@ -268,7 +409,7 @@ describe 'Properties' do
268
409
 
269
410
  it 'should raise an error when invalid options are passsed' do
270
411
  lambda do
271
- class JumpyCow
412
+ class JumpyCow #< DataMapper::Base # please do not remove this
272
413
  include DataMapper::Persistence
273
414
 
274
415
  set_table_name 'animals'
@@ -279,7 +420,7 @@ describe 'Properties' do
279
420
 
280
421
  it 'should raise an error when the first argument to index isnt an array' do
281
422
  lambda do
282
- class JumpyCow
423
+ class JumpyCow #< DataMapper::Base # please do not remove this
283
424
  include DataMapper::Persistence
284
425
 
285
426
  set_table_name 'animals'
@@ -287,4 +428,52 @@ describe 'Properties' do
287
428
  end
288
429
  end.should raise_error(ArgumentError)
289
430
  end
431
+
432
+ it "should return true on saving a new record" do
433
+ bob = User.new(:name => 'bob', :email => 'bob@example.com')
434
+ bob.save!.should == true
435
+ bob.destroy!
436
+ end
437
+
438
+ it "should assign to public setters" do
439
+ x = Project.new
440
+ x.attributes = { :set_us_up_the_bomb => true }
441
+ x.should be_set_up_for_bomb
442
+ end
443
+
444
+ it "should protect private setters" do
445
+ x = Project.new
446
+ x.attributes = { :be_wery_sneaky => true }
447
+ x.should_not be_wery_sneaky
448
+ end
449
+
450
+ it "should not call initialize on materialization" do
451
+ # pending "http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/113-use-allocate-to-materialize-objects"
452
+ x = Tomato.new
453
+ x.should be_initialized
454
+ x.save!
455
+ x.name.should eql('Ugly')
456
+ x.should be_bruised
457
+
458
+ x.name = 'Bob'
459
+ x.save!
460
+
461
+ x2 = Tomato.first(:name => 'Bob')
462
+ x2.should_not be_bruised
463
+ x2.heal!
464
+ x2.should == x
465
+ x2.name.should eql('Bob')
466
+ x2.should_not be_initialized
467
+
468
+ x3 = Tomato.get(x.key)
469
+ x3.should_not be_bruised
470
+ x3.heal!
471
+ x3.should == x
472
+ x3.name.should eql('Bob')
473
+ x3.should_not be_initialized
474
+ end
475
+
476
+ it "should report persistence" do
477
+ Tomato.should be_persistent
478
+ end
290
479
  end