dm-core 1.1.0 → 1.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/Gemfile +13 -11
  2. data/README.rdoc +1 -1
  3. data/Rakefile +1 -2
  4. data/VERSION +1 -1
  5. data/dm-core.gemspec +30 -176
  6. data/lib/dm-core.rb +32 -67
  7. data/lib/dm-core/adapters/abstract_adapter.rb +1 -2
  8. data/lib/dm-core/associations/many_to_many.rb +11 -5
  9. data/lib/dm-core/associations/many_to_one.rb +17 -2
  10. data/lib/dm-core/associations/one_to_many.rb +16 -0
  11. data/lib/dm-core/backwards.rb +13 -0
  12. data/lib/dm-core/collection.rb +1 -1
  13. data/lib/dm-core/model.rb +99 -41
  14. data/lib/dm-core/model/property.rb +24 -27
  15. data/lib/dm-core/model/relationship.rb +22 -28
  16. data/lib/dm-core/property.rb +37 -50
  17. data/lib/dm-core/property/boolean.rb +6 -10
  18. data/lib/dm-core/property/date.rb +0 -2
  19. data/lib/dm-core/property/date_time.rb +0 -2
  20. data/lib/dm-core/property/decimal.rb +5 -1
  21. data/lib/dm-core/property/discriminator.rb +24 -26
  22. data/lib/dm-core/property/float.rb +5 -1
  23. data/lib/dm-core/property/numeric.rb +6 -9
  24. data/lib/dm-core/property/string.rb +2 -1
  25. data/lib/dm-core/property/time.rb +0 -2
  26. data/lib/dm-core/property/typecast/time.rb +7 -2
  27. data/lib/dm-core/property_set.rb +1 -3
  28. data/lib/dm-core/query.rb +3 -10
  29. data/lib/dm-core/query/conditions/comparison.rb +5 -1
  30. data/lib/dm-core/query/conditions/operation.rb +1 -1
  31. data/lib/dm-core/relationship_set.rb +0 -2
  32. data/lib/dm-core/resource.rb +27 -28
  33. data/lib/dm-core/resource/{state.rb → persistence_state.rb} +2 -2
  34. data/lib/dm-core/resource/{state → persistence_state}/clean.rb +4 -4
  35. data/lib/dm-core/resource/{state → persistence_state}/deleted.rb +2 -2
  36. data/lib/dm-core/resource/{state → persistence_state}/dirty.rb +2 -2
  37. data/lib/dm-core/resource/{state → persistence_state}/immutable.rb +3 -3
  38. data/lib/dm-core/resource/{state → persistence_state}/persisted.rb +3 -3
  39. data/lib/dm-core/resource/{state → persistence_state}/transient.rb +3 -3
  40. data/lib/dm-core/spec/lib/adapter_helpers.rb +2 -5
  41. data/lib/dm-core/spec/setup.rb +3 -2
  42. data/lib/dm-core/spec/shared/public/property_spec.rb +8 -0
  43. data/lib/dm-core/spec/shared/resource_spec.rb +14 -0
  44. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +1 -1
  45. data/lib/dm-core/support/descendant_set.rb +0 -2
  46. data/lib/dm-core/support/ext/array.rb +0 -19
  47. data/lib/dm-core/support/ext/blank.rb +1 -0
  48. data/lib/dm-core/support/hook.rb +0 -3
  49. data/lib/dm-core/support/naming_conventions.rb +6 -0
  50. data/lib/dm-core/support/ordered_set.rb +0 -2
  51. data/lib/dm-core/support/subject_set.rb +0 -2
  52. data/lib/dm-core/version.rb +1 -1
  53. data/spec/public/associations/many_to_many_spec.rb +0 -1
  54. data/spec/public/model/property_spec.rb +55 -9
  55. data/spec/public/model/relationship_spec.rb +24 -2
  56. data/spec/public/model_spec.rb +32 -0
  57. data/spec/public/property/binary_spec.rb +14 -6
  58. data/spec/public/property/boolean_spec.rb +14 -6
  59. data/spec/public/property/class_spec.rb +14 -6
  60. data/spec/public/property/date_spec.rb +14 -6
  61. data/spec/public/property/date_time_spec.rb +14 -6
  62. data/spec/public/property/decimal_spec.rb +10 -2
  63. data/spec/public/property/discriminator_spec.rb +15 -1
  64. data/spec/public/property/float_spec.rb +14 -6
  65. data/spec/public/property/integer_spec.rb +14 -6
  66. data/spec/public/property/object_spec.rb +8 -0
  67. data/spec/public/property/serial_spec.rb +14 -6
  68. data/spec/public/property/string_spec.rb +14 -6
  69. data/spec/public/property/text_spec.rb +14 -6
  70. data/spec/public/property/time_spec.rb +14 -6
  71. data/spec/public/resource_spec.rb +58 -0
  72. data/spec/public/shared/finder_shared_spec.rb +8 -4
  73. data/spec/semipublic/associations/many_to_many_spec.rb +2 -0
  74. data/spec/semipublic/associations/many_to_one_spec.rb +2 -0
  75. data/spec/semipublic/associations/one_to_many_spec.rb +2 -0
  76. data/spec/semipublic/associations/one_to_one_spec.rb +2 -0
  77. data/spec/semipublic/property/binary_spec.rb +5 -5
  78. data/spec/semipublic/property/boolean_spec.rb +5 -5
  79. data/spec/semipublic/property/class_spec.rb +5 -5
  80. data/spec/semipublic/property/date_spec.rb +5 -5
  81. data/spec/semipublic/property/date_time_spec.rb +5 -5
  82. data/spec/semipublic/property/decimal_spec.rb +2 -2
  83. data/spec/semipublic/property/discriminator_spec.rb +5 -5
  84. data/spec/semipublic/property/float_spec.rb +5 -5
  85. data/spec/semipublic/property/integer_spec.rb +5 -5
  86. data/spec/semipublic/property/lookup_spec.rb +3 -3
  87. data/spec/semipublic/property/serial_spec.rb +5 -5
  88. data/spec/semipublic/property/string_spec.rb +5 -5
  89. data/spec/semipublic/property/text_spec.rb +5 -5
  90. data/spec/semipublic/property/time_spec.rb +5 -5
  91. data/spec/semipublic/query/conditions/comparison_spec.rb +44 -4
  92. data/spec/semipublic/query_spec.rb +2 -11
  93. data/spec/semipublic/resource/state/clean_spec.rb +6 -6
  94. data/spec/semipublic/resource/state/deleted_spec.rb +4 -4
  95. data/spec/semipublic/resource/state/dirty_spec.rb +8 -8
  96. data/spec/semipublic/resource/state/immutable_spec.rb +6 -6
  97. data/spec/semipublic/resource/state/transient_spec.rb +5 -5
  98. data/spec/semipublic/resource/state_spec.rb +15 -15
  99. data/spec/semipublic/shared/resource_shared_spec.rb +7 -1
  100. data/spec/semipublic/shared/resource_state_shared_spec.rb +8 -8
  101. data/spec/unit/array_spec.rb +0 -14
  102. data/spec/unit/blank_spec.rb +11 -0
  103. metadata +70 -188
  104. data/lib/dm-core/support/inflector.rb +0 -3
