dm-core 0.10.2 → 1.0.0.rc1

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 (183) hide show
  1. data/.gitignore +10 -1
  2. data/Gemfile +143 -0
  3. data/Rakefile +9 -5
  4. data/VERSION +1 -1
  5. data/dm-core.gemspec +160 -57
  6. data/lib/dm-core.rb +131 -56
  7. data/lib/dm-core/adapters.rb +98 -14
  8. data/lib/dm-core/adapters/abstract_adapter.rb +24 -4
  9. data/lib/dm-core/adapters/in_memory_adapter.rb +7 -2
  10. data/lib/dm-core/associations/many_to_many.rb +19 -30
  11. data/lib/dm-core/associations/many_to_one.rb +58 -42
  12. data/lib/dm-core/associations/one_to_many.rb +33 -23
  13. data/lib/dm-core/associations/one_to_one.rb +27 -11
  14. data/lib/dm-core/associations/relationship.rb +4 -4
  15. data/lib/dm-core/collection.rb +23 -16
  16. data/lib/dm-core/core_ext/array.rb +36 -0
  17. data/lib/dm-core/core_ext/hash.rb +30 -0
  18. data/lib/dm-core/core_ext/module.rb +46 -0
  19. data/lib/dm-core/core_ext/object.rb +31 -0
  20. data/lib/dm-core/core_ext/pathname.rb +20 -0
  21. data/lib/dm-core/core_ext/string.rb +22 -0
  22. data/lib/dm-core/core_ext/try_dup.rb +44 -0
  23. data/lib/dm-core/model.rb +88 -27
  24. data/lib/dm-core/model/hook.rb +75 -18
  25. data/lib/dm-core/model/property.rb +50 -9
  26. data/lib/dm-core/model/relationship.rb +31 -31
  27. data/lib/dm-core/model/scope.rb +3 -3
  28. data/lib/dm-core/property.rb +196 -516
  29. data/lib/dm-core/property/binary.rb +7 -0
  30. data/lib/dm-core/property/boolean.rb +35 -0
  31. data/lib/dm-core/property/class.rb +24 -0
  32. data/lib/dm-core/property/date.rb +47 -0
  33. data/lib/dm-core/property/date_time.rb +48 -0
  34. data/lib/dm-core/property/decimal.rb +43 -0
  35. data/lib/dm-core/property/discriminator.rb +48 -0
  36. data/lib/dm-core/property/float.rb +24 -0
  37. data/lib/dm-core/property/integer.rb +32 -0
  38. data/lib/dm-core/property/numeric.rb +43 -0
  39. data/lib/dm-core/property/object.rb +32 -0
  40. data/lib/dm-core/property/serial.rb +8 -0
  41. data/lib/dm-core/property/string.rb +49 -0
  42. data/lib/dm-core/property/text.rb +12 -0
  43. data/lib/dm-core/property/time.rb +48 -0
  44. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  45. data/lib/dm-core/property/typecast/time.rb +28 -0
  46. data/lib/dm-core/property_set.rb +10 -4
  47. data/lib/dm-core/query.rb +14 -37
  48. data/lib/dm-core/query/conditions/comparison.rb +8 -6
  49. data/lib/dm-core/query/conditions/operation.rb +33 -2
  50. data/lib/dm-core/query/operator.rb +2 -5
  51. data/lib/dm-core/query/path.rb +4 -6
  52. data/lib/dm-core/repository.rb +21 -6
  53. data/lib/dm-core/resource.rb +316 -133
  54. data/lib/dm-core/resource/state.rb +79 -0
  55. data/lib/dm-core/resource/state/clean.rb +40 -0
  56. data/lib/dm-core/resource/state/deleted.rb +30 -0
  57. data/lib/dm-core/resource/state/dirty.rb +86 -0
  58. data/lib/dm-core/resource/state/immutable.rb +34 -0
  59. data/lib/dm-core/resource/state/persisted.rb +29 -0
  60. data/lib/dm-core/resource/state/transient.rb +70 -0
  61. data/lib/dm-core/spec/lib/adapter_helpers.rb +52 -0
  62. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  63. data/{spec → lib/dm-core/spec}/lib/counter_adapter.rb +5 -1
  64. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  65. data/lib/dm-core/spec/lib/spec_helper.rb +68 -0
  66. data/lib/dm-core/spec/setup.rb +165 -0
  67. data/lib/dm-core/spec/{adapter_shared_spec.rb → shared/adapter_spec.rb} +21 -7
  68. data/{spec/public/shared/resource_shared_spec.rb → lib/dm-core/spec/shared/resource_spec.rb} +120 -83
  69. data/{spec/public/shared/sel_shared_spec.rb → lib/dm-core/spec/shared/sel_spec.rb} +5 -6
  70. data/lib/dm-core/support/assertions.rb +8 -0
  71. data/lib/dm-core/support/equalizer.rb +1 -0
  72. data/lib/dm-core/support/hook.rb +420 -0
  73. data/lib/dm-core/support/lazy_array.rb +453 -0
  74. data/lib/dm-core/support/local_object_space.rb +12 -0
  75. data/lib/dm-core/support/logger.rb +193 -6
  76. data/lib/dm-core/support/naming_conventions.rb +8 -8
  77. data/lib/dm-core/support/subject.rb +33 -0
  78. data/lib/dm-core/type.rb +4 -0
  79. data/lib/dm-core/types/boolean.rb +2 -0
  80. data/lib/dm-core/types/decimal.rb +9 -0
  81. data/lib/dm-core/types/discriminator.rb +2 -0
  82. data/lib/dm-core/types/object.rb +3 -0
  83. data/lib/dm-core/types/serial.rb +2 -0
  84. data/lib/dm-core/types/text.rb +2 -0
  85. data/lib/dm-core/version.rb +1 -1
  86. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +67 -0
  87. data/spec/public/model/hook_spec.rb +209 -0
  88. data/spec/public/model/property_spec.rb +35 -0
  89. data/spec/public/model/relationship_spec.rb +33 -20
  90. data/spec/public/model_spec.rb +142 -10
  91. data/spec/public/property/binary_spec.rb +14 -0
  92. data/spec/public/property/boolean_spec.rb +14 -0
  93. data/spec/public/property/class_spec.rb +20 -0
  94. data/spec/public/property/date_spec.rb +14 -0
  95. data/spec/public/property/date_time_spec.rb +14 -0
  96. data/spec/public/property/decimal_spec.rb +14 -0
  97. data/spec/public/{types → property}/discriminator_spec.rb +2 -12
  98. data/spec/public/property/float_spec.rb +14 -0
  99. data/spec/public/property/integer_spec.rb +14 -0
  100. data/spec/public/property/object_spec.rb +9 -17
  101. data/spec/public/property/serial_spec.rb +14 -0
  102. data/spec/public/property/string_spec.rb +14 -0
  103. data/spec/public/property/text_spec.rb +52 -0
  104. data/spec/public/property/time_spec.rb +14 -0
  105. data/spec/public/property_spec.rb +28 -87
  106. data/spec/public/resource_spec.rb +101 -0
  107. data/spec/public/sel_spec.rb +5 -15
  108. data/spec/public/shared/collection_shared_spec.rb +16 -30
  109. data/spec/public/shared/finder_shared_spec.rb +2 -4
  110. data/spec/public/shared/property_shared_spec.rb +176 -0
  111. data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
  112. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +2 -2
  113. data/spec/semipublic/associations/many_to_many_spec.rb +89 -0
  114. data/spec/semipublic/associations/many_to_one_spec.rb +24 -1
  115. data/spec/semipublic/associations/one_to_many_spec.rb +51 -0
  116. data/spec/semipublic/associations/one_to_one_spec.rb +49 -0
  117. data/spec/semipublic/associations/relationship_spec.rb +3 -3
  118. data/spec/semipublic/associations_spec.rb +1 -1
  119. data/spec/semipublic/property/binary_spec.rb +13 -0
  120. data/spec/semipublic/property/boolean_spec.rb +65 -0
  121. data/spec/semipublic/property/class_spec.rb +33 -0
  122. data/spec/semipublic/property/date_spec.rb +43 -0
  123. data/spec/semipublic/property/date_time_spec.rb +46 -0
  124. data/spec/semipublic/property/decimal_spec.rb +82 -0
  125. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  126. data/spec/semipublic/property/float_spec.rb +82 -0
  127. data/spec/semipublic/property/integer_spec.rb +82 -0
  128. data/spec/semipublic/property/serial_spec.rb +13 -0
  129. data/spec/semipublic/property/string_spec.rb +13 -0
  130. data/spec/semipublic/property/text_spec.rb +31 -0
  131. data/spec/semipublic/property/time_spec.rb +50 -0
  132. data/spec/semipublic/property_spec.rb +2 -532
  133. data/spec/semipublic/query/conditions/comparison_spec.rb +171 -169
  134. data/spec/semipublic/query/conditions/operation_spec.rb +53 -51
  135. data/spec/semipublic/query/path_spec.rb +17 -17
  136. data/spec/semipublic/query_spec.rb +47 -78
  137. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  138. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  139. data/spec/semipublic/resource/state/dirty_spec.rb +133 -0
  140. data/spec/semipublic/resource/state/immutable_spec.rb +99 -0
  141. data/spec/semipublic/resource/state/transient_spec.rb +128 -0
  142. data/spec/semipublic/resource/state_spec.rb +226 -0
  143. data/spec/semipublic/shared/property_shared_spec.rb +143 -0
  144. data/spec/semipublic/shared/resource_shared_spec.rb +16 -15
  145. data/spec/semipublic/shared/resource_state_shared_spec.rb +78 -0
  146. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  147. data/spec/spec_helper.rb +21 -97
  148. data/spec/support/types/huge_integer.rb +17 -0
  149. data/spec/unit/array_spec.rb +48 -0
  150. data/spec/unit/hash_spec.rb +35 -0
  151. data/spec/unit/hook_spec.rb +1234 -0
  152. data/spec/unit/lazy_array_spec.rb +1959 -0
  153. data/spec/unit/module_spec.rb +70 -0
  154. data/spec/unit/object_spec.rb +37 -0
  155. data/spec/unit/try_dup_spec.rb +45 -0
  156. data/tasks/local_gemfile.rake +18 -0
  157. data/tasks/spec.rake +0 -3
  158. metadata +197 -71
  159. data/deps.rip +0 -2
  160. data/lib/dm-core/adapters/data_objects_adapter.rb +0 -712
  161. data/lib/dm-core/adapters/mysql_adapter.rb +0 -42
  162. data/lib/dm-core/adapters/oracle_adapter.rb +0 -229
  163. data/lib/dm-core/adapters/postgres_adapter.rb +0 -22
  164. data/lib/dm-core/adapters/sqlite3_adapter.rb +0 -17
  165. data/lib/dm-core/adapters/sqlserver_adapter.rb +0 -114
  166. data/lib/dm-core/adapters/yaml_adapter.rb +0 -111
  167. data/lib/dm-core/core_ext/enumerable.rb +0 -28
  168. data/lib/dm-core/migrations.rb +0 -1427
  169. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +0 -366
  170. data/lib/dm-core/transaction.rb +0 -508
  171. data/lib/dm-core/types/paranoid_boolean.rb +0 -42
  172. data/lib/dm-core/types/paranoid_datetime.rb +0 -41
  173. data/spec/lib/adapter_helpers.rb +0 -105
  174. data/spec/lib/collection_helpers.rb +0 -18
  175. data/spec/lib/pending_helpers.rb +0 -46
  176. data/spec/public/migrations_spec.rb +0 -503
  177. data/spec/public/transaction_spec.rb +0 -153
  178. data/spec/semipublic/adapters/mysql_adapter_spec.rb +0 -17
  179. data/spec/semipublic/adapters/oracle_adapter_spec.rb +0 -194
  180. data/spec/semipublic/adapters/postgres_adapter_spec.rb +0 -17
  181. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +0 -17
  182. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +0 -17
  183. data/spec/semipublic/adapters/yaml_adapter_spec.rb +0 -12
