datamapper 0.2.4 → 0.2.5

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 (65) hide show
  1. data/CHANGELOG +16 -1
  2. data/README +10 -8
  3. data/environment.rb +1 -1
  4. data/example.rb +13 -5
  5. data/lib/data_mapper.rb +2 -0
  6. data/lib/data_mapper/adapters/abstract_adapter.rb +61 -0
  7. data/lib/data_mapper/adapters/data_object_adapter.rb +33 -6
  8. data/lib/data_mapper/adapters/mysql_adapter.rb +5 -0
  9. data/lib/data_mapper/adapters/postgresql_adapter.rb +12 -0
  10. data/lib/data_mapper/adapters/sql/commands/load_command.rb +6 -14
  11. data/lib/data_mapper/adapters/sql/mappings/column.rb +37 -26
  12. data/lib/data_mapper/adapters/sql/mappings/table.rb +50 -8
  13. data/lib/data_mapper/associations.rb +4 -5
  14. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +2 -2
  15. data/lib/data_mapper/associations/has_many_association.rb +40 -13
  16. data/lib/data_mapper/associations/has_n_association.rb +1 -1
  17. data/lib/data_mapper/base.rb +5 -448
  18. data/lib/data_mapper/callbacks.rb +12 -2
  19. data/lib/data_mapper/context.rb +4 -0
  20. data/lib/data_mapper/database.rb +1 -1
  21. data/lib/data_mapper/identity_map.rb +2 -2
  22. data/lib/data_mapper/persistence.rb +538 -0
  23. data/lib/data_mapper/support/active_record_impersonation.rb +21 -3
  24. data/lib/data_mapper/support/errors.rb +2 -0
  25. data/lib/data_mapper/support/serialization.rb +7 -10
  26. data/lib/data_mapper/support/struct.rb +7 -0
  27. data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +11 -4
  28. data/performance.rb +23 -10
  29. data/rakefile.rb +1 -1
  30. data/spec/active_record_impersonation_spec.rb +2 -6
  31. data/spec/acts_as_tree_spec.rb +3 -1
  32. data/spec/associations_spec.rb +40 -160
  33. data/spec/attributes_spec.rb +1 -1
  34. data/spec/base_spec.rb +41 -13
  35. data/spec/callbacks_spec.rb +32 -0
  36. data/spec/coersion_spec.rb +1 -1
  37. data/spec/column_spec.rb +22 -12
  38. data/spec/dependency_spec.rb +5 -3
  39. data/spec/embedded_value_spec.rb +33 -17
  40. data/spec/has_many_association_spec.rb +173 -0
  41. data/spec/legacy_spec.rb +2 -2
  42. data/spec/load_command_spec.rb +59 -7
  43. data/spec/models/animal.rb +6 -2
  44. data/spec/models/animals_exhibit.rb +3 -1
  45. data/spec/models/career.rb +2 -1
  46. data/spec/models/comment.rb +3 -1
  47. data/spec/models/exhibit.rb +3 -1
  48. data/spec/models/fruit.rb +3 -1
  49. data/spec/models/person.rb +10 -1
  50. data/spec/models/post.rb +3 -1
  51. data/spec/models/project.rb +3 -1
  52. data/spec/models/section.rb +3 -1
  53. data/spec/models/serializer.rb +3 -1
  54. data/spec/models/user.rb +3 -1
  55. data/spec/models/zoo.rb +3 -1
  56. data/spec/paranoia_spec.rb +3 -1
  57. data/spec/postgres_spec.rb +54 -0
  58. data/spec/save_command_spec.rb +9 -5
  59. data/spec/schema_spec.rb +0 -91
  60. data/spec/single_table_inheritance_spec.rb +8 -0
  61. data/spec/table_spec.rb +46 -0
  62. data/spec/validates_uniqueness_of_spec.rb +19 -1
  63. metadata +8 -10
  64. data/lib/data_mapper/associations/has_one_association.rb +0 -77
  65. data/plugins/dataobjects/do_rb +0 -0
