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
@@ -74,25 +74,29 @@ module DataMapper
74
74
 
75
75
  # @api private
76
76
  def get_children(parent, options = {}, finder = :all, *args)
77
- bind_values = parent_key.get(parent)
78
- parent_values = bind_values.dup
77
+ parent_value = parent_key.get(parent)
78
+ bind_values = [ parent_value ]
79
79
 
80
80
  with_repository(child_model) do |r|
81
81
  parent_identity_map = parent.repository.identity_map(parent_model)
82
82
  child_identity_map = r.identity_map(child_model)
83
83
 
84
- query_values = parent_identity_map.keys.flatten
85
- query_values.reject! { |k| child_identity_map[[k]] }
84
+ query_values = parent_identity_map.keys
85
+ query_values.reject! { |k| child_identity_map[k] }
86
86
 
87
87
  bind_values = query_values unless query_values.empty?
88
- query = child_key.map { |k| [ k, bind_values ] }.to_hash
88
+ query = child_key.zip(bind_values.transpose).to_hash
89
89
 
90
90
  collection = child_model.send(finder, *(args.dup << @query.merge(options).merge(query)))
91
+
91
92
  return collection unless collection.kind_of?(Collection) && collection.any?
92
93
 
93
- grouped_collection = Hash.new { |h,k| h[k] = [] }
94
+ grouped_collection = {}
94
95
  collection.each do |resource|
95
- grouped_collection[get_parent(resource, parent)] << resource
96
+ child_value = child_key.get(resource)
97
+ parent_obj = parent_identity_map[child_value]
98
+ grouped_collection[parent_obj] ||= []
99
+ grouped_collection[parent_obj] << resource
96
100
  end
97
101
 
98
102
  association_accessor = "#{self.name}_association"
@@ -112,14 +116,14 @@ module DataMapper
112
116
  parents_children = Collection.new(query)
113
117
  children.each { |child| parents_children.send(:add, child) }
114
118
 
115
- if parent_key.get(parent) == parent_values
119
+ if parent_key.get(parent) == parent_value
116
120
  ret = parents_children
117
121
  else
118
122
  association.instance_variable_set(:@children, parents_children)
119
123
  end
120
124
  end
121
125
 
122
- ret || child_model.send(finder, *(args.dup << @query.merge(options).merge(child_key.zip(parent_values).to_hash)))
126
+ ret || child_model.send(finder, *(args.dup << @query.merge(options).merge(child_key.zip([ parent_value ]).to_hash)))
123
127
  end
124
128
  end
125
129
 
@@ -129,14 +133,15 @@ module DataMapper
129
133
  return nil unless child_value.nitems == child_value.size
130
134
 
131
135
  with_repository(parent || parent_model) do
132
- parent_identity_map = (parent || parent_model).repository.identity_map(parent_model)
133
- child_identity_map = child.repository.identity_map(child_model)
136
+ parent_identity_map = (parent || parent_model).repository.identity_map(parent_model.base_model)
137
+ child_identity_map = child.repository.identity_map(child_model.base_model)
134
138
 
135
139
  if parent = parent_identity_map[child_value]
136
140
  return parent
137
141
  end
138
142
 
139
- children = child_identity_map.values | [child]
143
+ children = child_identity_map.values
144
+ children << child unless child_identity_map[child.key]
140
145
 
141
146
  bind_values = children.map { |c| child_key.get(c) }.uniq
142
147
  query_values = bind_values.reject { |k| parent_identity_map[k] }
@@ -151,21 +156,20 @@ module DataMapper
151
156
  children.each do |c|
152
157
  c.send(association_accessor).instance_variable_set(:@parent, collection.get(*child_key.get(c)))
153
158
  end
154
-
155
159
  child.send(association_accessor).instance_variable_get(:@parent)
156
160
  end
157
161
  end
158
162
  end
159
163
 
160
164
  # @api private
161
- def with_repository(object = nil, &block)
165
+ def with_repository(object = nil)
162
166
  other_model = object.model == child_model ? parent_model : child_model if object.respond_to?(:model)
163
167
  other_model = object == child_model ? parent_model : child_model if object.kind_of?(DataMapper::Resource)
164
168
 
165
169
  if other_model && other_model.repository == object.repository && object.repository.name != @repository_name