@@ -208,8 +208,7 @@ module DataMapper
208
208
  #
209
209
  # @api semipublic
210
210
  def attributes_as_fields(attributes)
211
- pairs = attributes.map { |property, value| [ property.field, property.dump(value) ] }
212
- DataMapper::Ext::Array.to_hash(pairs)
211
+ Hash[ attributes.map { |property, value| [ property.field, property.dump(value) ] } ]
213
212
  end
214
213
 
215
214
  private
@@ -124,6 +124,14 @@ module DataMapper
124
124
  @links.freeze
125
125
  end
126
126
 
127
+ # Initialize the chain for "many to many" relationships
128
+ #
129
+ # @api public
130
+ def finalize
131
+ through
132
+ via
133
+ end
134
+
127
135
  # @api private
128
136
  def source_scope(source)
129
137
  { through.inverse => source }
@@ -248,15 +256,13 @@ module DataMapper
248
256
  target_key.valid?(source_key.get(source))
249
257
  end
250
258
 
251
- # @api semipublic
252
259
  chainable do
260
+ # @api semipublic
253
261
  def many_to_one_options
254
262
  { :parent_key => target_key.map { |property| property.name } }
255
263
  end
256
- end
257
264
 
258
- # @api semipublic
259
- chainable do
265
+ # @api semipublic
260
266
  def one_to_many_options
