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
@@ -61,9 +61,9 @@ module DataMapper
61
61
  #
62
62
  # class Post
63
63
  # include DataMapper::Resource
64
- # property :title, String, :accessor => :private
64
+ # property :title, String, :accessor => :private
65
65
  # # Both reader and writer are private
66
- # property :body, Text, :accessor => :protected
66
+ # property :body, Text, :accessor => :protected
67
67
  # # Both reader and writer are protected
68
68
  # end
69
69
  #
@@ -243,7 +243,7 @@ module DataMapper
243
243
  # NOTE: PLEASE update PROPERTY_OPTIONS in DataMapper::Type when updating
244
244
  # them here
245
245
  PROPERTY_OPTIONS = [
246
- :public, :protected, :private, :accessor, :reader, :writer,
246
+ :accessor, :reader, :writer,
247
247
  :lazy, :default, :nullable, :key, :serial, :field, :size, :length,
248
248
  :format, :index, :unique_index, :check, :ordinal, :auto_validation,
249
249
  :validates, :unique, :track, :precision, :scale
@@ -264,7 +264,8 @@ module DataMapper
264
264
  Time,
265
265
  Object,
266
266
  Class,
267
- DataMapper::Types::Discriminator
267
+ DataMapper::Types::Discriminator,
268
+ DataMapper::Types::Serial
268
269
  ]
269
270
 
270
271
  IMMUTABLE_TYPES = [ TrueClass, Float, Integer, BigDecimal]
@@ -273,7 +274,8 @@ module DataMapper
273
274
 
274
275
  DEFAULT_LENGTH = 50
275
276
  DEFAULT_PRECISION = 10
276
- DEFAULT_SCALE = 0
277
+ DEFAULT_SCALE_BIGDECIMAL = 0
278
+ DEFAULT_SCALE_FLOAT = nil
277
279
 
278
280
  attr_reader :primitive, :model, :name, :instance_variable_name,
279
281
  :type, :reader_visibility, :writer_visibility, :getter, :options,
@@ -284,18 +286,14 @@ module DataMapper
284
286
  # @return <String> name of field in data-store
285
287
  # -
286
288
  # @api semi-public
287
- def field(*args)
288
- @options.fetch(:field, repository(*args).adapter.field_naming_convention.call(name))
289
+ def field(repository_name = nil)
290
+ @field || fields[repository_name || model.repository_name]
289
291
  end
290
292
 
291
293
  def unique
292
294
  @unique ||= @options.fetch(:unique, @serial || @key || false)
293
295
  end
294
296
 
295
- def repository(*args)
296
- @model.repository(*args)
297
- end
298
-
299
297
  def hash
300
298
  if @custom && !@bound
301
299
  @type.bind(self)
@@ -336,7 +334,6 @@ module DataMapper
336
334
  @lazy
337
335
  end
338
336
 
339
-
340
337
  # Returns whether or not the property is a key or a part of a key
341
338
  #
342
339
  # @return <TrueClass, FalseClass> whether the property is a key or a part of
@@ -377,17 +374,7 @@ module DataMapper
377
374
  def get(resource)
378
375
  new_record = resource.new_record?
379
376
 
380
- unless new_record || resource.attribute_loaded?(name)
381
- # TODO: refactor this section
382
- contexts = if lazy?
383
- name
384
- else
385
- model.properties(resource.repository.name).reject do |property|
386
- property.lazy? || resource.attribute_loaded?(property.name)
387
- end
388
- end
389
- resource.send(:lazy_load, contexts)
390
- end
377
+ lazy_load(resource) unless new_record || resource.attribute_loaded?(name)
391
378
 
392
379
  value = get!(resource)
393
380
 
@@ -416,6 +403,7 @@ module DataMapper
416
403
  #-
417
404
  # @api private
418
405
  def set(resource, value)
406
+ lazy_load(resource) unless resource.new_record? || resource.attribute_loaded?(name)
419
407
  new_value = typecast(value)
420
408
  old_value = get!(resource)
421
409
 
