sbf-dm-core 1.3.0.beta

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 (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +44 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +468 -0
  7. data/.travis.yml +57 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +70 -0
  10. data/LICENSE +20 -0
  11. data/README.md +269 -0
  12. data/Rakefile +4 -0
  13. data/dm-core.gemspec +21 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
  15. data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
  16. data/lib/dm-core/adapters.rb +249 -0
  17. data/lib/dm-core/associations/many_to_many.rb +477 -0
  18. data/lib/dm-core/associations/many_to_one.rb +282 -0
  19. data/lib/dm-core/associations/one_to_many.rb +332 -0
  20. data/lib/dm-core/associations/one_to_one.rb +84 -0
  21. data/lib/dm-core/associations/relationship.rb +650 -0
  22. data/lib/dm-core/backwards.rb +11 -0
  23. data/lib/dm-core/collection.rb +1486 -0
  24. data/lib/dm-core/core_ext/kernel.rb +21 -0
  25. data/lib/dm-core/core_ext/pathname.rb +4 -0
  26. data/lib/dm-core/core_ext/symbol.rb +10 -0
  27. data/lib/dm-core/identity_map.rb +6 -0
  28. data/lib/dm-core/model/hook.rb +99 -0
  29. data/lib/dm-core/model/is.rb +30 -0
  30. data/lib/dm-core/model/property.rb +244 -0
  31. data/lib/dm-core/model/relationship.rb +366 -0
  32. data/lib/dm-core/model/scope.rb +87 -0
  33. data/lib/dm-core/model.rb +876 -0
  34. data/lib/dm-core/property/binary.rb +19 -0
  35. data/lib/dm-core/property/boolean.rb +35 -0
  36. data/lib/dm-core/property/class.rb +23 -0
  37. data/lib/dm-core/property/date.rb +45 -0
  38. data/lib/dm-core/property/date_time.rb +44 -0
  39. data/lib/dm-core/property/decimal.rb +47 -0
  40. data/lib/dm-core/property/discriminator.rb +40 -0
  41. data/lib/dm-core/property/float.rb +27 -0
  42. data/lib/dm-core/property/integer.rb +32 -0
  43. data/lib/dm-core/property/invalid_value_error.rb +17 -0
  44. data/lib/dm-core/property/lookup.rb +26 -0
  45. data/lib/dm-core/property/numeric.rb +35 -0
  46. data/lib/dm-core/property/object.rb +33 -0
  47. data/lib/dm-core/property/serial.rb +13 -0
  48. data/lib/dm-core/property/string.rb +47 -0
  49. data/lib/dm-core/property/text.rb +12 -0
  50. data/lib/dm-core/property/time.rb +46 -0
  51. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  52. data/lib/dm-core/property/typecast/time.rb +33 -0
  53. data/lib/dm-core/property.rb +856 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query/conditions/comparison.rb +886 -0
  56. data/lib/dm-core/query/conditions/operation.rb +710 -0
  57. data/lib/dm-core/query/direction.rb +33 -0
  58. data/lib/dm-core/query/operator.rb +34 -0
  59. data/lib/dm-core/query/path.rb +113 -0
  60. data/lib/dm-core/query/sort.rb +38 -0
  61. data/lib/dm-core/query.rb +1352 -0
  62. data/lib/dm-core/relationship_set.rb +69 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
  65. data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
  66. data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
  67. data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
  68. data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
  69. data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
  70. data/lib/dm-core/resource/persistence_state.rb +70 -0
  71. data/lib/dm-core/resource.rb +1220 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +164 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +388 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +13 -0
  102. data/lib/dm-core/support/logger.rb +201 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +109 -0
  105. data/lib/dm-core/support/ordered_set.rb +381 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +251 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/lib/dm-core.rb +274 -0
  110. data/script/performance.rb +275 -0
  111. data/script/profile.rb +218 -0
  112. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  113. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +69 -0
  114. data/spec/public/associations/many_to_many_spec.rb +197 -0
  115. data/spec/public/associations/many_to_one_spec.rb +83 -0
  116. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  117. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  118. data/spec/public/associations/one_to_many_spec.rb +81 -0
  119. data/spec/public/associations/one_to_one_spec.rb +176 -0
  120. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  121. data/spec/public/collection_spec.rb +69 -0
  122. data/spec/public/finalize_spec.rb +77 -0
  123. data/spec/public/model/hook_spec.rb +245 -0
  124. data/spec/public/model/property_spec.rb +91 -0
  125. data/spec/public/model/relationship_spec.rb +1040 -0
  126. data/spec/public/model_spec.rb +456 -0
  127. data/spec/public/property/binary_spec.rb +43 -0
  128. data/spec/public/property/boolean_spec.rb +21 -0
  129. data/spec/public/property/class_spec.rb +27 -0
  130. data/spec/public/property/date_spec.rb +21 -0
  131. data/spec/public/property/date_time_spec.rb +21 -0
  132. data/spec/public/property/decimal_spec.rb +23 -0
  133. data/spec/public/property/discriminator_spec.rb +134 -0
  134. data/spec/public/property/float_spec.rb +22 -0
  135. data/spec/public/property/integer_spec.rb +22 -0
  136. data/spec/public/property/object_spec.rb +117 -0
  137. data/spec/public/property/serial_spec.rb +22 -0
  138. data/spec/public/property/string_spec.rb +21 -0
  139. data/spec/public/property/text_spec.rb +62 -0
  140. data/spec/public/property/time_spec.rb +21 -0
  141. data/spec/public/property_spec.rb +333 -0
  142. data/spec/public/resource/state_spec.rb +72 -0
  143. data/spec/public/resource_spec.rb +289 -0
  144. data/spec/public/sel_spec.rb +53 -0
  145. data/spec/public/setup_spec.rb +145 -0
  146. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  147. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  148. data/spec/public/shared/collection_shared_spec.rb +1637 -0
  149. data/spec/public/shared/finder_shared_spec.rb +1647 -0
  150. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  151. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  152. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  153. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  154. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  155. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  156. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  157. data/spec/semipublic/associations_spec.rb +177 -0
  158. data/spec/semipublic/collection_spec.rb +110 -0
  159. data/spec/semipublic/model_spec.rb +96 -0
  160. data/spec/semipublic/property/binary_spec.rb +13 -0
  161. data/spec/semipublic/property/boolean_spec.rb +47 -0
  162. data/spec/semipublic/property/class_spec.rb +33 -0
  163. data/spec/semipublic/property/date_spec.rb +43 -0
  164. data/spec/semipublic/property/date_time_spec.rb +46 -0
  165. data/spec/semipublic/property/decimal_spec.rb +83 -0
  166. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  167. data/spec/semipublic/property/float_spec.rb +82 -0
  168. data/spec/semipublic/property/integer_spec.rb +82 -0
  169. data/spec/semipublic/property/lookup_spec.rb +29 -0
  170. data/spec/semipublic/property/serial_spec.rb +13 -0
  171. data/spec/semipublic/property/string_spec.rb +13 -0
  172. data/spec/semipublic/property/text_spec.rb +31 -0
  173. data/spec/semipublic/property/time_spec.rb +50 -0
  174. data/spec/semipublic/property_spec.rb +114 -0
  175. data/spec/semipublic/query/conditions/comparison_spec.rb +1502 -0
  176. data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
  177. data/spec/semipublic/query/path_spec.rb +471 -0
  178. data/spec/semipublic/query_spec.rb +3665 -0
  179. data/spec/semipublic/resource/state/clean_spec.rb +89 -0
  180. data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
  181. data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
  182. data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
  183. data/spec/semipublic/resource/state/transient_spec.rb +163 -0
  184. data/spec/semipublic/resource/state_spec.rb +230 -0
  185. data/spec/semipublic/resource_spec.rb +23 -0
  186. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  187. data/spec/semipublic/shared/resource_shared_spec.rb +198 -0
  188. data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
  189. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  190. data/spec/spec_helper.rb +34 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +27 -0
  248. data/spec/unit/hook_spec.rb +1216 -0
  249. data/spec/unit/inflections_spec.rb +14 -0
  250. data/spec/unit/lazy_array_spec.rb +1949 -0
  251. data/spec/unit/mash_spec.rb +289 -0
  252. data/spec/unit/module_spec.rb +70 -0
  253. data/spec/unit/object_spec.rb +38 -0
  254. data/spec/unit/try_dup_spec.rb +46 -0
  255. data/tasks/ci.rake +1 -0
  256. data/tasks/spec.rake +18 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +323 -0
@@ -0,0 +1,48 @@
1
+ module DataMapper
2
+ module Equalizer
3
+ def equalize(*methods)
4
+ define_eql_method(methods)
5
+ define_equivalent_method(methods)
6
+ define_hash_method(methods)
7
+ end
8
+
9
+ private
10
+
11
+ def define_eql_method(methods)
12
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
13
+ def eql?(other)
14
+ return true if equal?(other)
15
+ instance_of?(other.class) &&
16
+ #{methods.map { |method| "#{method}.eql?(other.#{method})" }.join(' && ')}
17
+ end
18
+ RUBY
19
+ end
20
+
21
+ def define_equivalent_method(methods)
22
+ respond_to = []
23
+ equivalent = []
24
+
25
+ methods.each do |method|
26
+ respond_to << "other.respond_to?(#{method.inspect})"
27
+ equivalent << "#{method} == other.#{method}"
28
+ end
29
+
30
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
+ def ==(other)
32
+ return true if equal?(other)
33
+ return false unless kind_of?(other.class) || other.kind_of?(self.class)
34
+ #{respond_to.join(' && ')} &&
35
+ #{equivalent.join(' && ')}
36
+ end
37
+ RUBY
38
+ end
39
+
40
+ def define_hash_method(methods)
41
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
42
+ def hash
43
+ self.class.hash ^ #{methods.map { |method| "#{method}.hash" }.join(' ^ ')}
44
+ end
45
+ RUBY
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ module DataMapper; module Ext
2
+ module Array
3
+ # Transforms an Array of key/value pairs into a {Mash}.
4
+ #
5
+ # This is a better idiom than using Mash[*array.flatten] in Ruby 1.8.6
6
+ # because it is not possible to limit the flattening to a single
7
+ # level.
8
+ #
9
+ # @param [Array] array
10
+ # The array of key/value pairs to transform.
11
+ #
12
+ # @return [Mash]
13
+ # A {Mash} where each entry in the Array is turned into a key/value.
14
+ #
15
+ # @api semipublic
16
+ def self.to_mash(array)
17
+ m = Mash.new
18
+ array.each { |k,v| m[k] = v }
19
+ m
20
+ end
21
+ end # class Array
22
+ end; end
@@ -0,0 +1,25 @@
1
+ module DataMapper
2
+ module Ext
3
+ # Determines whether the specified +value+ is blank.
4
+ #
5
+ # An object is blank if it's false, empty, or a whitespace string.
6
+ # For example, "", " ", +nil+, [], and {} are blank.
7
+ #
8
+ # @api semipublic
9
+ def self.blank?(value)
10
+ return value.blank? if value.respond_to?(:blank?)
11
+ case value
12
+ when ::NilClass, ::FalseClass
13
+ true
14
+ when ::TrueClass, ::Numeric
15
+ false
16
+ when ::Array, ::Hash
17
+ value.empty?
18
+ when ::String
19
+ value !~ /\S/
20
+ else
21
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ module DataMapper; module Ext
2
+ module Hash
3
+ # Creates a hash with *only* the specified key/value pairs from +hash+.
4
+ #
5
+ # @param [Hash] hash The hash from which to pick the key/value pairs.
6
+ # @param [Array] *keys The hash keys to include.
7
+ #
8
+ # @return [Hash] A new hash with only the selected keys.
9
+ #
10
+ # @example
11
+ # hash = { :one => 1, :two => 2, :three => 3 }
12
+ # Ext::Hash.only(hash, :one, :two) # => { :one => 1, :two => 2 }
13
+ #
14
+ # @api semipublic
15
+ def self.only(hash, *keys)
16
+ h = {}
17
+ keys.each {|k| h[k] = hash[k] if hash.has_key?(k) }
18
+ h
19
+ end
20
+
21
+ # Returns a hash that includes everything but the given +keys+.
22
+ #
23
+ # @param [Hash] hash The hash from which to pick the key/value pairs.
24
+ # @param [Array] *keys The hash keys to exclude.
25
+ #
26
+ # @return [Hash] A new hash without the specified keys.
27
+ #
28
+ # @example
29
+ # hash = { :one => 1, :two => 2, :three => 3 }
30
+ # Ext::Hash.except(hash, :one, :two) # => { :three => 3 }
31
+ #
32
+ # @api semipublic
33
+ def self.except(hash, *keys)
34
+ self.except!(hash.dup, *keys)
35
+ end
36
+
37
+ # Removes the specified +keys+ from the given +hash+.
38
+ #
39
+ # @param [Hash] hash The hash to modify.
40
+ # @param [Array] *keys The hash keys to exclude.
41
+ #
42
+ # @return [Hash] +hash+
43
+ #
44
+ # @example
45
+ # hash = { :one => 1, :two => 2, :three => 3 }
46
+ # Ext::Hash.except!(hash, :one, :two)
47
+ # hash # => { :three => 3 }
48
+ #
49
+ # @api semipublic
50
+ def self.except!(hash, *keys)
51
+ keys.each { |key| hash.delete(key) }
52
+ hash
53
+ end
54
+
55
+ # Converts the specified +hash+ to a {Mash}.
56
+ #
57
+ # @param [Hash] hash The hash to convert.
58
+ # @return [Mash] The {Mash} for the specified +hash+.
59
+ #
60
+ # @api semipublic
61
+ def self.to_mash(hash)
62
+ h = Mash.new(hash)
63
+ h.default = hash.default
64
+ h
65
+ end
66
+ end
67
+ end; end
@@ -0,0 +1,47 @@
1
+ module DataMapper; module Ext
2
+ module Module
3
+
4
+ # @api semipublic
5
+ def self.find_const(mod, const_name)
6
+ if const_name[0..1] == '::'
7
+ DataMapper::Ext::Object.full_const_get(const_name[2..-1])
8
+ else
9
+ nested_const_lookup(mod, 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 self.nested_const_lookup(mod, const_name)
17
+ unless mod.equal?(::Object)
18
+ constants = []
19
+
20
+ mod.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
+ DataMapper::Ext::Object.full_const_get(const_name)
44
+ end
45
+
46
+ end
47
+ end; end
@@ -0,0 +1,57 @@
1
+ module DataMapper; module Ext
2
+ module Object
3
+ # Returns the value of the specified constant.
4
+ #
5
+ # @overload full_const_get(obj, name)
6
+ # Returns the value of the specified constant in +obj+.
7
+ # @param [Object] obj The root object used as origin.
8
+ # @param [String] name The name of the constant to get, e.g. "Merb::Router".
9
+ #
10
+ # @overload full_const_get(name)
11
+ # Returns the value of the fully-qualified constant.
12
+ # @param [String] name The name of the constant to get, e.g. "Merb::Router".
13
+ #
14
+ # @return [Object] The constant corresponding to +name+.
15
+ #
16
+ # @api semipublic
17
+ def self.full_const_get(obj, name = nil)
18
+ obj, name = ::Object, obj if name.nil?
19
+
20
+ list = name.split("::")
21
+ list.shift if DataMapper::Ext.blank?(list.first)
22
+ list.each do |x|
23
+ # This is required because const_get tries to look for constants in the
24
+ # ancestor chain, but we only want constants that are HERE
25
+ obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
26
+ end
27
+ obj
28
+ end
29
+
30
+ # Sets the specified constant to the given +value+.
31
+ #
32
+ # @overload full_const_set(obj, name)
33
+ # Sets the specified constant in +obj+ to the given +value+.
34
+ # @param [Object] obj The root object used as origin.
35
+ # @param [String] name The name of the constant to set, e.g. "Merb::Router".
36
+ # @param [Object] value The value to assign to the constant.
37
+ #
38
+ # @overload full_const_set(name)
39
+ # Sets the fully-qualified constant to the given +value+.
40
+ # @param [String] name The name of the constant to set, e.g. "Merb::Router".
41
+ # @param [Object] value The value to assign to the constant.
42
+ #
43
+ # @return [Object] The constant corresponding to +name+.
44
+ #
45
+ # @api semipublic
46
+ def self.full_const_set(obj, name, value = nil)
47
+ obj, name, value = ::Object, obj, name if value.nil?
48
+
49
+ list = name.split("::")
50
+ toplevel = DataMapper::Ext.blank?(list.first)
51
+ list.shift if toplevel
52
+ last = list.pop
53
+ obj = list.empty? ? ::Object : DataMapper::Ext::Object.full_const_get(list.join("::"))
54
+ obj.const_set(last, value) if obj && !obj.const_defined?(last)
55
+ end
56
+ end
57
+ end; end
@@ -0,0 +1,24 @@
1
+ module DataMapper; module Ext
2
+ module String
3
+ # Replace sequences of whitespace (including newlines) with either
4
+ # a single space or remove them entirely (according to param _spaced_).
5
+ #
6
+ # compress_lines(<<QUERY)
7
+ # SELECT name
8
+ # FROM users
9
+ # QUERY => "SELECT name FROM users"
10
+ #
11
+ # @param [String] string
12
+ # The input string.
13
+ #
14
+ # @param [TrueClass, FalseClass] spaced (default=true)
15
+ # Determines whether returned string has whitespace collapsed or removed.
16
+ #
17
+ # @return [String] The input string with whitespace (including newlines) replaced.
18
+ #
19
+ # @api semipublic
20
+ def self.compress_lines(string, spaced = true)
21
+ string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
22
+ end
23
+ end
24
+ end; end
@@ -0,0 +1,12 @@
1
+ module DataMapper
2
+ module Ext
3
+ def self.try_dup(value)
4
+ case value
5
+ when ::TrueClass, ::FalseClass, ::NilClass, ::Module, ::Numeric, ::Symbol
6
+ value
7
+ else
8
+ value.dup
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,388 @@
1
+ module DataMapper
2
+ #
3
+ # TODO: Write more documentation!
4
+ #
5
+ # Overview
6
+ # ========
7
+ #
8
+ # The Hook module is a very simple set of AOP helpers. Basically, it
9
+ # allows the developer to specify a method or block that should run
10
+ # before or after another method.
11
+ #
12
+ # Usage
13
+ # =====
14
+ #
15
+ # Halting The Hook Stack
16
+ #
17
+ # Inheritance
18
+ #
19
+ # Other Goodies
20
+ #
21
+ # Please bring up any issues regarding Hooks with carllerche on IRC
22
+ #
23
+ module Hook
24
+ def self.included(base)
25
+ base.extend(ClassMethods)
26
+ base.const_set('CLASS_HOOKS', {}) unless base.const_defined?('CLASS_HOOKS')
27
+ base.const_set('INSTANCE_HOOKS', {}) unless base.const_defined?('INSTANCE_HOOKS')
28
+ base.class_eval do
29
+ class << self
30
+ def method_added(name)
31
+ process_method_added(name, :instance)
32
+ super
33
+ end
34
+
35
+ def singleton_method_added(name)
36
+ process_method_added(name, :class)
37
+ super
38
+ end
39
+ end
40
+ end
41
+ super
42
+ end
43
+
44
+ module ClassMethods
45
+ extend DataMapper::LocalObjectSpace
46
+ include DataMapper::Assertions
47
+ # Inject code that executes before the target class method.
48
+ #
49
+ # @param target_method<Symbol> the name of the class method to inject before
50
+ # @param method_sym<Symbol> the name of the method to run before the
51
+ # target_method
52
+ # @param block<Block> the code to run before the target_method
53
+ #
54
+ # @note
55
+ # Either method_sym or block is required.
56
+ # -
57
+ # @api public
58
+ def before_class_method(target_method, method_sym = nil, &block)
59
+ install_hook :before, target_method, method_sym, :class, &block
60
+ end
61
+
62
+ #
63
+ # Inject code that executes after the target class method.
64
+ #
65
+ # @param target_method<Symbol> the name of the class method to inject after
66
+ # @param method_sym<Symbol> the name of the method to run after the target_method
67
+ # @param block<Block> the code to run after the target_method
68
+ #
69
+ # @note
70
+ # Either method_sym or block is required.
71
+ # -
72
+ # @api public
73
+ def after_class_method(target_method, method_sym = nil, &block)
74
+ install_hook :after, target_method, method_sym, :class, &block
75
+ end
76
+
77
+ #
78
+ # Inject code that executes before the target instance method.
79
+ #
80
+ # @param target_method<Symbol> the name of the instance method to inject before
81
+ # @param method_sym<Symbol> the name of the method to run before the
82
+ # target_method
83
+ # @param block<Block> the code to run before the target_method
84
+ #
85
+ # @note
86
+ # Either method_sym or block is required.
87
+ # -
88
+ # @api public
89
+ def before(target_method, method_sym = nil, &block)
90
+ install_hook :before, target_method, method_sym, :instance, &block
91
+ end
92
+
93
+ #
94
+ # Inject code that executes after the target instance method.
95
+ #
96
+ # @param target_method<Symbol> the name of the instance method to inject after
97
+ # @param method_sym<Symbol> the name of the method to run after the
98
+ # target_method
99
+ # @param block<Block> the code to run after the target_method
100
+ #
101
+ # @note
102
+ # Either method_sym or block is required.
103
+ # -
104
+ # @api public
105
+ def after(target_method, method_sym = nil, &block)
106
+ install_hook :after, target_method, method_sym, :instance, &block
107
+ end
108
+
109
+ # Register a class method as hook-able. Registering a method means that
110
+ # before hooks will be run immediately before the method is invoked and
111
+ # after hooks will be called immediately after the method is invoked.
112
+ #
113
+ # @param hooks <Symbol> The name of the class method that should
114
+ # be hook-able
115
+ # -
116
+ # @api public
117
+ def register_class_hooks(*hooks)
118
+ hooks.each { |hook| register_hook(hook, :class) }
119
+ end
120
+
121
+ # Register an instance method as hook-able. Registering a method means that
122
+ # before hooks will be run immediately before the method is invoked and
123
+ # after hooks will be called immediately after the method is invoked.
124
+ #
125
+ # @param hooks <Symbol> The name of the instance method that should
126
+ # be hook-able
127
+ # -
128
+ # @api public
129
+ def register_instance_hooks(*hooks)
130
+ hooks.each { |hook| register_hook(hook, :instance) }
131
+ end
132
+
133
+ # Not yet implemented
134
+ def reset_hook!(_target_method, _scope)
135
+ raise NotImplementedError
136
+ end
137
+
138
+ # --- Alright kids... the rest is internal stuff ---
139
+
140
+ # Returns the correct HOOKS Hash depending on whether we are
141
+ # working with class methods or instance methods
142
+ def hooks_with_scope(scope)
143
+ case scope
144
+ when :class then class_hooks
145
+ when :instance then instance_hooks
146
+ else raise ArgumentError, 'You need to pass :class or :instance as scope'
147
+ end
148
+ end
149
+
150
+ def class_hooks
151
+ const_get('CLASS_HOOKS')
152
+ end
153
+
154
+ def instance_hooks
155
+ const_get('INSTANCE_HOOKS')
156
+ end
157
+
158
+ # Registers a method as hook-able. Registering hooks involves the following
159
+ # process
160
+ #
161
+ # * Create a blank entry in the HOOK Hash for the method.
162
+ # * Define the methods that execute the before and after hook stack.
163
+ # These methods will be no-ops at first, but everytime a new hook is
164
+ # defined, the methods will be redefined to incorporate the new hook.
165
+ # * Redefine the method that is to be hook-able so that the hook stacks
166
+ # are invoked appropriately.
167
+ def register_hook(target_method, scope)
168
+ if scope == :instance && !method_defined?(target_method)
169
+ raise ArgumentError, "#{target_method} instance method does not exist"
170
+ elsif scope == :class && !respond_to?(target_method)
171
+ raise ArgumentError, "#{target_method} class method does not exist"
172
+ end
173
+
174
+ hooks = hooks_with_scope(scope)
175
+
176
+ return unless hooks[target_method].nil?
177
+
178
+ hooks[target_method] = {
179
+ # We need to keep track of which class in the Inheritance chain the
180
+ # method was declared hook-able in. Every time a child declares a new
181
+ # hook for the method, the hook stack invocations need to be redefined
182
+ # in the original Class. See #define_hook_stack_execution_methods
183
+ before: [], after: [], in: self
184
+ }
185
+
186
+ define_hook_stack_execution_methods(target_method, scope)
187
+ define_advised_method(target_method, scope)
188
+ end
189
+
190
+ # Is the method registered as a hook-able in the given scope.
191
+ def registered_as_hook?(target_method, scope)
192
+ !hooks_with_scope(scope)[target_method].nil?
193
+ end
194
+
195
+ # Generates names for the various utility methods. We need to do this because
196
+ # the various utility methods should not end in = so, while we're at it, we
197
+ # might as well get rid of all punctuation.
198
+ def hook_method_name(target_method, prefix, suffix)
199
+ target_method = target_method.to_s
200
+
201
+ case target_method[-1, 1]
202
+ when '?' then "#{prefix}_#{target_method[0..-2]}_ques_#{suffix}"
203
+ when '!' then "#{prefix}_#{target_method[0..-2]}_bang_#{suffix}"
204
+ when '=' then "#{prefix}_#{target_method[0..-2]}_eq_#{suffix}"
205
+ # I add a _nan_ suffix here so that we don't ever encounter
206
+ # any naming conflicts.
207
+ else "#{prefix}_#{target_method}_nan_#{suffix}"
208
+ end
209
+ end
210
+
211
+ # This will need to be refactored
212
+ def process_method_added(method_name, scope)
213
+ hooks_with_scope(scope).each do |target_method, hooks|
214
+ define_hook_stack_execution_methods(target_method, scope) if hooks[:before].any? { |hook| hook[:name] == method_name }
215
+ define_hook_stack_execution_methods(target_method, scope) if hooks[:after].any? { |hook| hook[:name] == method_name }
216
+ end
217
+ end
218
+
219
+ # Defines two methods. One method executes the before hook stack. The other executes
220
+ # the after hook stack. This method will be called many times during the Class definition
221
+ # process. It should be called for each hook that is defined. It will also be called
222
+ # when a hook is redefined (to make sure that the arity hasn't changed).
223
+ def define_hook_stack_execution_methods(target_method, scope)
224
+ raise ArgumentError, "#{target_method} has not be registered as a hook-able #{scope} method" unless registered_as_hook?(target_method, scope)
225
+
226
+ hooks = hooks_with_scope(scope)
227
+
228
+ before_hooks = hooks[target_method][:before]
229
+ before_hooks = before_hooks.map { |info| inline_call(info, scope) }.join("\n")
230
+
231
+ after_hooks = hooks[target_method][:after]
232
+ after_hooks = after_hooks.map { |info| inline_call(info, scope) }.join("\n")
233
+
234
+ before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
235
+ after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack')
236
+
237
+ hooks[target_method][:in].class_eval <<-RUBY, __FILE__, __LINE__ + 1
238
+ #{(scope == :class) ? 'class << self' : ''}
239
+
240
+ private
241
+
242
+ remove_method :#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} }
243
+ def #{before_hook_name}(*args)
244
+ #{before_hooks}
245
+ end
246
+
247
+ remove_method :#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{after_hook_name} }
248
+ def #{after_hook_name}(*args)
249
+ #{after_hooks}
250
+ end
251
+
252
+ #{(scope == :class) ? 'end' : ''}
253
+ RUBY
254
+ end
255
+
256
+ # Returns ruby code that will invoke the hook. It checks the arity of the hook method
257
+ # and passes arguments accordingly.
258
+ def inline_call(method_info, scope)
259
+ DataMapper::Hook::ClassMethods.hook_scopes << method_info[:from]
260
+ name = method_info[:name]
261
+ if scope == :instance
262
+ args = (method_defined?(name) && instance_method(name).arity != 0) ? '*args' : ''
263
+ %(#{name}(#{args}) if self.class <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id}))
264
+ else
265
+ args = (respond_to?(name) && method(name).arity != 0) ? '*args' : ''
266
+ %(#{name}(#{args}) if self <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id}))
267
+ end
268
+ end
269
+
270
+ def define_advised_method(target_method, scope)
271
+ args = args_for(method_with_scope(target_method, scope))
272
+
273
+ renamed_target = hook_method_name(target_method, 'hookable_', 'before_advised')
274
+
275
+ source = <<-EOD
276
+ def #{target_method}(#{args})
277
+ retval = nil
278
+ catch(:halt) do
279
+ #{hook_method_name(target_method, 'execute_before', 'hook_stack')}(#{args})
280
+ retval = #{renamed_target}(#{args})
281
+ #{hook_method_name(target_method, 'execute_after', 'hook_stack')}(retval, #{args})
282
+ retval
283
+ end
284
+ end
285
+ EOD
286
+
287
+ if scope == :instance && instance_methods(false).none? { |m| m.to_sym == target_method }
288
+ send(:alias_method, renamed_target, target_method)
289
+
290
+ proxy_module = Module.new
291
+ proxy_module.class_eval(source, __FILE__, __LINE__)
292
+ send(:include, proxy_module)
293
+ else
294
+ source = %(alias_method :#{renamed_target}, :#{target_method}\n#{source})
295
+ source = %(class << self\n#{source}\nend) if scope == :class
296
+ class_eval(source, __FILE__, __LINE__)
297
+ end
298
+ end
299
+
300
+ # --- Add a hook ---
301
+
302
+ def install_hook(type, target_method, method_sym, scope, &block)
303
+ assert_kind_of 'target_method', target_method, Symbol
304
+ assert_kind_of 'method_sym', method_sym, Symbol unless method_sym.nil?
305
+ assert_kind_of 'scope', scope, Symbol
306
+
307
+ raise ArgumentError, "You need to pass 2 arguments to \"#{type}\"." if !block_given? && method_sym.nil?
308
+
309
+ raise ArgumentError, 'Methods ending in = cannot be hooks' if method_sym.to_s[-1, 1] == '='
310
+
311
+ raise ArgumentError, 'You need to pass :class or :instance as scope' unless %i(class instance).include?(scope)
312
+
313
+ if registered_as_hook?(target_method, scope)
314
+ hooks = hooks_with_scope(scope)
315
+
316
+ # if this hook is previously declared in a sibling or cousin we must move the :in class
317
+ # to the common ancestor to get both hooks to run.
318
+ unless hooks[target_method][:in] <=> self
319
+ before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
320
+ after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack')
321
+
322
+ hooks[target_method][:in].class_eval <<-RUBY, __FILE__, __LINE__ + 1
323
+ remove_method :#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} }
324
+ def #{before_hook_name}(*args)
325
+ super
326
+ end
327
+
328
+ remove_method :#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} }
329
+ def #{after_hook_name}(*args)
330
+ super
331
+ end
332
+ RUBY
333
+
334
+ hooks[target_method][:in] = hooks[target_method][:in].superclass until hooks[target_method][:in] <=> self
335
+
336
+ define_hook_stack_execution_methods(target_method, scope)
337
+ hooks[target_method][:in].class_eval { define_advised_method(target_method, scope) }
338
+ end
339
+ else
340
+ register_hook(target_method, scope)
341
+ hooks = hooks_with_scope(scope)
342
+ end
343
+
344
+ # if we were passed a block, create a method out of it.
345
+ if block
346
+ method_sym = "__hooks_#{type}_#{quote_method(target_method)}_#{hooks[target_method][type].length}".to_sym
347
+ if scope == :class
348
+ singleton_class.instance_eval do
349
+ define_method(method_sym, &block)
350
+ end
351
+ else
352
+ define_method(method_sym, &block)
353
+ end
354
+ end
355
+
356
+ # Adds method to the stack an redefines the hook invocation method
357
+ hooks[target_method][type] << {name: method_sym, from: self}
358
+ define_hook_stack_execution_methods(target_method, scope)
359
+ end
360
+
361
+ # --- Helpers ---
362
+
363
+ def args_for(method)
364
+ if method.arity == 0
365
+ '&block'
366
+ elsif method.arity > 0
367
+ '_' << (1..method.arity).to_a.join(', _') << ', &block'
368
+ elsif (method.arity + 1) < 0
369
+ '_' << (1..method.arity.abs - 1).to_a.join(', _') << ', *args, &block'
370
+ else
371
+ '*args, &block'
372
+ end
373
+ end
374
+
375
+ def method_with_scope(name, scope)
376
+ case scope
377
+ when :class then method(name)
378
+ when :instance then instance_method(name)
379
+ else raise ArgumentError, 'You need to pass :class or :instance as scope'
380
+ end
381
+ end
382
+
383
+ def quote_method(name)
384
+ name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_').gsub(/=$/, '_eq_')
385
+ end
386
+ end
387
+ end
388
+ end