@@ -35,6 +35,18 @@ module DataMapper
35
35
  database.all(self, options)
36
36
  end
37
37
 
38
+ def each(options = {}, &b)
39
+ raise ArgumentError.new(":offset is not supported with the #each method") if options.has_key?(:offset)
40
+
41
+ offset = 0
42
+ limit = options[:limit] || (self::const_defined?('DEFAULT_LIMIT') ? self::DEFAULT_LIMIT : 500)
43
+
44
+ until (results = all(options.merge(:limit => limit, :offset => offset))).empty?
45
+ results.each(&b)
46
+ offset += limit
47
+ end
48
+ end
49
+
38
50
  def first(*args)
39
51
  database.first(self, *args)
40
52
  end
@@ -63,8 +75,13 @@ module DataMapper
63
75
  DataMapper::database.query(*args)
64
76
  end
65
77
 
66
- def [](id_or_hash)
67
- first(id_or_hash)
78
+ def get(*keys)
79
+ database.get(self, keys)
80
+ end
81
+
82
+ def [](*keys)
83
+ raise ArgumentError.new('Hash is not a valid key') if keys.size == 1 && keys.first.is_a?(Hash)
84
+ database.get(self, keys)
68
85
  end
69
86
 
70
87
  def create(attributes)
@@ -75,7 +92,8 @@ module DataMapper
75
92
 
76
93
  def create!(attributes)
77
94
  instance = self.new(attributes)
78
- raise InvalidRecord.new(instance) unless instance.save
95
+ instance.save
96
+ raise InvalidRecord.new(instance) if instance.new_record?
79
97
  instance
80
98
  end
81
99
  end
@@ -2,4 +2,6 @@ module DataMapper
2
2
 
3
3
  class InvalidRecord < StandardError; end
4
4
 
5
+ class MaterializationError < StandardError; end
6
+
5
7
  end
@@ -16,7 +16,7 @@ module DataMapper
16
16
  out.map(nil, to_yaml_style ) do |map|
17
17
  database_context.table(self).columns.each do |column|
18
18
  lazy_load!(column.name) if column.lazy?
19
- value = instance_variable_get(column.instance_variable_name)
19
+ value = get_value_for_column(column)
20
20
  map.add(column.to_s, value.is_a?(Class) ? value.to_s : value)
21
21
  end
22
22
  (self.instance_variable_get("@yaml_added") || []).each do |k,v|
@@ -48,9 +48,9 @@ module DataMapper
48
48
 
49
49
  table.columns.each do |column|
50
50
  next if column.key?
51
- value = send(column.name)
51
+ value = get_value_for_column(column)
52
52
  node = root.add_element(column.to_s)
53
- node << REXML::Text.new(copy_frozen_value(value).to_s) unless value.nil?
53
+ node << REXML::Text.new(value.to_s) unless value.nil?
54
54
  end
55
55
 
56
56
  doc
@@ -62,20 +62,17 @@ module DataMapper
62
62
  result = '{ '
63
63
 
64
64
  result << table.columns.map do |column|
65
- "#{column.name.to_json}: #{copy_frozen_value(send(column.name)).to_json(*a)}"
65
+ "#{column.name.to_json}: #{get_value_for_column(column).to_json(*a)}"
66
66
  end.join(', ')
67
67
 
68
68
  result << ' }'
69
69
  result
70
70
  end
71
71
 
72
- def copy_frozen_value(value)
73
- case value
74
- when Date, DateTime, Time, String then value.dup
75
- when Fixnum, Class then value
76
- else value
77
- end
72
+ def get_value_for_column(column)
73
+ send(column.type == :boolean ? column.name.to_s.ensure_ends_with('?') : column.name)
78
74
  end
75
+
79
76
  end
80
77
  end # module Support
81
78
  end # module DataMapper