261
267
  { :parent_key => source_key.map { |property| property.name } }
262
268
  end
@@ -359,7 +365,7 @@ module DataMapper
359
365
  end
360
366
 
361
367
  each do |resource|
362
- resource.persisted_state = Resource::State::Immutable.new(resource)
368
+ resource.persistence_state = Resource::PersistenceState::Immutable.new(resource)
363
369
  end
364
370
 
365
371
  clear
@@ -4,7 +4,7 @@ module DataMapper
4
4
  # Relationship class with implementation specific
5
5
  # to n side of 1 to n association
6
6
  class Relationship < Associations::Relationship
7
- OPTIONS = superclass::OPTIONS.dup << :required << :key
7
+ OPTIONS = superclass::OPTIONS.dup << :required << :key << :unique
8
8
 
9
9
  # @api semipublic
10
10
  alias_method :source_repository_name, :child_repository_name
@@ -31,6 +31,11 @@ module DataMapper
31
31
  @key
32
32
  end
33
33
 
34
+ # @api semipublic
35
+ def unique?
36
+ !!@unique
37
+ end
38
+
34
39
  # @deprecated
35
40
  def nullable?
36
41
  raise "#{self.class}#nullable? is deprecated, use #{self.class}#required? instead (#{caller.first})"
@@ -64,6 +69,14 @@ module DataMapper
64
69
  # @api semipublic
65
70
  alias_method :source_key, :child_key
66
71
 
72
+ # Initialize the foreign key property this "many to one"
73
+ # relationship uses to persist itself
74
+ #
75
+ # @api public
76
+ def finalize
77
+ child_key
78
+ end
79
+
67
80
  # Returns a hash of conditions that scopes query that fetches
68
81
  # target object
69
82
  #
@@ -193,6 +206,7 @@ module DataMapper
193
206
 
194
207
  @required = options.fetch(:required, true)
195
208
  @key = options.fetch(:key, false)
209
+ @unique = options.fetch(:unique, false)
196
210
  target_model ||= DataMapper::Inflector.camelize(name)
197
211
  options = { :min => @required ? 1 : 0, :max => 1 }.update(options)
198
212
  super
@@ -248,7 +262,8 @@ module DataMapper
248
262
  options = DataMapper::Ext::Hash.only(target_property.options, :length, :precision, :scale).update(
249
263
  :index => name,
250
264
  :required => required?,
251
- :key => key?
265
+ :key => key?,
266
+ :unique => @unique
252
267
  )
253
268
 
254
269
  if target_property.primitive == Integer
@@ -100,6 +100,22 @@ module DataMapper
100
100
  end
101
101
  end
102
102
 
103
+ # initialize the inverse "many to one" relationships explicitly before
104
+ # initializing other relationships. This makes sure that foreign key
105
+ # properties always appear in the order they were declared.
106
+ #
107
+ # @api public
108
+ def finalize
109
+ child_model.relationships.each do |relationship|
110
+ # TODO: should this check #inverse?
111
+ # relationship.child_key if inverse?(relationship)
112
+ if relationship.kind_of?(Associations::ManyToOne::Relationship)
113
+ relationship.finalize
114
+ end
115
+ end
116
+ inverse.finalize
117
+ end
118
+
103
119
  # @api semipublic
104
120
  def default_for(source)
105
121
  collection_for(source).replace(Array(super))
@@ -0,0 +1,13 @@
1
+ require "dm-core/support/deprecate"
2
+
3
+ module DataMapper
4
+ module Resource
5
+ extend Deprecate
6
+
7
+ deprecate :persisted_state, :persistence_state
8
+ deprecate :persisted_state=, :persistence_state=
9
+ deprecate :persisted_state?, :persistence_state?
10
+
11
+ end # module Resource
12
+
13
+ end # module DataMapper
@@ -919,7 +919,7 @@ module DataMapper
919
919
  end