166
- object.repository.scope(&block)
170
+ object.repository.scope { |block_args| yield(*block_args) }
167
171
  else
168
- repository(@repository_name, &block)
172
+ repository(@repository_name) { |block_args| yield(*block_args) }
169
173
  end
170
174
  end
171
175
 
@@ -59,7 +59,7 @@ module DataMapper
59
59
  # REPEAT: THIS IS DESTRUCTIVE
60
60
  #
61
61
  # @param Symbol repository_name the repository to be migrated
62
- def auto_migrate!(repository_name = nil)
62
+ def auto_migrate!(repository_name = self.repository_name)
63
63
  auto_migrate_down!(repository_name)
64
64
  auto_migrate_up!(repository_name)
65
65
  end
@@ -71,7 +71,8 @@ module DataMapper
71
71
  #
72
72
  # @param Symbol repository_name the repository to be migrated
73
73
  # @api private
74
- def auto_migrate_down!(repository_name = nil)
74
+ def auto_migrate_down!(repository_name = self.repository_name)
75
+ # repository_name ||= default_repository_name
75
76
  if self.superclass != Object
76
77
  self.superclass.auto_migrate!(repository_name)
77
78
  else
@@ -86,7 +87,7 @@ module DataMapper
86
87
  #
87
88
  # @param Symbol repository_name the repository to be migrated
88
89
  # @api private
89
- def auto_migrate_up!(repository_name = nil)
90
+ def auto_migrate_up!(repository_name = self.repository_name)
90
91
  if self.superclass != Object
91
92
  self.superclass.auto_migrate!(repository_name)
92
93
  else
@@ -101,7 +102,7 @@ module DataMapper
101
102
  # preserving data already in the data-store
102
103
  #
103
104
  # @param Symbol repository_name the repository to be migrated
104
- def auto_upgrade!(repository_name = nil)
105
+ def auto_upgrade!(repository_name = self.repository_name)
105
106
  repository(repository_name) do |r|
106
107
  r.adapter.upgrade_model_storage(r, self)
107
108
  end
@@ -52,8 +52,9 @@ module DataMapper
52
52
  def get(*key)
53
53
  key = model.typecast_key(key)
54
54
  if loaded?
55
- # loop over the collection to find the matching resource
56
- detect { |resource| resource.key == key }
55
+ # find indexed resource (create index first if it does not exist)
56
+ each {|r| @cache[r.key] = r } if @cache.empty?
57
+ @cache[key]
57
58
  elsif query.limit || query.offset > 0
58
59
  # current query is exclusive, find resource within the set
59
60
 
@@ -167,7 +168,7 @@ module DataMapper
167
168
  end
168
169
 
169
170
  ##
170
- # Simulates Array#at and returns the entrie at that index.
171
+ # Simulates Array#at and returns the entry at that index.
171
172
  # Also accepts negative indexes and appropriate reverses
172
173
  # the order of the query
173
174
  #
@@ -295,7 +296,7 @@ module DataMapper
295
296
  # @see Array#delete
296
297
  #
297
298
  # @api public
298
- def delete(resource, &block)
299
+ def delete(resource)
299
300
  orphan_resource(super)
300
301
  end
301
302
 
@@ -515,6 +516,7 @@ module DataMapper
515
516
 
516
517
  @query = query
517
518
  @key_properties = model.key(repository.name)
519
+ @cache = {}
518
520
 
519
521
  super()
520
522
 
@@ -533,6 +535,7 @@ module DataMapper
533
535
  def relate_resource(resource)
534
536
  return unless resource
535
537
  resource.collection = self
538
+ @cache[resource.key] = resource
536
539
  resource
537
540
  end
538
541
 
@@ -540,7 +543,8 @@ module DataMapper
540
543
  # @api private
541
544
  def orphan_resource(resource)
542
545
  return unless resource
543
- resource.collection = nil if resource.collection == self
546
+ resource.collection = nil if resource.collection.object_id == self.object_id
547
+ @cache.delete(resource.key)
544
548
  resource
545
549
  end
546
550
 
@@ -626,7 +630,7 @@ module DataMapper
626
630
 
627
631
  query.update(
628
632
  :fields => klass.properties(repository.name).defaults,
629
- :links => self.query.links + [ relationship ]
633
+ :links => [ relationship ] + self.query.links
630
634
  )