@@ -6,9 +6,10 @@ module DataMapper
6
6
  # (1 to 1, 1 to n, n to m) implements a subclass of this class
7
7
  # with methods like get and set overridden.
8
8
  class Relationship
9
- include Extlib::Assertions
9
+ include DataMapper::Assertions
10
+ include Subject
10
11
 
11
- OPTIONS = [ :child_repository_name, :parent_repository_name, :child_key, :parent_key, :min, :max, :inverse, :reader_visibility, :writer_visibility ].to_set
12
+ OPTIONS = [ :child_repository_name, :parent_repository_name, :child_key, :parent_key, :min, :max, :inverse, :reader_visibility, :writer_visibility, :default ].to_set
12
13
 
13
14
  # Relationship name
14
15
  #
@@ -319,8 +320,6 @@ module DataMapper
319
320
  #
320
321
  # @api semipublic
321
322
  def loaded?(resource)
322
- assert_kind_of 'resource', resource, source_model
323
-
324
323
  resource.instance_variable_defined?(instance_variable_name)
325
324
  end
326
325
 
@@ -449,6 +448,7 @@ module DataMapper
449
448
  @max = @options[:max]
450
449
  @reader_visibility = @options.fetch(:reader_visibility, :public)
451
450
  @writer_visibility = @options.fetch(:writer_visibility, :public)
