dm-core 0.10.2 → 1.0.0.rc1

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