@@ -432,6 +420,21 @@ module DataMapper
432
420
  resource.instance_variable_set(instance_variable_name, value)
433
421
  end
434
422
 
423
+ # Loads lazy columns when get or set is called.
424
+ #-
425
+ # @api private
426
+ 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
435
+ resource.send(:lazy_load, contexts)
436
+ end
437
+
435
438
  # typecasts values into a primitive
436
439
  #
437
440
  # @return <TrueClass, String, Float, Integer, BigDecimal, DateTime, Date, Time
@@ -439,17 +442,37 @@ module DataMapper
439
442
  #-
440
443
  # @api private
441
444
  def typecast(value)
442
- return value if value.kind_of?(type) || value.nil?
445
+ return type.typecast(value, self) if type.respond_to?(:typecast)
446
+ return value if value.kind_of?(primitive) || value.nil?
443
447
  begin
444
- if type == TrueClass then %w[ true 1 t ].include?(value.to_s.downcase)
445
- elsif type == String then value.to_s
446
- elsif type == Float then value.to_f
447
- elsif type == Integer then value.to_i
448
- elsif type == BigDecimal then BigDecimal(value.to_s)
449
- elsif type == DateTime then typecast_to_datetime(value)
450
- elsif type == Date then typecast_to_date(value)
451
- elsif type == Time then typecast_to_time(value)
452
- elsif type == Class then self.class.find_const(value)
448
+ if primitive == TrueClass then %w[ true 1 t ].include?(value.to_s.downcase)
449
+ elsif primitive == String then value.to_s
450
+ elsif primitive == Float then value.to_f
451
+ elsif primitive == Integer
452
+ # The simplest possible implementation, i.e. value.to_i, is not
453
+ # desirable because "junk".to_i gives "0". We want nil instead,
454
+ # because this makes it clear that the typecast failed.
455
+ #
456
+ # After benchmarking, we preferred the current implementation over
457
+ # these two alternatives:
458
+ # * Integer(value) rescue nil
459
+ # * Integer(value_to_s =~ /(\d+)/ ? $1 : value_to_s) rescue nil
460
+ 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
468
+ else
469
+ value_to_i
470
+ end
471
+ elsif primitive == BigDecimal then BigDecimal(value.to_s)
472
+ elsif primitive == DateTime then typecast_to_datetime(value)
473
+ elsif primitive == Date then typecast_to_date(value)
474
+ elsif primitive == Time then typecast_to_time(value)
475
+ elsif primitive == Class then self.class.find_const(value)
453
476
  else
454
477
  value
455
478
  end
@@ -503,6 +526,7 @@ module DataMapper
503
526
  @primitive = @options.fetch(:primitive, @type.respond_to?(:primitive) ? @type.primitive : @type)
504
527
 
505
528
  @getter = TrueClass == @primitive ? "#{@name}?".to_sym : @name
529
+ @field = @options.fetch(:field, nil)
506
530
  @serial = @options.fetch(:serial, false)
507
531
  @key = @options.fetch(:key, @serial || false)
508
532
  @default = @options.fetch(:default, nil)
@@ -524,18 +548,23 @@ module DataMapper
524
548
  @length = @options.fetch(:length, @options.fetch(:size, DEFAULT_LENGTH))
525
549
  elsif BigDecimal == @primitive || Float == @primitive
526
550
  @precision = @options.fetch(:precision, DEFAULT_PRECISION)
527
- @scale = @options.fetch(:scale, DEFAULT_SCALE)
551
+
552
+ default_scale = (Float == @primitive) ? DEFAULT_SCALE_FLOAT : DEFAULT_SCALE_BIGDECIMAL
553
+ @scale = @options.fetch(:scale, default_scale)
554
+ # @scale = @options.fetch(:scale, DEFAULT_SCALE_BIGDECIMAL)
528
555
 
529
556
  unless @precision > 0
530
557
  raise ArgumentError, "precision must be greater than 0, but was #{@precision.inspect}"
531
558
  end
532
559
 