920
920
 
921
921
  each do |resource|
922
- resource.persisted_state = Resource::State::Immutable.new(resource)
922
+ resource.persistence_state = Resource::PersistenceState::Immutable.new(resource)
923
923
  end
924
924
 
925
925
  clear
@@ -1,14 +1,10 @@
1
- # TODO: add Model#create!, Model#update, Model#update!, Model#destroy and Model#destroy!
2
-
3
- # TODO: DRY up raise_on_save_failure with attr_accessor_with_default
4
- # once AS branch is merged in
5
-
6
1
  module DataMapper
7
2
  module Model
8
- extend Chainable
9
-
10
3
  include Enumerable
11
4
 
5
+ WRITER_METHOD_REGEXP = /=\z/.freeze
6
+ INVALID_WRITER_METHODS = %w[ == != === []= taguri= attributes= collection= persistence_state= raise_on_save_failure= ].to_set.freeze
7
+
12
8
  # Creates a new Model class with its constant already set
13
9
  #
14
10
  # If a block is passed, it will be eval'd in the context of the new Model
@@ -132,6 +128,19 @@ module DataMapper
132
128
  @raise_on_save_failure = raise_on_save_failure
133
129
  end
134
130
 
131
+ # Finish model setup and verify it is valid
132
+ #
133
+ # @return [undefined]
134
+ #
135
+ # @api public
136
+ def finalize
137
+ finalize_relationships
138
+ finalize_allowed_writer_methods
139
+ assert_valid_name
140
+ assert_valid_properties
141
+ assert_valid_key
142
+ end
143
+
135
144
  # Appends a module for inclusion into the model class after Resource.
136
145
  #
137
146
  # This is a useful way to extend Resource while still retaining a
@@ -211,15 +220,13 @@ module DataMapper
211
220
  end
212
221
 
213
222
  # @api private
214
- chainable do
215
- def inherited(descendant)
216
- descendants << descendant
217
-
218
- descendant.instance_variable_set(:@valid, false)
219
- descendant.instance_variable_set(:@base_model, base_model)
220
- descendant.instance_variable_set(:@storage_names, @storage_names.dup)
221
- descendant.instance_variable_set(:@default_order, @default_order.dup)
222
- end
223
+ def inherited(descendant)
224
+ descendants << descendant
225
+
226
+ descendant.instance_variable_set(:@valid, false)
227
+ descendant.instance_variable_set(:@base_model, base_model)
228
+ descendant.instance_variable_set(:@storage_names, @storage_names.dup)
229
+ descendant.instance_variable_set(:@default_order, @default_order.dup)
223
230
  end
224
231
 
225
232
  # Gets the name of the storage receptacle for this resource in the given
@@ -564,8 +571,7 @@ module DataMapper
564
571
  discriminator = properties(repository_name).discriminator
565
572
  no_reload = !query.reload?
566
573
 
567
- field_map = fields.map { |property| [ property, property.field ] }
568
- field_map = DataMapper::Ext::Array.to_hash(field_map)
574
+ field_map = Hash[ fields.map { |property| [ property, property.field ] } ]
569
575
 
570
576
  records.map do |record|
571
577
  identity_map = nil
@@ -621,13 +627,13 @@ module DataMapper
621
627
  resource.instance_variable_set(:@_repository, repository)
622
628
 
623
629
  if identity_map
624
- resource.persisted_state = Resource::State::Clean.new(resource) unless resource.persisted_state?
630
+ resource.persistence_state = Resource::PersistenceState::Clean.new(resource) unless resource.persistence_state?
625
631
 
626
632
  # defer setting the IdentityMap so second level caches can
627
633
  # record the state of the resource after loaded
628
634
  identity_map[key_values] = resource
629
635
  else
630
- resource.persisted_state = Resource::State::Immutable.new(resource)
636
+ resource.persistence_state = Resource::PersistenceState::Immutable.new(resource)
631
637
  end
632
638
 