451
+ @default = @options.fetch(:default, nil)
452
452
 
453
453
  # TODO: normalize the @query to become :conditions => AndOperation
454
454
  # - Property/Relationship/Path should be left alone
@@ -352,8 +352,12 @@ module DataMapper
352
352
  def at(offset)
353
353
  if loaded? || partially_loaded?(offset)
354
354
  super
355
- elsif offset >= 0
355
+ elsif offset == 0
356
+ first
357
+ elsif offset > 0
356
358
  first(:offset => offset)
359
+ elsif offset == -1
360
+ last
357
361
  else
358
362
  last(:offset => offset.abs - 1)
359
363
  end
@@ -787,7 +791,7 @@ module DataMapper
787
791
  #
788
792
  # @api public
789
793
  def create(attributes = {})
790
- _create(true, attributes)
794
+ _create(attributes)
791
795
  end
792
796
 
793
797
  # Create a Resource in the Collection, bypassing hooks
@@ -800,7 +804,7 @@ module DataMapper
800
804
  #
801
805
  # @api public
802
806
  def create!(attributes = {})
803
- _create(false, attributes)
807
+ _create(attributes, false)
804
808
  end
805
809
 
806
810
  # Update every Resource in the Collection
@@ -814,7 +818,7 @@ module DataMapper
814
818
  # true if the resources were successfully updated