631
635
 
632
636
  klass.all(query, &block)
@@ -6,10 +6,11 @@ module DataMapper
6
6
  #
7
7
  class DependencyQueue
8
8
  def initialize
9
- @dependencies = Hash.new { |h,k| h[k] = [] }
9
+ @dependencies = {}
10
10
  end
11
11
 
12
12
  def add(class_name, &callback)
13
+ @dependencies[class_name] ||= []
13
14
  @dependencies[class_name] << callback
14
15
  resolve!
15
16
  end
@@ -5,7 +5,7 @@ module DataMapper
5
5
  class IdentityMap
6
6
  # Get a resource from the IdentityMap
7
7
  def get(key)
8
- @cache[key]
8
+ @cache[key] || (@second_level_cache && @second_level_cache.get(key))
9
9
  end
10
10
 
11
11
  alias [] get
@@ -27,11 +27,8 @@ module DataMapper
27
27
  private
28
28
 
29
29
  def initialize(second_level_cache = nil)
30
- @cache = if @second_level_cache = second_level_cache
31
- Hash.new { |h,key| h[key] = @second_level_cache.get(key) }
32
- else
33
- Hash.new
34
- end
30
+ @cache = {}
31
+ @second_level_cache = second_level_cache
35
32
  end
36
33
 
37
34
  def cache
data/lib/dm-core/model.rb CHANGED
@@ -29,15 +29,15 @@ module DataMapper
29
29
  end
30
30
 
31
31
  def self.extended(model)
32
- model.instance_variable_set(:@storage_names, Hash.new { |h,k| h[k] = repository(k).adapter.resource_naming_convention.call(model.send(:default_storage_name)) })
33
- model.instance_variable_set(:@properties, Hash.new { |h,k| h[k] = k == Repository.default_name ? PropertySet.new : h[Repository.default_name].dup })
34
- model.instance_variable_set(:@field_naming_conventions, Hash.new { |h,k| h[k] = repository(k).adapter.field_naming_convention })
32
+ model.instance_variable_set(:@storage_names, {})
33
+ model.instance_variable_set(:@properties, {})
34
+ model.instance_variable_set(:@field_naming_conventions, {})
35
35
  extra_extensions.each { |extension| model.extend(extension) }
36
36
  end
37
37
 
38
38
  def inherited(target)
39
39
  target.instance_variable_set(:@storage_names, @storage_names.dup)
40
- target.instance_variable_set(:@properties, Hash.new { |h,k| h[k] = k == Repository.default_name ? PropertySet.new : h[Repository.default_name].dup })
40
+ target.instance_variable_set(:@properties, {})
41
41
  target.instance_variable_set(:@base_model, self.base_model)
42
42
  target.instance_variable_set(:@paranoid_properties, @paranoid_properties)
43
43
  target.instance_variable_set(:@field_naming_conventions, @field_naming_conventions.dup)
@@ -58,12 +58,13 @@ module DataMapper
58
58
  end
59
59
 
60
60
  if @relationships
61
- duped_relationships = Hash.new { |h,k| h[k] = {} }
61
+ duped_relationships = {}
62
62
  @relationships.each do |repository_name,relationships|
63
63
  relationships.each do |name, relationship|
64
64
  dup = relationship.dup
65
65
  dup.instance_variable_set(:@child_model, target) if dup.instance_variable_get(:@child_model) == self
66
66
  dup.instance_variable_set(:@parent_model, target) if dup.instance_variable_get(:@parent_model) == self
67
+ duped_relationships[repository_name] ||= {}
67
68
  duped_relationships[repository_name][name] = dup
68
69
  end
69
70
  end
@@ -102,13 +103,17 @@ module DataMapper
102
103
  # if given a block, otherwise the requested repository.
103
104
  #-
104
105
  # @api public
105
- def repository(name = nil, &block)
106
+ def repository(name = nil)
106
107
  #
107
108
  # There has been a couple of different strategies here, but me (zond) and dkubb are at least
108
109
  # united in the concept of explicitness over implicitness. That is - the explicit wish of the
109
110
  # caller (+name+) should be given more priority than the implicit wish of the caller (Repository.context.last).
110
111
  #