633
639
  resource
@@ -637,6 +643,13 @@ module DataMapper
637
643
  # @api semipublic
638
644
  attr_reader :base_model
639
645
 
646
+ # The list of writer methods that can be mass-assigned to in #attributes=
647
+ #
648
+ # @return [Set]
649
+ #
650
+ # @api private
651
+ attr_reader :allowed_writer_methods
652
+
640
653
  # @api semipublic
641
654
  def default_repository_name
642
655
  Repository.default_name
@@ -760,32 +773,32 @@ module DataMapper
760
773
  end
761
774
  end
762
775
 
776
+ # Initialize all foreign key properties established by relationships
777
+ #
778
+ # @return [undefined]
779
+ #
780
+ # @api private
781
+ def finalize_relationships
782
+ relationships(repository_name).each { |relationship| relationship.finalize }
783
+ end
784
+
785
+ # Initialize the list of allowed writer methods
786
+ #
787
+ # @return [undefined]
788
+ #
789
+ # @api private
790
+ def finalize_allowed_writer_methods
791
+ @allowed_writer_methods = public_instance_methods.map { |method| method.to_s }.grep(WRITER_METHOD_REGEXP).to_set
792
+ @allowed_writer_methods -= INVALID_WRITER_METHODS
793
+ @allowed_writer_methods.freeze
794
+ end
795
+
763
796
  # @api private
764
797
  # TODO: Remove this once appropriate warnings can be added.
765
798
  def assert_valid(force = false) # :nodoc:
766
799
  return if @valid && !force
767
800
  @valid = true
768
-
769
- name = self.name
770
- repository_name = self.repository_name
771
-
772
- if properties(repository_name).empty? &&
773
- !relationships(repository_name).any? { |(relationship_name, relationship)| relationship.kind_of?(Associations::ManyToOne::Relationship) }
774
- raise IncompleteModelError, "#{name} must have at least one property or many to one relationship to be valid"
775
- end
776
-
777
- if key(repository_name).empty?
778
- raise IncompleteModelError, "#{name} must have a key to be valid"
779
- end
780
-
781
- # initialize join models and target keys
782
- @relationships.values.each do |relationships|
783
- relationships.each do |relationship|
784
- relationship.child_key
785
- relationship.through if relationship.respond_to?(:through)
786
- relationship.via if relationship.respond_to?(:via)
787
- end
788
- end
801
+ finalize
789
802
  end
790
803
 
791
804
  # Raises an exception if #get receives the wrong number of arguments
@@ -807,5 +820,50 @@ module DataMapper
807
820
  raise ArgumentError, "The number of arguments for the key is invalid, expected #{expected_key_size} but was #{actual_key_size}"
808
821
  end
809
822
  end
823
+
824
+ # Test if the model name is valid
825
+ #
826
+ # @return [undefined]
827
+ #
828
+ # @api private
829
+ def assert_valid_name
830
+ if name.to_s.strip.empty?
831
+ raise IncompleteModelError, "#{inspect} must have a name"
832
+ end
833
+ end
834
+
835
+ # Test if the model has properties
836
+ #
837
+ # A model may also be valid if it has at least one m:1 relationships which
838
+ # will add inferred foreign key properties.
839
+ #
840
+ # @return [undefined]
841
+ #
842
+ # @raise [IncompleteModelError]
843
+ # raised if the model has no properties
844
+ #
845
+ # @api private
846
+ def assert_valid_properties
847
+ repository_name = self.repository_name
848
+ if properties(repository_name).empty? &&
849
+ !relationships(repository_name).any? { |relationship| relationship.kind_of?(Associations::ManyToOne::Relationship) }
850
+ raise IncompleteModelError, "#{name} must have at least one property or many to one relationship to be valid"
851
+ end
852
+ end
853
+
854
+ # Test if the model has a valid key
855
+ #
856
+ # @return [undefined]
857
+ #
858
+ # @raise [IncompleteModelError]
859
+ # raised if the model does not have a valid key
860
+ #
861
+ # @api private
862
+ def assert_valid_key
863
+ if key(repository_name).empty?
864
+ raise IncompleteModelError, "#{name} must have a key to be valid"
865
+ end
866
+ end
867
+
810
868
  end # module Model