815
819
  #
816
820
  # @api public
817
- def update(attributes = {})
821
+ def update(attributes)
818
822
  assert_update_clean_only(:update)
819
823
 
820
824
  dirty_attributes = model.new(attributes).dirty_attributes
@@ -832,7 +836,7 @@ module DataMapper
832
836
  # true if the resources were successfully updated
833
837
  #
834
838
  # @api public
835
- def update!(attributes = {})
839
+ def update!(attributes)
836
840
  assert_update_clean_only(:update!)
837
841
 
838
842
  model = self.model
@@ -866,7 +870,7 @@ module DataMapper
866
870
  #
867
871
  # @api public
868
872
  def save
869
- _save(true)
873
+ _save
870
874
  end
871
875
 
872
876
  # Save every Resource in the Collection bypassing validation
@@ -915,7 +919,10 @@ module DataMapper
915
919
  return false
916
920
  end
917
921
 
918
- each { |resource| resource.reset }
922
+ each do |resource|
923
+ resource.persisted_state = Resource::State::Immutable.new(resource)
924
+ end
925
+
919
926
  clear
920
927
  else
921
928
  mark_loaded
@@ -1192,8 +1199,8 @@ module DataMapper
1192
1199
 
1193
1200
  # Creates a resource in the collection
1194
1201
  #
1195
- # @param [Boolean] safe
1196
- # Whether to use the safe or unsafe create
1202
+ # @param [Boolean] execute_hooks
1203
+ # Whether to execute hooks or not
1197
1204
  # @param [Hash] attributes
1198
1205
  # Attributes with which to create the new resource
1199
1206
  #
@@ -1201,8 +1208,8 @@ module DataMapper
1201
1208
  # a saved Resource
1202
1209
  #
1203
1210
  # @api private
1204
- def _create(safe, attributes)
1205
- resource = repository.scope { model.send(safe ? :create : :create!, default_attributes.merge(attributes)) }
1211
+ def _create(attributes, execute_hooks = true)
1212
+ resource = repository.scope { model.send(execute_hooks ? :create : :create!, default_attributes.merge(attributes)) }
1206
1213
  self << resource if resource.saved?
1207
1214
  resource
1208
1215
  end
@@ -1220,18 +1227,18 @@ module DataMapper
1220
1227
 
1221
1228
  # Saves a collection
1222
1229
  #
1223
- # @param [Symbol] method
1224
- # The name of the Resource method to save the collection with
1230
+ # @param [Boolean] execute_hooks
1231
+ # Whether to execute hooks or not
1225
1232
  #
1226
1233
  # @return [Boolean]
1227
1234
  # Returns true if collection was updated