111
- DataMapper.repository(name || repository_name, &block)
112
+ if block_given?
113
+ DataMapper.repository(name || repository_name) { |*block_args| yield(*block_args) }
114
+ else
115
+ DataMapper.repository(name || repository_name)
116
+ end
112
117
  end
113
118
 
114
119
  ##
@@ -116,7 +121,7 @@ module DataMapper
116
121
  #
117
122
  # @return <String> the storage name (IE table name, for database stores) associated with this resource in the given repository
118
123
  def storage_name(repository_name = default_repository_name)
119
- @storage_names[repository_name]
124
+ @storage_names[repository_name] ||= repository(repository_name).adapter.resource_naming_convention.call(base_model.send(:default_storage_name))
120
125
  end
121
126
 
122
127
  ##
@@ -130,9 +135,9 @@ module DataMapper
130
135
  ##
131
136
  # The field naming conventions for this resource across all repositories.
132
137
  #
133
- # @return <Hash(Symbol => String)> All available field naming conventions
134
- def field_naming_conventions
135
- @field_naming_conventions
138
+ # @return <String> The naming convention for the given repository
139
+ def field_naming_convention(repository_name = default_storage_name)
140
+ @field_naming_conventions[repository_name] ||= repository(repository_name).adapter.field_naming_convention
136
141
  end
137
142
 
138
143
  ##
@@ -148,14 +153,15 @@ module DataMapper
148
153
  create_property_getter(property)
149
154
  create_property_setter(property)
150
155
 
151
- @properties[repository_name][property.name] = property
156
+ properties(repository_name)[property.name] = property
157
+ @_valid_relations = false
152
158
 
153
159
  # Add property to the other mappings as well if this is for the default
154
160
  # repository.
155
161
  if repository_name == default_repository_name
156
162
  @properties.each_pair do |repository_name, properties|
157
163
  next if repository_name == default_repository_name
158
- properties << property
164
+ properties << property unless properties.has_property?(property.name)
159
165
  end
160
166
  end
161
167
 
@@ -168,7 +174,7 @@ module DataMapper
168
174
  context = :default if context == true
169
175
 
170
176
  Array(context).each do |item|
171
- @properties[repository_name].lazy_context(item) << name
177
+ properties(repository_name).lazy_context(item) << name
172
178
  end
173
179
  end
174
180
 
@@ -190,7 +196,25 @@ module DataMapper
190
196
  end
191
197
 
192
198
  def properties(repository_name = default_repository_name)
193
- @properties[repository_name]
199
+ # We need to check whether all relations are already set up.
200
+ # If this isn't the case, we try to reload them here
201
+ if !@_valid_relations && respond_to?(:many_to_one_relationships)
202
+ @_valid_relations = true
203
+ begin
204
+ many_to_one_relationships.each do |r|
205
+ r.child_key
206
+ end
207
+ rescue NameError
208
+ # Apparently not all relations are loaded,
209
+ # so we will try again later on
210
+ @_valid_relations = false
211
+ end
212
+ end
213
+ @properties[repository_name] ||= repository_name == Repository.default_name ? PropertySet.new : properties(Repository.default_name).dup
214
+ end
215
+
216
+ def eager_properties(repository_name = default_repository_name)
217
+ properties(repository_name).defaults
194
218
  end
195
219
 
196
220
  # @api private
@@ -207,15 +231,12 @@ module DataMapper
207
231
  end
208
232
 
209
233
  def key(repository_name = default_repository_name)
210
- @properties[repository_name].key
211
- end
212
-
213
- def inheritance_property(repository_name = default_repository_name)
214
- @properties[repository_name].inheritance_property
234
+ properties(repository_name).key
215
235
  end
216
236
 
217
- def default_order
218
- @default_order ||= key.map { |property| Query::Direction.new(property) }
237
+ def default_order(repository_name = default_repository_name)
238
+ @default_order ||= {}
239
+ @default_order[repository_name] ||= key(repository_name).map { |property| Query::Direction.new(property) }
219
240
  end
220
241
 
221
242
  def get(*key)
@@ -288,8 +309,8 @@ module DataMapper
288
309
  # TODO SPEC
289
310
  def copy(source, destination, query = {})
290
311
  repository(destination) do
291
- repository(source).read_many(query).each do |resource|
292
- self.create(resource)
312
+ repository(source).read_many(scoped_query(query)).each do |resource|
313
+ self.create(resource.attributes)
293
314
  end