533
- unless @scale >= 0
534
- raise ArgumentError, "scale must be equal to or greater than 0, but was #{@scale.inspect}"
535
- end
560
+ if (BigDecimal == @primitive) || (Float == @primitive && !@scale.nil?)
561
+ unless @scale >= 0
562
+ raise ArgumentError, "scale must be equal to or greater than 0, but was #{@scale.inspect}"
563
+ end
536
564
 
537
- unless @precision >= @scale
538
- raise ArgumentError, "precision must be equal to or greater than scale, but was #{@precision.inspect} and scale was #{@scale.inspect}"
565
+ unless @precision >= @scale
566
+ raise ArgumentError, "precision must be equal to or greater than scale, but was #{@precision.inspect} and scale was #{@scale.inspect}"
567
+ end
539
568
  end
540
569
  end
541
570
 
@@ -545,11 +574,13 @@ module DataMapper
545
574
  @model.property_serialization_setup(self) if @model.respond_to?(:property_serialization_setup)
546
575
  end
547
576
 
577
+ def fields
578
+ @fields ||= Hash.new { |h,k| h[k] = repository(k).adapter.field_naming_convention.call(self.name) }
579
+ end
580
+
548
581
  def determine_visibility # :nodoc:
549
582
  @reader_visibility = @options[:reader] || @options[:accessor] || :public
550
583
  @writer_visibility = @options[:writer] || @options[:accessor] || :public
551
- @writer_visibility = :protected if @options[:protected]
552
- @writer_visibility = :private if @options[:private]
553
584
 
554
585
  unless VISIBILITY_OPTIONS.include?(@reader_visibility) && VISIBILITY_OPTIONS.include?(@writer_visibility)
555
586
  raise ArgumentError, 'property visibility must be :public, :protected, or :private', caller(2)
@@ -4,10 +4,12 @@ module DataMapper
4
4
  include Enumerable
5
5
 
6
6
  def [](name)
7
- @property_for[name]
7
+ @property_for[name] || raise(ArgumentError, "Unknown property '#{name}'", caller)
8
8
  end
9
9
 
10
- alias has_property? []
10
+ def has_property?(name)
11
+ !!@property_for[name]
12
+ end
11
13
 
12
14
  def slice(*names)
13
15
  @property_for.values_at(*names)
@@ -44,13 +46,15 @@ module DataMapper
44
46
 
45
47
  def indexes
46
48
  index_hash = {}
47
- each { |property| parse_index(property.index, property.field.to_s, index_hash) }
49
+ repository_name = repository.name
50
+ each { |property| parse_index(property.index, property.field(repository_name), index_hash) }
48
51
  index_hash
49
52
  end
50
53
 
51
54
  def unique_indexes
52
55
  index_hash = {}
53
- each { |property| parse_index(property.unique_index, property.field.to_s, index_hash) }
56
+ repository_name = repository.name
57
+ each { |property| parse_index(property.unique_index, property.field(repository_name), index_hash) }
54
58
  index_hash
55
59
  end
56
60
 
@@ -108,16 +112,6 @@ module DataMapper
108
112
  '#<PropertySet:{' + map { |property| property.inspect }.join(',') + '}>'
109
113
  end
110
114
 
111
- def dup(target = nil)
112
- return super() unless target
113
-
114
- properties = map do |property|
115
- Property.new(target || property.model, property.name, property.type, property.options.dup)
116
- end
117
-
118
- self.class.new(properties)
119
- end
120
-
121
115
  private
122
116
 
123
117
  def initialize(properties = [])
data/lib/dm-core/query.rb CHANGED
@@ -3,10 +3,10 @@ module DataMapper
3
3
  include Assertions
4
4
 
5
5
  OPTIONS = [
6
- :reload, :offset, :limit, :order, :add_reversed, :fields, :links, :includes, :conditions
6
+ :reload, :offset, :limit, :order, :add_reversed, :fields, :links, :includes, :conditions, :unique
7
7
  ]
8
8
 
9
- attr_reader :repository, :model, *OPTIONS - [ :reload ]
9
+ attr_reader :repository, :model, *OPTIONS - [ :reload, :unique ]
10
10
  attr_writer :add_reversed