1228
1235
  #
1229
1236
  # @api private
1230
- def _save(safe)
1237
+ def _save(execute_hooks = true)
1231
1238
  loaded_entries = self.loaded_entries
1232
1239
  loaded_entries.each { |resource| set_default_attributes(resource) }
1233
1240
  @removed.clear
1234
- loaded_entries.all? { |resource| resource.__send__(safe ? :save : :save!) }
1241
+ loaded_entries.all? { |resource| resource.__send__(execute_hooks ? :save : :save!) }
1235
1242
  end
1236
1243
 
1237
1244
  # Returns default values to initialize new Resources in the Collection
@@ -1436,7 +1443,7 @@ module DataMapper
1436
1443
 
1437
1444
  if model.model_method_defined?(method)
1438
1445
  delegate_to_model(method, *args, &block)
1439
- elsif relationship = relationships[method] || relationships[method.to_s.singular.to_sym]
1446
+ elsif relationship = relationships[method] || relationships[method.to_s.singularize.to_sym]
1440
1447
  delegate_to_relationship(relationship, *args)
1441
1448
  else
1442
1449
  super
@@ -0,0 +1,36 @@
1
+ class Array
2
+
3
+ ##
4
+ # Transforms an Array of key/value pairs into a Hash
5
+ #
6
+ # This is a better idiom than using Hash[*array.flatten] in Ruby 1.8.6
7
+ # because it is not possible to limit the flattening to a single
8
+ # level.
9
+ #
10
+ # @return [Hash]
11
+ # A Hash where each entry in the Array is turned into a key/value
12
+ #
13
+ # @api public
14
+ def to_hash
15
+ h = {}
16
+ each { |k,v| h[k] = v }
17
+ h
18
+ end
19
+
20
+ ##
21
+ # Transforms an Array of key/value pairs into a Mash
22
+ #
23
+ # This is a better idiom than using Mash[*array.flatten] in Ruby 1.8.6
24
+ # because it is not possible to limit the flattening to a single
25
+ # level.
26
+ #
27
+ # @return [Mash]
28
+ # A Hash where each entry in the Array is turned into a key/value
29
+ #
30
+ # @api public
31
+ def to_mash
32
+ m = Mash.new
33
+ each { |k,v| m[k] = v }
34
+ m
35
+ end
36
+ end # class Array
@@ -0,0 +1,30 @@
1
+ class Hash
2
+
3
+ ##
4
+ # Create a hash with *only* key/value pairs in receiver and +allowed+
5
+ #
6
+ # { :one => 1, :two => 2, :three => 3 }.only(:one) #=> { :one => 1 }
7
+ #
8
+ # @param [Array[String, Symbol]] *allowed The hash keys to include.
9
+ #
10
+ # @return [Hash] A new hash with only the selected keys.
11
+ #
12
+ # @api public
13
+ def only(*allowed)
14
+ hash = {}
15
+ allowed.each {|k| hash[k] = self[k] if self.has_key?(k) }
16
+ hash
17
+ end
18
+
19
+ # Convert to Mash. This class has semantics of ActiveSupport's
20
+ # HashWithIndifferentAccess and we only have it so that people can write
21
+ # params[:key] instead of params['key'].
22
+ #
23
+ # @return [Mash] This hash as a Mash for string or symbol key access.
24
+ def to_mash
25
+ hash = Mash.new(self)
26
+ hash.default = default
27
+ hash
28
+ end
29
+
30
+ end
@@ -0,0 +1,46 @@
1
+ require 'dm-core/core_ext/object'
2
+
3
+ class Module
4
+
5
+ def find_const(const_name)
6
+ if const_name[0..1] == '::'
7
+ Object.full_const_get(const_name[2..-1])
8
+ else
9
+ nested_const_lookup(const_name)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ # Doesn't do any caching since constants can change with remove_const
16
+ def nested_const_lookup(const_name)
17
+ unless equal?(Object)
18
+ constants = []
19
+
20
+ name.split('::').each do |part|
21
+ const = constants.last || Object
22
+ constants << const.const_get(part)
23
+ end
24
+
25
+ parts = const_name.split('::')
26
+
27
+ # from most to least specific constant, use each as a base and try
28
+ # to find a constant with the name const_name within them
29
+ constants.reverse_each do |const|
30
+ # return the nested constant if available
31
+ return const if parts.all? do |part|
32
+ const = if RUBY_VERSION >= '1.9.0'
33
+ const.const_defined?(part, false) ? const.const_get(part, false) : nil
34
+ else
35
+ const.const_defined?(part) ? const.const_get(part) : nil
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # no relative constant found, fallback to an absolute lookup and
42
+ # use const_missing if not found
43
+ Object.full_const_get(const_name)
44
+ end
45
+
46
+ end
@@ -0,0 +1,31 @@
1
+ class Object
2
+
3
+ # @param name<String> The name of the constant to get, e.g. "Merb::Router".
4
+ #
5
+ # @return [Object] The constant corresponding to the name.
6
+ def full_const_get(name)
7
+ list = name.split("::")
8
+ list.shift if list.first.blank?
9
+ obj = self
10
+ list.each do |x|
11
+ # This is required because const_get tries to look for constants in the
12
+ # ancestor chain, but we only want constants that are HERE
13
+ obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
14
+ end
15
+ obj
16
+ end
17
+
18
+ # @param name<String> The name of the constant to get, e.g. "Merb::Router".
19
+ # @param value<Object> The value to assign to the constant.
20
+ #
21
+ # @return [Object] The constant corresponding to the name.
22
+ def full_const_set(name, value)
23
+ list = name.split("::")
24
+ toplevel = list.first.blank?
25
+ list.shift if toplevel
26
+ last = list.pop
27
+ obj = list.empty? ? Object : Object.full_const_get(list.join("::"))
28
+ obj.const_set(last, value) if obj && !obj.const_defined?(last)
29
+ end
30
+
31
+ end
@@ -0,0 +1,20 @@
1
+ class Pathname
2
+ # Append path segments and expand to absolute path
3
+ #
4
+ # file = Pathname(Dir.pwd) / "subdir1" / :subdir2 / "filename.ext"
5
+ #
6
+ # @param [Pathname, String, #to_s] path path segment to concatenate with receiver
7
+ #
8
+ # @return [Pathname]
9
+ # receiver with _path_ appended and expanded to an absolute path
10
+ #
11
+ # @api public
12
+ def /(path)
13
+ (self + path).expand_path
14
+ end
15
+
16
+ # alias to_s to to_str when to_str not defined
17
+ unless public_instance_methods(false).any? { |m| m.to_sym == :to_str }
18
+ alias to_str to_s
19
+ end
20
+ end # class Pathname
@@ -0,0 +1,22 @@
1
+ class String
2
+
3
+ ##
4
+ # Replace sequences of whitespace (including newlines) with either
5
+ # a single space or remove them entirely (according to param _spaced_)
6
+ #
7
+ # <<QUERY.compress_lines
8
+ # SELECT name
9
+ # FROM users
10
+ # QUERY => "SELECT name FROM users"
11
+ #
12
+ # @param [TrueClass, FalseClass] spaced (default=true)
13
+ # Determines whether returned string has whitespace collapsed or removed
14
+ #
15
+ # @return [String] Receiver with whitespace (including newlines) replaced
16
+ #
17
+ # @api public
18
+ def compress_lines(spaced = true)
19
+ split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
20
+ end
21
+
22
+ end
@@ -0,0 +1,44 @@
1
+ class Object
2
+ # Override this in a child if it cannot be dup'ed
3
+ #
4
+ # @return [Object]
5
+ def try_dup
6
+ self.dup
7
+ end
8
+ end
9
+
10
+ class TrueClass
11
+ def try_dup
12
+ self
13
+ end
14
+ end
15
+
16
+ class FalseClass
17
+ def try_dup
18
+ self
19
+ end
20
+ end
21
+
22
+ class Module
23
+ def try_dup
24
+ self
25
+ end
26
+ end
27
+
28
+ class NilClass
29
+ def try_dup
30
+ self
31
+ end
32
+ end
33
+
34
+ class Numeric
35
+ def try_dup
36
+ self
37
+ end
38
+ end
39
+
40
+ class Symbol
41
+ def try_dup
42
+ self
43
+ end
44
+ end
data/lib/dm-core/model.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # TODO: add Model#create!, Model#update, Model#update!, Model#destroy and Model#destroy!
2
2
 