294
315
  end
295
316
  end
@@ -300,7 +321,7 @@ module DataMapper
300
321
  repository = query.repository
301
322
  model = self
302
323
 
303
- if inheritance_property_index = query.inheritance_property_index(repository)
324
+ if inheritance_property_index = query.inheritance_property_index
304
325
  model = values.at(inheritance_property_index) || model
305
326
  end
306
327
 
@@ -447,8 +468,8 @@ module DataMapper
447
468
  # @api public
448
469
  #
449
470
  # TODO: move to dm-more/dm-transactions
450
- def transaction(&block)
451
- DataMapper::Transaction.new(self, &block)
471
+ def transaction
472
+ DataMapper::Transaction.new(self) { |block_args| yield(*block_args) }
452
473
  end
453
474
  end # module Transaction
454
475
 
@@ -220,6 +220,21 @@ module DataMapper
220
220
  # dm-more bundle. For more information about validations, check the
221
221
  # documentation for dm-validations.
222
222
  #
223
+ # == Default Values
224
+ # To set a default for a property, use the <tt>:default</tt> key. The
225
+ # property will be set to the value associated with that key the first time
226
+ # it is accessed, or when the resource is saved if it hasn't been set with
227
+ # another value already. This value can be a static value, such as 'hello'
228
+ # but it can also be a proc that will be evaluated when the property is read
229
+ # before its value has been set. The property is set to the return of the
230
+ # proc. The proc is passed two values, the resource the property is being set
231
+ # for and the property itself.
232
+ #
233
+ # property :display_name, String, :default => { |r, p| r.login }
234
+ #
235
+ # Word of warning. Don't try to read the value of the property you're setting
236
+ # the default for in the proc. An infinite loop will ensue.
237
+ #
223
238
  # == Embedded Values
224
239
  # As an alternative to extraneous has_one relationships, consider using an
225
240
  # EmbeddedValue.
@@ -287,7 +302,7 @@ module DataMapper
287
302
  # -
288
303
  # @api semi-public
289
304
  def field(repository_name = nil)
290
- @field || fields[repository_name || model.repository_name]
305
+ @field || @fields[repository_name] ||= self.model.field_naming_convention(repository_name).call(self)
291
306
  end
292
307
 
293
308
  def unique
@@ -372,20 +387,15 @@ module DataMapper
372
387
  #-
373
388
  # @api private
374
389
  def get(resource)
375
- new_record = resource.new_record?
376
-
377
- lazy_load(resource) unless new_record || resource.attribute_loaded?(name)
390
+ lazy_load(resource)
378
391
 
379
392
  value = get!(resource)
380
393
 
381
- case track
382
- when :hash
383
- resource.original_values[name] = value.dup.hash unless resource.original_values.has_key?(name) rescue value.hash
384
- when :get
385
- resource.original_values[name] = value.dup unless resource.original_values.has_key?(name) rescue value
386
- end
394
+ set_original_value(resource, value)
387
395
 
388
- if value.nil? && new_record && !options[:default].nil? && !resource.attribute_loaded?(name)
396
+ # [YK] Why did we previously care whether options[:default] is nil.
397
+ # The default value of nil will be applied either way
398
+ if value.nil? && resource.new_record? && !resource.attribute_loaded?(name)
389
399
  value = default_for(resource)
390
400
  set(resource, value)
391
401
  end
@@ -397,21 +407,32 @@ module DataMapper
397
407
  resource.instance_variable_get(instance_variable_name)
398
408
  end
399
409
 
410
+ def set_original_value(resource, val)
411
+ unless resource.original_values.key?(name)
412
+ val = val.try_dup
413
+ val = val.hash if track == :hash
414
+ resource.original_values[name] = val
415
+ end
416
+ end
417
+
400
418
  # Provides a standardized setter method for the property
401
419
  #
402
420
  # @raise <ArgumentError> "+resource+ should be a DataMapper::Resource, but was ...."
403
421
  #-
404
422
  # @api private
405
423
  def set(resource, value)
406
- lazy_load(resource) unless resource.new_record? || resource.attribute_loaded?(name)
424
+ # [YK] We previously checked for new_record? here, but lazy loading
425
+ # is blocked anyway if we're in a new record by by
426
+ # Resource#reload_attributes. This may eventually be useful for
427
+ # optimizing, but let's (a) benchmark it first, and (b) do
428
+ # whatever refactoring is necessary, which will benefit from the
429
+ # centralize checking
430
+ lazy_load(resource)
431
+
407
432
  new_value = typecast(value)