@@ -0,0 +1,7 @@
1
+ class Struct
2
+ def attributes
3
+ h = {}
4
+ each_pair { |k,v| h[k] = v }
5
+ h
6
+ end
7
+ end
@@ -20,9 +20,16 @@ module Validatable
20
20
  { self.attribute.like => value }
21
21
  end
22
22
 
23
- if scope
24
- scope_value = instance.send(scope)
25
- finder_options.merge! scope => scope_value
23
+ if scope
24
+ if scope.kind_of?(Array) # if scope is larger than just one property, check them all
25
+ scope.each do |scope_property|
26
+ scope_value = instance.send(scope_property)
27
+ finder_options.merge! scope_property => scope_value
28
+ end
29
+ else
30
+ scope_value = instance.send(scope)
31
+ finder_options.merge! scope => scope_value
32
+ end
26
33
  end
27
34
 
28
35
  finder_options.merge!({ instance.database_context.table(instance.class).key.name.not => instance.key }) unless instance.new_record?
@@ -30,4 +37,4 @@ module Validatable
30
37
  end
31
38
  end
32
39
 
33
- end
40
+ end
data/performance.rb CHANGED
@@ -43,11 +43,25 @@ class DMPerson < DataMapper::Base #:nodoc:
43
43
  property :name, :string
44
44
  property :age, :integer
45
45
  property :occupation, :string
46
+ property :type, :class
46
47
  property :notes, :text
47
- property :street, :string
48
- property :city, :string
49
- property :state, :string, :size => 2
50
- property :zip_code, :string, :size => 10
48
+ property :date_of_birth, :date
49
+
50
+ embed :address, :prefix => true do
51
+ property :street, :string
52
+ property :city, :string
53
+ property :state, :string, :size => 2
54
+ property :zip_code, :string, :size => 10
55
+
56
+ def city_state_zip_code
57
+ "#{city}, #{state} #{zip_code}"
58
+ end
59
+
60
+ def to_s
61
+ "#{street}\n#{city_state_zip_code}"
62
+ end
63
+ end
64
+
51
65
  end
52
66
 
53
67
  class Exhibit < DataMapper::Base #:nodoc:
@@ -115,12 +129,12 @@ Benchmark::send(ENV['BM'] || :bmbm, 40) do |x|
115
129
  end
116
130
 
117
131
  x.report('DataMapper:conditions:short') do
118
- N.times { Zoo[:name => 'Galveston'].name }
132
+ N.times { Zoo.first(:name => 'Galveston').name }
119
133
  end
120
134
 
121
135
  x.report('DataMapper:conditions:short:in-session') do
122
136
  database do
123
- N.times { Zoo[:name => 'Galveston'].name }
137
+ N.times { Zoo.first(:name => 'Galveston').name }
124
138
  end
125
139
  end
126
140
 
@@ -241,8 +255,8 @@ Benchmark::send(ENV['BM'] || :bmbm, 40) do |x|
241
255
  #{person.name} (#{person.age})
242
256
  #{person.occupation}
243
257
 
244
- #{person.street}
245
- #{person.city}, #{person.state} #{person.zip_code}
258
+ #{person.address_street}
259
+ #{person.address_city}, #{person.address_state} #{person.address_zip_code}
246
260
 
247
261
  #{person.notes}
248
262
  VCARD
@@ -257,8 +271,7 @@ Benchmark::send(ENV['BM'] || :bmbm, 40) do |x|
257
271
  #{person.name} (#{person.age})
258
272
  #{person.occupation}
259
273
 
260
- #{person.street}
261
- #{person.city}, #{person.state} #{person.zip_code}
274
+ #{person.address}
262
275
 
263
276
  #{person.notes}
264
277
  VCARD
data/rakefile.rb CHANGED
@@ -38,7 +38,7 @@ namespace :dm do
38
38
 
39
39
  end
40
40
 