3
+ # TODO: DRY up raise_on_save_failure with attr_accessor_with_default
4
+ # once AS branch is merged in
5
+
3
6
  module DataMapper
4
7
  module Model
5
8
  extend Chainable
@@ -30,7 +33,7 @@ module DataMapper
30
33
  warn "Passing in +storage_name+ to #{name}.new is deprecated (#{caller[0]})"
31
34
  model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
32
35
  def self.default_storage_name
33
- #{Extlib::Inflection.classify(storage_name).inspect}.freeze
36
+ #{ActiveSupport::Inflector.classify(storage_name).inspect}.freeze
34
37
  end
35
38
  RUBY
36
39
  end
@@ -72,6 +75,68 @@ module DataMapper
72
75
  # @api semipublic
73
76
  attr_reader :descendants
74
77
 
78
+ # Return if Resource#save should raise an exception on save failures (globally)
79
+ #
80
+ # This is false by default.
81
+ #
82
+ # DataMapper::Model.raise_on_save_failure # => false
83
+ #
84
+ # @return [Boolean]
85
+ # true if a failure in Resource#save should raise an exception
86
+ #
87
+ # @api public
88
+ def self.raise_on_save_failure
89
+ if defined?(@raise_on_save_failure)
90
+ @raise_on_save_failure
91
+ else
92
+ false
93
+ end
94
+ end
95
+
96
+ # Specify if Resource#save should raise an exception on save failures (globally)
97
+ #
98
+ # @param [Boolean]
99
+ # a boolean that if true will cause Resource#save to raise an exception
100
+ #
101
+ # @return [Boolean]
102
+ # true if a failure in Resource#save should raise an exception
103
+ #
104
+ # @api public
105
+ def self.raise_on_save_failure=(raise_on_save_failure)
106
+ @raise_on_save_failure = raise_on_save_failure
107
+ end
108
+
109
+ # Return if Resource#save should raise an exception on save failures (per-model)
110
+ #
111
+ # This delegates to DataMapper::Model.raise_on_save_failure by default.
112
+ #
113
+ # User.raise_on_save_failure # => false
114
+ #
115
+ # @return [Boolean]
116
+ # true if a failure in Resource#save should raise an exception
117
+ #
118
+ # @api public
119
+ def raise_on_save_failure
120
+ if defined?(@raise_on_save_failure)
121
+ @raise_on_save_failure
122
+ else
123
+ DataMapper::Model.raise_on_save_failure
124
+ end
125
+ end
126
+
127
+ # Specify if Resource#save should raise an exception on save failures (per-model)
128
+ #
129
+ # @param [Boolean]
130
+ # a boolean that if true will cause Resource#save to raise an exception
131
+ #
132
+ # @return [Boolean]
133
+ # true if a failure in Resource#save should raise an exception
134
+ #
135
+ # @api public
136
+ def raise_on_save_failure=(raise_on_save_failure)
137
+ @raise_on_save_failure = raise_on_save_failure
138
+ end
139
+
75
140
  # Appends a module for inclusion into the model class after Resource.