408
433
  old_value = get!(resource)
409
434
 
410
- # skip setting the property if the new value is equal
411
- # to the old value, and the old value was defined
412
- return if new_value == old_value && resource.attribute_loaded?(name)
413
-
414
- resource.original_values[name] = old_value unless resource.original_values.has_key?(name)
435
+ set_original_value(resource, old_value)
415
436
 
416
437
  set!(resource, new_value)
417
438
  end
@@ -424,14 +445,13 @@ module DataMapper
424
445
  #-
425
446
  # @api private
426
447
  def lazy_load(resource)
427
- # TODO: refactor this section
428
- contexts = if lazy?
429
- name
430
- else
431
- model.properties(resource.repository.name).reject do |property|
432
- property.lazy? || resource.attribute_loaded?(property.name)
433
- end
434
- end
448
+ # It is faster to bail out at at a new_record? rather than to process
449
+ # which properties would be loaded and then not load them.
450
+ return if resource.new_record? || resource.attribute_loaded?(name)
451
+ # If we're trying to load a lazy property, load it. Otherwise, lazy-load
452
+ # any properties that should be eager-loaded but were not included
453
+ # in the original :fields list
454
+ contexts = lazy? ? name : model.eager_properties(resource.repository.name)
435
455
  resource.send(:lazy_load, contexts)
436
456
  end
437
457
 
@@ -457,14 +477,13 @@ module DataMapper
457
477
  # these two alternatives:
458
478
  # * Integer(value) rescue nil
459
479
  # * Integer(value_to_s =~ /(\d+)/ ? $1 : value_to_s) rescue nil
480
+ #
481
+ # [YK] The previous implementation used a rescue. Why use a rescue
482
+ # when the list of cases where a valid string other than "0" could
483
+ # produce 0 is known?
460
484
  value_to_i = value.to_i
461
- if value_to_i == 0 && value != '0'
462
- value_to_s = value.to_s
463
- begin
464
- Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
465
- rescue ArgumentError
466
- nil
467
- end
485
+ if value_to_i == 0
486
+ value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
468
487
  else
469
488
  value_to_i
470
489
  end
@@ -485,6 +504,10 @@ module DataMapper
485
504
  @default.respond_to?(:call) ? @default.call(resource, self) : @default
486
505
  end
487
506
 
507
+ def value(val)
508
+ custom? ? self.type.dump(val, self) : val
509
+ end
510
+
488
511
  def inspect
489
512
  "#<Property:#{@model}:#{@name}>"
490
513
  end
@@ -535,6 +558,7 @@ module DataMapper
535
558
  @index = @options.fetch(:index, false)
536
559
  @unique_index = @options.fetch(:unique_index, false)
537
560
  @lazy = @options.fetch(:lazy, @type.respond_to?(:lazy) ? @type.lazy : false) && !@key
561
+ @fields = {}
538
562
 
539
563
  @track = @options.fetch(:track) do
540
564
  if @custom && @type.respond_to?(:track) && @type.track
@@ -575,10 +599,6 @@ module DataMapper
575
599
  @model.property_serialization_setup(self) if @model.respond_to?(:property_serialization_setup)
576
600
  end
577
601
 
578
- def fields
579
- @fields ||= Hash.new { |h,k| h[k] = self.model.field_naming_conventions[k].call(self) }
580
- end
581
-
582
602
  def determine_visibility # :nodoc:
583
603
  @reader_visibility = @options[:reader] || @options[:accessor] || :public
584
604
  @writer_visibility = @options[:writer] || @options[:accessor] || :public
@@ -615,7 +635,7 @@ module DataMapper
615
635
  def typecast_hash_to_datetime(hash)
616
636
  args = extract_time_args_from_hash(hash, :year, :month, :day, :hour, :min, :sec)
617
637
  DateTime.new(*args)
618
- rescue ArgumentError
638
+ rescue ArgumentError => e
619
639
  t = typecast_hash_to_time(hash)
620
640
  DateTime.new(t.year, t.month, t.day, t.hour, t.min, t.sec)
621
641
  end