41
- PACKAGE_VERSION = '0.2.4'
41
+ PACKAGE_VERSION = '0.2.5'
42
42
 
43
43
  PACKAGE_FILES = FileList[
44
44
  'README',
@@ -23,7 +23,7 @@ describe DataMapper::Support::ActiveRecordImpersonation do
23
23
  end
24
24
 
25
25
  it 'should reload its attributes' do
26
- frog = Animal[:name => 'Frog']
26
+ frog = Animal.first(:name => 'Frog')
27
27
  frog.name = 'MegaFrog'
28
28
  frog.name.should == 'MegaFrog'
29
29
  frog.reload!
@@ -43,7 +43,7 @@ describe DataMapper::Support::ActiveRecordImpersonation do
43
43
  capybara = Animal.create(:name => 'Capybara')
44
44
  count = Animal.count
45
45
  capybara.destroy!
46
- Animal[:name => 'Capybara'].should be_nil
46
+ Animal.first(:name => 'Capybara').should be_nil
47
47
  Animal.count.should == count - 1
48
48
  end
49
49
  end
@@ -120,10 +120,6 @@ describe DataMapper::Support::ActiveRecordImpersonation do
120
120
  Animal[1].id.should == 1
121
121
  end
122
122
 
123
- it 'should retrieve the indexed element using a hash condition' do
124
- Animal[:name => 'Frog'].name.should == 'Frog'
125
- end
126
-
127
123
  it 'should create a new record' do
128
124
  count = Animal.count
129
125
  capybara = Animal.create(:name => 'Capybara')
@@ -3,7 +3,9 @@ require File.dirname(__FILE__) + "/spec_helper"
3
3
  describe('A tree') do
4
4
 
5
5
  before(:all) do
6
- class Node < DataMapper::Base
6
+ class Node
7
+ include DataMapper::Persistence
8
+
7
9
  property :name, :string
8
10
 
9
11
  belongs_to :parent, :class => 'Node'
@@ -6,7 +6,7 @@ describe DataMapper::Associations::BelongsToAssociation do
6
6
  end
7
7
 
8
8
  before(:each) do
9
- @aviary = Exhibit[:name => 'Monkey Mayhem']
9
+ @aviary = Exhibit.first(:name => 'Monkey Mayhem')
10
10
  end
11
11
 
12
12
  it 'has a zoo association' do
@@ -71,160 +71,6 @@ describe DataMapper::Associations::BelongsToAssociation do
71
71
  end
72
72
  end
73
73
 
74
- describe DataMapper::Associations::HasManyAssociation do
75
-
76
- before(:all) do
77
- fixtures(:zoos)
78
- fixtures(:exhibits)
79
- end
80
-
81
- before(:each) do
82
- @zoo = Zoo.new(:name => "ZOO")
83
- @zoo.save
84
- end
85
-
86
- after(:each) do
87
- @zoo.destroy!
88
- end
89
-
90
- it "should return an empty Enumerable for new objects" do
91
- project = Project.new
92
- project.sections.should be_a_kind_of(Enumerable)
93
- project.sections.should be_empty
94
- end
95
-
96
- it "should display correctly when inspected" do
97
- Zoo.first(:name => 'Dallas').exhibits.inspect.should match(/\#\<Exhibit\:0x.{7}/)
98
- end
99
-
100
- it 'should lazily-load the association when Enumerable methods are called' do
101
- database do |db|
102
- san_diego = Zoo[:name => 'San Diego']
103
- san_diego.exhibits.size.should == 2
104
- san_diego.exhibits.should include(Exhibit[:name => 'Monkey Mayhem'])
105
- end
106
- end
107
-
108
- it 'should eager-load associations for an entire set' do
109
- database do
110
- zoos = Zoo.all
111
- zoos.each do |zoo|
112
- zoo.exhibits.each do |exhibit|
113
- exhibit.zoo.should == zoo
114
- end
115
- end
116
- end
117
- end
118
-
119
- it "should be dirty even when clean objects are associated" do
120
- zoo = Zoo[:name => 'New York']
121
- zoo.exhibits << Exhibit.first
122
- zoo.should be_dirty
123
- end
124
-
125
- it "should proxy associations on the associated type" do
126
- Zoo[:name => 'Miami'].exhibits.animals.size.should == 1
127
- end
128
-
129
- it "should have a valid zoo setup for testing" do
130
- @zoo.should be_valid
131
- @zoo.should_not be_a_new_record
132
- @zoo.id.should_not be_nil
133
- end
134
-
135
- it "should generate the SQL for a join statement" do
136
- exhibits_association = database(:mock).schema[Zoo].associations.find { |a| a.name == :exhibits }
137
-
138
- exhibits_association.to_sql.should == <<-EOS.compress_lines
139
- JOIN `exhibits` ON `exhibits`.`zoo_id` = `zoos`.`id`
140
- EOS
141
- end
142
-
143
- it "should add an item to an association" do
144
- bear = Exhibit.new( :name => "Bear")
145
- @zoo.exhibits << bear
146
- @zoo.exhibits.should include(bear)
147
- end
148
-
149
- it "should build a new item" do
150
- bear = @zoo.exhibits.build( :name => "Bear" )
151
- bear.should be_kind_of(Exhibit)
152
- @zoo.exhibits.should include(bear)
153
- end
154
-
155
- it "should not save the item when building" do
156
- bear = @zoo.exhibits.build( :name => "Bear" )
157
- bear.should be_new_record
158
- end
159
-
160
- it "should create a new item" do
161
- bear = @zoo.exhibits.create( :name => "Bear" )
162
- bear.should be_kind_of(Exhibit)
163
- @zoo.exhibits.should include(bear)
164
- end
165
-
166
- it "should save the item when creating" do
167
- bear = @zoo.exhibits.create( :name => "Bear" )
168
- bear.should_not be_new_record
169
- end
170
-
171
- it "should set the association to a saved target when added with <<" do
172
- pirahna = Exhibit.new(:name => "Pirahna")
173
- pirahna.zoo_id.should be_nil
174
-
175
- @zoo.exhibits << pirahna
176
- pirahna.zoo.should == @zoo
177
- end
178
-
179
- it "should set the association to a non-saved target when added with <<" do
180
- zoo = Zoo.new(:name => "My Zoo")
181
- kangaroo = Exhibit.new(:name => "Kangaroo")
182
- zoo.exhibits << kangaroo
183
- kangaroo.zoo.should == zoo
184
- end
185
-
186
- it "should set the id of the exhibit when the associated zoo is saved" do
187
- snake = Exhibit.new(:name => "Snake")
188
- @zoo.exhibits << snake
189
- @zoo.save
190
- @zoo.id.should == snake.zoo_id
191
- end
192
-
193
- it "should set the id of an already saved exibit if it's added to a different zoo" do
194
- beaver = Exhibit.new(:name => "Beaver")
195
- beaver.save
196
- beaver.should_not be_a_new_record
197
- @zoo.exhibits << beaver
198
- @zoo.save
199
- beaver.zoo.should == @zoo
200
- beaver.zoo_id.should == @zoo.id
201
- end
202
-
203
- it "should set the size of the assocation" do
204
- @zoo.exhibits << Exhibit.new(:name => "anonymous")
205
- @zoo.exhibits.size.should == 1
206
- end
207
-
208
- it "should give the association when an inspect is done on it" do
209
- whale = Exhibit.new(:name => "Whale")
210
- @zoo.exhibits << whale
211
- @zoo.exhibits.should_not == "nil"
212
- @zoo.exhibits.inspect.should_not be_nil
213
- end
214
- end
215
-
216
- describe DataMapper::Associations::HasOneAssociation do
217
-
218
- it "should generate the SQL for a join statement" do
219
- fruit_association = database(:mock).schema[Animal].associations.find { |a| a.name == :favourite_fruit }
220
-
221
- fruit_association.to_sql.should == <<-EOS.compress_lines
222
- JOIN `fruit` ON `fruit`.`devourer_id` = `animals`.`id`
223
- EOS
224
- end
225
-
226
- end
227
-
228
74
  describe DataMapper::Associations::HasAndBelongsToManyAssociation do
229
75
 
230
76
  before(:all) do
@@ -233,7 +79,7 @@ describe DataMapper::Associations::HasAndBelongsToManyAssociation do
233
79
  end
234
80
 
235
81
  before(:each) do
236
- @amazonia = Exhibit[:name => 'Amazonia']
82
+ @amazonia = Exhibit.first :name => 'Amazonia'
237
83
  end
238
84
 
239
85
  it "should generate the SQL for a join statement" do
@@ -247,9 +93,9 @@ describe DataMapper::Associations::HasAndBelongsToManyAssociation do
247
93
 
248
94
  it "should load associations" do
249
95
  database do
250
- froggy = Animal[:name => 'Frog']
96
+ froggy = Animal.first(:name => 'Frog')
251
97
  froggy.exhibits.size.should == 1
252
- froggy.exhibits.entries.first.should == Exhibit[:name => 'Amazonia']
98
+ froggy.exhibits.entries.first.should == Exhibit.first(:name => 'Amazonia')
253
99
  end
254
100
  end
255
101
 
@@ -277,6 +123,18 @@ describe DataMapper::Associations::HasAndBelongsToManyAssociation do
277
123
  @amazonia.reload
278
124
  end
279
125
 
126
+ it "should allow association of additional objects (CLEAN)" do
127
+ pending "http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/92"
128
+ @amazonia.should_not be_dirty
129
+
130
+ animal = Animal[2]
131
+ animal.should_not be_dirty
132
+
133
+ @amazonia.animals << animal
134
+ @amazonia.animals.size.should == 2
135
+ @amazonia.reload
136
+ end
137
+
280
138
  it 'should allow you to fill and clear an association' do
281
139
  marcy = Exhibit.create(:name => 'marcy')
282
140
 
@@ -334,12 +192,13 @@ describe DataMapper::Associations::HasAndBelongsToManyAssociation do
334
192
  end
335
193
 
336
194
  it "Should handle setting complementary associations" do
195
+ pending("some borkage here")
337
196
  u1 = User.create(:name => "u1")
338
197
  u1.comments.should be_empty
339
198
 
340
199
  c1 = Comment.create(:comment => "c", :author => u1)
341
- p u1
342
- p c1
200
+ # p u1
201
+ # p c1
343
202
 
344
203
  u1.comments.should_not be_empty
345
204
  u1.comments.should include(c1)
@@ -349,4 +208,25 @@ describe DataMapper::Associations::HasAndBelongsToManyAssociation do
349
208
  u1.comments.should include(c1)
350
209
  end
351
210
 
211
+ it "should raise an error when attempting to associate an object not of the correct type (assuming added model doesn't inherit from the correct type)" do
212
+ pending("see: http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/91")
213
+ @amazonia.animals.should_not be_empty
214
+ chuck = Person.new(:name => "Chuck")
215
+
216
+ ## InvalidRecord isn't the error we should use here....needs to be changed
217
+ lambda { @amazonia.animals << chuck }.should raise_error(WrongType)
218
+
219
+ end
220
+
221
+ it "should associate an object which has inherited from the correct type into an association" do
222
+ pending("see: http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/91")
223
+ programmer = Career.first(:name => 'Programmer')
224
+ programmer.followers.should_not be_empty
225
+
226
+ sales_person = SalesPerson.new(:name => 'Chuck')
227
+
228
+ lambda { programmer.followers << sales_person }.should_not raise_error(WrongType)
229
+
230
+ end
231
+
352
232
  end