76
141
  #
77
142
  # This is a useful way to extend Resource while still retaining a
@@ -147,6 +212,8 @@ module DataMapper
147
212
  model.instance_variable_set(:@default_order, {})
148
213
  model.instance_variable_set(:@descendants, descendants.class.new(model, descendants))
149
214
 
215
+ model.extend(Chainable)
216
+
150
217
  extra_extensions.each { |mod| model.extend(mod) }
151
218
  extra_inclusions.each { |mod| model.send(:include, mod) }
152
219
  end
@@ -163,13 +230,6 @@ module DataMapper
163
230
  model.instance_variable_set(:@storage_names, @storage_names.dup)
164
231
  model.instance_variable_set(:@default_order, @default_order.dup)
165
232
  model.instance_variable_set(:@descendants, descendants.class.new(model, descendants))
166
-
167
- # TODO: move this into dm-validations
168
- if respond_to?(:validators)
169
- validators.contexts.each do |context, validators|
170
- model.validators.context(context).concat(validators)
171
- end
172
- end
173
233
  end
174
234
  end
175
235
 
@@ -410,7 +470,7 @@ module DataMapper
410
470
  #
411
471
  # @api public
412
472
  def create(attributes = {})
413
- _create(true, attributes)
473
+ _create(attributes)
414
474
  end
415
475
 
416
476
  # Create a Resource, bypassing hooks
@@ -423,7 +483,7 @@ module DataMapper
423
483
  #
424
484
  # @api public
425
485
  def create!(attributes = {})
426
- _create(false, attributes)
486
+ _create(attributes, false)
427
487
  end