11
11
  alias add_reversed? add_reversed
12
12
 
@@ -14,6 +14,10 @@ module DataMapper
14
14
  @reload
15
15
  end
16
16
 
17
+ def unique?
18
+ @unique
19
+ end
20
+
17
21
  def reverse
18
22
  dup.reverse!
19
23
  end
@@ -42,6 +46,7 @@ module DataMapper
42
46
 
43
47
  # only overwrite the attributes with non-default values
44
48
  @reload = other.reload? unless other.reload? == false
49
+ @unique = other.unique? unless other.unique? == false
45
50
  @offset = other.offset unless other.offset == 0
46
51
  @limit = other.limit unless other.limit == nil
47
52
  @order = other.order unless other.order == model.default_order
@@ -67,6 +72,7 @@ module DataMapper
67
72
  # return hash == other.hash
68
73
  @model == other.model &&
69
74
  @reload == other.reload? &&
75
+ @unique == other.unique? &&
70
76
  @offset == other.offset &&
71
77
  @limit == other.limit &&
72
78
  @order == other.order && # order is significant, so do not sort this
@@ -136,6 +142,7 @@ module DataMapper
136
142
  [ :limit, limit ],
137
143
  [ :offset, offset ],
138
144
  [ :reload, reload? ],
145
+ [ :unique, unique? ],
139
146
  ]
140
147
 
141
148
  "#<#{self.class.name} #{attrs.map { |(k,v)| "@#{k}=#{v.inspect}" } * ' '}>"
@@ -157,6 +164,7 @@ module DataMapper
157
164
 
158
165
  @model = model # must be Class that includes DM::Resource
159
166
  @reload = options.fetch :reload, false # must be true or false
167
+ @unique = options.fetch :unique, false # must be true or false
160
168
  @offset = options.fetch :offset, 0 # must be an Integer greater than or equal to 0
161
169
  @limit = options.fetch :limit, nil # must be an Integer greater than or equal to 1
162
170
  @order = options.fetch :order, model.default_order # must be an Array of Symbol, DM::Query::Direction or DM::Property
@@ -214,18 +222,20 @@ module DataMapper
214
222
  options.each do |key, value|
215
223
  case key
216
224
  when DataMapper::Query::Operator
217
- options[key] = properties[key.target].type.dump(value, properties[key.target]) if !properties[key.target].nil? && properties[key.target].custom?
225
+ options[key] = properties[key.target].type.dump(value, properties[key.target]) if properties.has_property?(key.target) && properties[key.target].custom?
218
226
  when Symbol, String
219
- options[key] = properties[key].type.dump(value, properties[key]) if !properties[key].nil? && properties[key].custom?
227
+ options[key] = properties[key].type.dump(value, properties[key]) if properties.has_property?(key) && properties[key].custom?
220
228
  end
221
229
  end
222
230
  end
223
231
 
224
232
  # validate the options
225
233
  def assert_valid_options(options)
226
- # validate the reload option
227
- if options.has_key?(:reload) && options[:reload] != true && options[:reload] != false
228
- raise ArgumentError, "+options[:reload]+ must be true or false, but was #{options[:reload].inspect}", caller(2)
234
+ # validate the reload option and unique option
235
+ ([ :reload, :unique ] & options.keys).each do |attribute|
236
+ if options[attribute] != true && options[attribute] != false
237
+ raise ArgumentError, "+options[:#{attribute}]+ must be true or false, but was #{options[attribute].inspect}", caller(2)
238
+ end
229
239
  end
230
240
 
231
241
  # validate the offset and limit options
@@ -248,7 +258,17 @@ module DataMapper
248
258
  assert_kind_of "options[:#{attribute}]", value, Array
249
259
 
250
260
  if value.empty?