811
869
  end # module DataMapper
@@ -6,25 +6,22 @@ module DataMapper
6
6
  module Property
7
7
  Model.append_extensions self, DataMapper::Property::Lookup
8
8
 
9
- extend Chainable
10
-
11
9
  def self.extended(model)
12
10
  model.instance_variable_set(:@properties, {})
13
11
  model.instance_variable_set(:@field_naming_conventions, {})
14
12
  end
15
13
 
16
- chainable do
17
- def inherited(model)
18
- model.instance_variable_set(:@properties, {})
19
- model.instance_variable_set(:@field_naming_conventions, @field_naming_conventions.dup)
20
14
 
21
- @properties.each do |repository_name, properties|
22
- model_properties = model.properties(repository_name)
23
- properties.each { |property| model_properties << property }
24
- end
15
+ def inherited(model)
16
+ model.instance_variable_set(:@properties, {})
17
+ model.instance_variable_set(:@field_naming_conventions, @field_naming_conventions.dup)
25
18
 
26
- super
19
+ @properties.each do |repository_name, properties|
20
+ model_properties = model.properties(repository_name)
21
+ properties.each { |property| model_properties << property }
27
22
  end
23
+
24
+ super
28
25
  end
29
26
 
30
27
  # Defines a Property on the Resource
@@ -64,13 +61,16 @@ module DataMapper
64
61
 
65
62
  # Add property to the other mappings as well if this is for the default
66
63
  # repository.
64
+
67
65
  if repository_name == default_repository_name
68
- DataMapper::Ext::Hash.except(@properties, default_repository_name).each do |other_repository_name, properties|
66
+ other_repository_properties = DataMapper::Ext::Hash.except(@properties, default_repository_name)
67
+
68
+ other_repository_properties.each do |other_repository_name, properties|
69
69
  next if properties.named?(name)
70
70
 
71
71
  # make sure the property is created within the correct repository scope
72
72
  DataMapper.repository(other_repository_name) do
73
- properties << klass.new(self, name, options, type)
73
+ properties << klass.new(self, name, options)
74
74
  end
75
75
  end
76
76
  end
@@ -175,11 +175,10 @@ module DataMapper
175
175
 
176
176
  # @api private
177
177
  def key_conditions(repository, key)
178
- conditions = self.key(repository.name).zip(key.nil? ? [] : key)
179
- DataMapper::Ext::Array.to_hash(conditions)
178
+ Hash[ self.key(repository.name).zip(key.nil? ? [] : key) ]
180
179
  end
181
180
 
182
- private
181
+ private
183
182
 
184
183
  # Defines the anonymous module that is used to add properties.
185
184
  # Using a single module here prevents having a very large number
@@ -207,7 +206,7 @@ module DataMapper
207
206
  def #{name}
208
207
  return #{instance_variable_name} if defined?(#{instance_variable_name})
209
208
  property = properties[#{name.inspect}]
210
- #{instance_variable_name} = property ? persisted_state.get(property) : nil
209
+ #{instance_variable_name} = property ? persistence_state.get(property) : nil
211
210
  end
212
211
  RUBY
213
212
 
@@ -235,21 +234,19 @@ module DataMapper
235
234
  #{writer_visibility}
236
235
  def #{writer_name}(value)
237
236
  property = properties[#{name.inspect}]
238
- self.persisted_state = persisted_state.set(property, value)
239
- persisted_state.get(property)
237
+ self.persistence_state = persistence_state.set(property, value)
238
+ persistence_state.get(property)
240
239
  end
241
240
  RUBY
242
241
  end
243
242
 
244
- chainable do
245
- # @api public
246
- def method_missing(method, *args, &block)
247
- if property = properties(repository_name)[method]
248
- return property
249
- end
250
-
251
- super
243
+ # @api public
244
+ def method_missing(method, *args, &block)
245
+ if property = properties(repository_name)[method]
246
+ return property
252
247
  end
248
+
249
+ super
253
250
  end
254
251
  end # module Property
255
252
  end # module Model