428
488
 
429
489
  # Copy a set of records from one repository to another.
@@ -452,7 +512,7 @@ module DataMapper
452
512
 
453
513
  all(query.merge(:repository => source_repository_name)).each do |resource|
454
514
  new_resource = new
455
- query[:fields].each { |property| property.set(new_resource, property.get(resource)) }
515
+ query[:fields].each { |property| new_resource.__send__("#{property.name}=", property.get(resource)) }
456
516
  resources << new_resource if new_resource.save
457
517
  end
458
518
 
@@ -490,7 +550,7 @@ module DataMapper
490
550
  record = record.dup
491
551
  field_map.each { |property, field| record[property] = record.delete(field) if record.key?(field) }
492
552
 
493
- model = discriminator && record[discriminator] || self
553
+ model = discriminator && discriminator.load(record[discriminator]) || self
494
554
  model_key = model.key(repository_name)
495
555
 
496
556
  resource = if model_key.valid?(key_values = record.values_at(*model_key))
@@ -507,9 +567,7 @@ module DataMapper
507
567
 
508
568
  # TODO: typecasting should happen inside the Adapter
509
569
  # and all values should come back as expected objects
510
- if property.custom?
511
- value = property.type.load(value, property)
512
- end
570
+ value = property.load(value)
513
571
 
514
572
  property.set!(resource, value)
515
573
  end
@@ -533,14 +591,15 @@ module DataMapper
533
591
  end
534
592
 
535
593
  resource.instance_variable_set(:@_repository, repository)
536
- resource.instance_variable_set(:@_saved, true)
537
594
 
538
595
  if identity_map
596
+ resource.persisted_state = Resource::State::Clean.new(resource) unless resource.persisted_state?
597
+
539
598
  # defer setting the IdentityMap so second level caches can
540
599
  # record the state of the resource after loaded
541
600
  identity_map[key_values] = resource
542
601
  else
543
- resource.instance_variable_set(:@_readonly, true)
602
+ resource.persisted_state = Resource::State::Immutable.new(resource)
544
603
  end
545
604
 
546
605
  resource
@@ -622,9 +681,9 @@ module DataMapper
622
681
  private
623
682
 
624
683
  # @api private
625
- def _create(safe, attributes)
684
+ def _create(attributes, execute_hooks = true)
626
685
  resource = new(attributes)
627
- resource.__send__(safe ? :save : :save!)
686
+ resource.__send__(execute_hooks ? :save : :save!)
628
687
  resource
629
688
  end
630
689
 
@@ -635,8 +694,10 @@ module DataMapper
635
694
  self
636
695
  elsif name == :Resource
637
696
  Resource
638
- elsif Types.const_defined?(name)
639
- Types.const_get(name)
697
+ elsif DataMapper::Property.const_defined?(name)
698
+ DataMapper::Property.const_get(name)
699
+ elsif DataMapper::Types.const_defined?(name)
700
+ DataMapper::Types.const_get(name)
640
701
  else
641
702
  super
642
703
  end
@@ -681,14 +742,14 @@ module DataMapper
681
742
  if self.query.repository == repository
682
743
  query
683
744
  else
684
- Query.new(repository, self, query.options)
745
+ repository.new_query(self, query.options)
685
746
  end
686
747
  end
687
748
  end
688
749
 
689
750
  # @api private
690
- def assert_valid # :nodoc:
691
- return if @valid
751
+ def assert_valid(force = false) # :nodoc:
752
+ return if @valid && !force
692
753
  @valid = true
693
754
 
694
755
  name = self.name
@@ -704,8 +765,8 @@ module DataMapper
704
765
  end
705
766
 
706
767
  # initialize join models and target keys
707
- @relationships.each_value do |relationships|
708
- relationships.each_value do |relationship|
768
+ @relationships.values.each do |relationships|
769
+ relationships.values.each do |relationship|
709
770
  relationship.child_key
710
771
  relationship.through if relationship.respond_to?(:through)
711
772
  relationship.via if relationship.respond_to?(:via)
@@ -715,7 +776,7 @@ module DataMapper
715
776
 
716
777
  # @api private
717
778
  def model_methods
718
- @model_methods ||= ancestor_instance_methods { |mod| mod.meta_class }
779
+ @model_methods ||= ancestor_instance_methods { |mod| mod.singleton_class }
719
780
  end
720
781
 
721
782
  # @api private