datamapper 0.2.4 → 0.2.5

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