251
- raise ArgumentError, "+options[:#{attribute}]+ cannot be empty", caller(2)
261
+ if attribute == :fields
262
+ if options[:unique] == false
263
+ raise ArgumentError, '+options[:fields]+ cannot be empty if +options[:unique] is false', caller(2)
264
+ end
265
+ elsif attribute == :order
266
+ if options[:fields] && options[:fields].any? { |p| !p.kind_of?(Operator) }
267
+ raise ArgumentError, '+options[:order]+ cannot be empty if +options[:fields] contains a non-operator', caller(2)
268
+ end
269
+ else
270
+ raise ArgumentError, "+options[:#{attribute}]+ cannot be empty", caller(2)
271
+ end
252
272
  end
253
273
  end
254
274
 
@@ -318,9 +338,10 @@ module DataMapper
318
338
  # normalize fields to DM::Property
319
339
  def normalize_fields(fields)
320
340
  # TODO: return a PropertySet
341
+ # TODO: raise an exception if the property is not available in the repository
321
342
  fields.map do |field|
322
343
  case field
323
- when Property
344
+ when Property, Operator
324
345
  # TODO: if the Property's model doesn't match
325
346
  # self.model, append the property's model to @links
326
347
  # eg:
@@ -527,6 +548,8 @@ module DataMapper
527
548
  end
528
549
 
529
550
  def ==(other)
551
+ return true if super
552
+ return false unless other.kind_of?(self.class)
530
553
  @operator == other.operator && @target == other.target
531
554
  end
532
555
 
@@ -542,6 +565,8 @@ module DataMapper
542
565
 
543
566
  class Path
544
567
  include Assertions
568
+
569
+ %w[ id type ].each { |m| undef_method m }
545
570
 
546
571
  attr_reader :relationships, :model, :property, :operator
547
572
 
File without changes
@@ -2,6 +2,8 @@ require 'set'
2
2
 
3
3
  module DataMapper
4
4
  module Resource
5
+ include Assertions
6
+
5
7
  ##
6
8
  #
7
9
  # Appends a module for inclusion into the model class after
@@ -22,12 +24,10 @@ module DataMapper
22
24
  extra_inclusions.concat inclusions
23
25
  true
24
26
  end
25
-
27
+
26
28
  def self.extra_inclusions
27
29
  @extra_inclusions ||= []
28
30
  end
29
-
30
- include Assertions
31
31
 
32
32
  # When Resource is included in a class this method makes sure
33
33
  # it gets all the methods
@@ -334,11 +334,7 @@ module DataMapper
334
334
  # --
335
335
  # @api public
336
336
  def loaded_attributes
337
- names = []
338
- properties.each do |property|
339
- names << property.name if attribute_loaded?(property.name)
340
- end
341
- names
337
+ properties.map{|p| p.name if attribute_loaded?(p.name)}.compact
342
338
  end
343
339
 
344
340
  # set of original values of properties
@@ -471,7 +467,9 @@ module DataMapper
471
467
  # --
472
468
  # @api public
473
469
  def attributes
474
- properties.map{|p| [p.name,send(p.getter)] if p.reader_visibility == :public}.compact.to_hash
470
+ properties.map do |p|
471
+ [p.name, send(p.getter)] if p.reader_visibility == :public
472
+ end.compact.to_hash
475
473
  end
476
474
 
477
475
  # Mass assign of attributes
@@ -485,9 +483,11 @@ module DataMapper
485
483
  values_hash.each_pair do |k,v|
486
484
  setter = "#{k.to_s.sub(/\?\z/, '')}="
487
485
 
488
- # use the attribute mutator if it is public to set the value
489
- next unless respond_to?(setter, false)
490
- send(setter, v)
486
+ if respond_to?(setter)
487
+ send(setter, v)
488
+ else
489
+ raise NameError, "#{setter} is not a public property"
490
+ end
491
491
  end
492
492
  end
493
493
 
@@ -585,7 +585,7 @@ module DataMapper
585
585
  end
586
586
 
587
587
  def lazy_load(name)
588
- reload_attributes(*properties.lazy_load_context(name))
588
+ reload_attributes(*properties.lazy_load_context(name) - loaded_attributes)
589
589
  end
590
590
 
591
591
  def child_associations