ardm-core 1.2.1

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 +35 -0
  5. data/.travis.yml +23 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +63 -0
  8. data/LICENSE +20 -0
  9. data/README.rdoc +237 -0
  10. data/Rakefile +4 -0
  11. data/VERSION +1 -0
  12. data/ardm-core.gemspec +25 -0
  13. data/lib/ardm-core.rb +1 -0
  14. data/lib/dm-core.rb +285 -0
  15. data/lib/dm-core/adapters.rb +222 -0
  16. data/lib/dm-core/adapters/abstract_adapter.rb +236 -0
  17. data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
  18. data/lib/dm-core/associations/many_to_many.rb +496 -0
  19. data/lib/dm-core/associations/many_to_one.rb +296 -0
  20. data/lib/dm-core/associations/one_to_many.rb +345 -0
  21. data/lib/dm-core/associations/one_to_one.rb +86 -0
  22. data/lib/dm-core/associations/relationship.rb +663 -0
  23. data/lib/dm-core/backwards.rb +13 -0
  24. data/lib/dm-core/collection.rb +1514 -0
  25. data/lib/dm-core/core_ext/kernel.rb +23 -0
  26. data/lib/dm-core/core_ext/pathname.rb +6 -0
  27. data/lib/dm-core/core_ext/symbol.rb +10 -0
  28. data/lib/dm-core/identity_map.rb +7 -0
  29. data/lib/dm-core/model.rb +869 -0
  30. data/lib/dm-core/model/hook.rb +102 -0
  31. data/lib/dm-core/model/is.rb +32 -0
  32. data/lib/dm-core/model/property.rb +253 -0
  33. data/lib/dm-core/model/relationship.rb +377 -0
  34. data/lib/dm-core/model/scope.rb +89 -0
  35. data/lib/dm-core/property.rb +839 -0
  36. data/lib/dm-core/property/binary.rb +22 -0
  37. data/lib/dm-core/property/boolean.rb +31 -0
  38. data/lib/dm-core/property/class.rb +24 -0
  39. data/lib/dm-core/property/date.rb +45 -0
  40. data/lib/dm-core/property/date_time.rb +44 -0
  41. data/lib/dm-core/property/decimal.rb +50 -0
  42. data/lib/dm-core/property/discriminator.rb +46 -0
  43. data/lib/dm-core/property/float.rb +28 -0
  44. data/lib/dm-core/property/integer.rb +32 -0
  45. data/lib/dm-core/property/lookup.rb +29 -0
  46. data/lib/dm-core/property/numeric.rb +40 -0
  47. data/lib/dm-core/property/object.rb +28 -0
  48. data/lib/dm-core/property/serial.rb +13 -0
  49. data/lib/dm-core/property/string.rb +50 -0
  50. data/lib/dm-core/property/text.rb +12 -0
  51. data/lib/dm-core/property/time.rb +46 -0
  52. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  53. data/lib/dm-core/property/typecast/time.rb +33 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query.rb +1444 -0
  56. data/lib/dm-core/query/conditions/comparison.rb +910 -0
  57. data/lib/dm-core/query/conditions/operation.rb +720 -0
  58. data/lib/dm-core/query/direction.rb +36 -0
  59. data/lib/dm-core/query/operator.rb +35 -0
  60. data/lib/dm-core/query/path.rb +114 -0
  61. data/lib/dm-core/query/sort.rb +39 -0
  62. data/lib/dm-core/relationship_set.rb +72 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource.rb +1228 -0
  65. data/lib/dm-core/resource/persistence_state.rb +75 -0
  66. data/lib/dm-core/resource/persistence_state/clean.rb +40 -0
  67. data/lib/dm-core/resource/persistence_state/deleted.rb +30 -0
  68. data/lib/dm-core/resource/persistence_state/dirty.rb +96 -0
  69. data/lib/dm-core/resource/persistence_state/immutable.rb +34 -0
  70. data/lib/dm-core/resource/persistence_state/persisted.rb +29 -0
  71. data/lib/dm-core/resource/persistence_state/transient.rb +78 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +54 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -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 +173 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +326 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1236 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +134 -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 +402 -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 +12 -0
  102. data/lib/dm-core/support/logger.rb +199 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +90 -0
  105. data/lib/dm-core/support/ordered_set.rb +380 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +250 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/script/performance.rb +275 -0
  110. data/script/profile.rb +218 -0
  111. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  112. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +68 -0
  113. data/spec/public/associations/many_to_many_spec.rb +197 -0
  114. data/spec/public/associations/many_to_one_spec.rb +83 -0
  115. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  116. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  117. data/spec/public/associations/one_to_many_spec.rb +81 -0
  118. data/spec/public/associations/one_to_one_spec.rb +176 -0
  119. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  120. data/spec/public/collection_spec.rb +69 -0
  121. data/spec/public/finalize_spec.rb +76 -0
  122. data/spec/public/model/hook_spec.rb +246 -0
  123. data/spec/public/model/property_spec.rb +88 -0
  124. data/spec/public/model/relationship_spec.rb +1040 -0
  125. data/spec/public/model_spec.rb +458 -0
  126. data/spec/public/property/binary_spec.rb +41 -0
  127. data/spec/public/property/boolean_spec.rb +22 -0
  128. data/spec/public/property/class_spec.rb +28 -0
  129. data/spec/public/property/date_spec.rb +22 -0
  130. data/spec/public/property/date_time_spec.rb +22 -0
  131. data/spec/public/property/decimal_spec.rb +23 -0
  132. data/spec/public/property/discriminator_spec.rb +135 -0
  133. data/spec/public/property/float_spec.rb +22 -0
  134. data/spec/public/property/integer_spec.rb +22 -0
  135. data/spec/public/property/object_spec.rb +107 -0
  136. data/spec/public/property/serial_spec.rb +22 -0
  137. data/spec/public/property/string_spec.rb +22 -0
  138. data/spec/public/property/text_spec.rb +63 -0
  139. data/spec/public/property/time_spec.rb +22 -0
  140. data/spec/public/property_spec.rb +341 -0
  141. data/spec/public/resource_spec.rb +284 -0
  142. data/spec/public/sel_spec.rb +53 -0
  143. data/spec/public/setup_spec.rb +145 -0
  144. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  145. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  146. data/spec/public/shared/collection_shared_spec.rb +1669 -0
  147. data/spec/public/shared/finder_shared_spec.rb +1629 -0
  148. data/spec/rcov.opts +6 -0
  149. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  150. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  151. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  152. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  153. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  154. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  155. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  156. data/spec/semipublic/associations_spec.rb +177 -0
  157. data/spec/semipublic/collection_spec.rb +110 -0
  158. data/spec/semipublic/model_spec.rb +96 -0
  159. data/spec/semipublic/property/binary_spec.rb +13 -0
  160. data/spec/semipublic/property/boolean_spec.rb +47 -0
  161. data/spec/semipublic/property/class_spec.rb +33 -0
  162. data/spec/semipublic/property/date_spec.rb +43 -0
  163. data/spec/semipublic/property/date_time_spec.rb +46 -0
  164. data/spec/semipublic/property/decimal_spec.rb +83 -0
  165. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  166. data/spec/semipublic/property/float_spec.rb +82 -0
  167. data/spec/semipublic/property/integer_spec.rb +82 -0
  168. data/spec/semipublic/property/lookup_spec.rb +29 -0
  169. data/spec/semipublic/property/serial_spec.rb +13 -0
  170. data/spec/semipublic/property/string_spec.rb +13 -0
  171. data/spec/semipublic/property/text_spec.rb +31 -0
  172. data/spec/semipublic/property/time_spec.rb +50 -0
  173. data/spec/semipublic/property_spec.rb +114 -0
  174. data/spec/semipublic/query/conditions/comparison_spec.rb +1501 -0
  175. data/spec/semipublic/query/conditions/operation_spec.rb +1294 -0
  176. data/spec/semipublic/query/path_spec.rb +471 -0
  177. data/spec/semipublic/query_spec.rb +3777 -0
  178. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  179. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  180. data/spec/semipublic/resource/state/dirty_spec.rb +156 -0
  181. data/spec/semipublic/resource/state/immutable_spec.rb +105 -0
  182. data/spec/semipublic/resource/state/transient_spec.rb +162 -0
  183. data/spec/semipublic/resource/state_spec.rb +230 -0
  184. data/spec/semipublic/resource_spec.rb +23 -0
  185. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  186. data/spec/semipublic/shared/resource_shared_spec.rb +199 -0
  187. data/spec/semipublic/shared/resource_state_shared_spec.rb +79 -0
  188. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  189. data/spec/spec.opts +5 -0
  190. data/spec/spec_helper.rb +37 -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 +28 -0
  248. data/spec/unit/hook_spec.rb +1235 -0
  249. data/spec/unit/lazy_array_spec.rb +1949 -0
  250. data/spec/unit/mash_spec.rb +312 -0
  251. data/spec/unit/module_spec.rb +71 -0
  252. data/spec/unit/object_spec.rb +38 -0
  253. data/spec/unit/try_dup_spec.rb +46 -0
  254. data/tasks/ci.rake +1 -0
  255. data/tasks/db.rake +11 -0
  256. data/tasks/spec.rake +38 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +491 -0
@@ -0,0 +1,402 @@
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
+
25
+ def self.included(base)
26
+ base.extend(ClassMethods)
27
+ base.const_set("CLASS_HOOKS", {}) unless base.const_defined?("CLASS_HOOKS")
28
+ base.const_set("INSTANCE_HOOKS", {}) unless base.const_defined?("INSTANCE_HOOKS")
29
+ base.class_eval do
30
+ class << self
31
+ def method_added(name)
32
+ process_method_added(name, :instance)
33
+ end
34
+
35
+ def singleton_method_added(name)
36
+ process_method_added(name, :class)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ module ClassMethods
43
+ extend DataMapper::LocalObjectSpace
44
+ include DataMapper::Assertions
45
+ # Inject code that executes before the target class method.
46
+ #
47
+ # @param target_method<Symbol> the name of the class method to inject before
48
+ # @param method_sym<Symbol> the name of the method to run before the
49
+ # target_method
50
+ # @param block<Block> the code to run before the target_method
51
+ #
52
+ # @note
53
+ # Either method_sym or block is required.
54
+ # -
55
+ # @api public
56
+ def before_class_method(target_method, method_sym = nil, &block)
57
+ install_hook :before, target_method, method_sym, :class, &block
58
+ end
59
+
60
+ #
61
+ # Inject code that executes after the target class method.
62
+ #
63
+ # @param target_method<Symbol> the name of the class method to inject after
64
+ # @param method_sym<Symbol> the name of the method to run after the target_method
65
+ # @param block<Block> the code to run after the target_method
66
+ #
67
+ # @note
68
+ # Either method_sym or block is required.
69
+ # -
70
+ # @api public
71
+ def after_class_method(target_method, method_sym = nil, &block)
72
+ install_hook :after, target_method, method_sym, :class, &block
73
+ end
74
+
75
+ #
76
+ # Inject code that executes before the target instance method.
77
+ #
78
+ # @param target_method<Symbol> the name of the instance method to inject before
79
+ # @param method_sym<Symbol> the name of the method to run before the
80
+ # target_method
81
+ # @param block<Block> the code to run before the target_method
82
+ #
83
+ # @note
84
+ # Either method_sym or block is required.
85
+ # -
86
+ # @api public
87
+ def before(target_method, method_sym = nil, &block)
88
+ install_hook :before, target_method, method_sym, :instance, &block
89
+ end
90
+
91
+ #
92
+ # Inject code that executes after the target instance method.
93
+ #
94
+ # @param target_method<Symbol> the name of the instance method to inject after
95
+ # @param method_sym<Symbol> the name of the method to run after the
96
+ # target_method
97
+ # @param block<Block> the code to run after the target_method
98
+ #
99
+ # @note
100
+ # Either method_sym or block is required.
101
+ # -
102
+ # @api public
103
+ def after(target_method, method_sym = nil, &block)
104
+ install_hook :after, target_method, method_sym, :instance, &block
105
+ end
106
+
107
+ # Register a class method as hookable. Registering a method means that
108
+ # before hooks will be run immediately before the method is invoked and
109
+ # after hooks will be called immediately after the method is invoked.
110
+ #
111
+ # @param hookable_method<Symbol> The name of the class method that should
112
+ # be hookable
113
+ # -
114
+ # @api public
115
+ def register_class_hooks(*hooks)
116
+ hooks.each { |hook| register_hook(hook, :class) }
117
+ end
118
+
119
+ # Register aninstance method as hookable. Registering a method means that
120
+ # before hooks will be run immediately before the method is invoked and
121
+ # after hooks will be called immediately after the method is invoked.
122
+ #
123
+ # @param hookable_method<Symbol> The name of the instance method that should
124
+ # be hookable
125
+ # -
126
+ # @api public
127
+ def register_instance_hooks(*hooks)
128
+ hooks.each { |hook| register_hook(hook, :instance) }
129
+ end
130
+
131
+ # Not yet implemented
132
+ def reset_hook!(target_method, scope)
133
+ raise NotImplementedError
134
+ end
135
+
136
+ # --- Alright kids... the rest is internal stuff ---
137
+
138
+ # Returns the correct HOOKS Hash depending on whether we are
139
+ # working with class methods or instance methods
140
+ def hooks_with_scope(scope)
141
+ case scope
142
+ when :class then class_hooks
143
+ when :instance then instance_hooks
144
+ else raise ArgumentError, 'You need to pass :class or :instance as scope'
145
+ end
146
+ end
147
+
148
+ def class_hooks
149
+ self.const_get("CLASS_HOOKS")
150
+ end
151
+
152
+ def instance_hooks
153
+ self.const_get("INSTANCE_HOOKS")
154
+ end
155
+
156
+ # Registers a method as hookable. Registering hooks involves the following
157
+ # process
158
+ #
159
+ # * Create a blank entry in the HOOK Hash for the method.
160
+ # * Define the methods that execute the before and after hook stack.
161
+ # These methods will be no-ops at first, but everytime a new hook is
162
+ # defined, the methods will be redefined to incorporate the new hook.
163
+ # * Redefine the method that is to be hookable so that the hook stacks
164
+ # are invoked approprietly.
165
+ def register_hook(target_method, scope)
166
+ if scope == :instance && !method_defined?(target_method)
167
+ raise ArgumentError, "#{target_method} instance method does not exist"
168
+ elsif scope == :class && !respond_to?(target_method)
169
+ raise ArgumentError, "#{target_method} class method does not exist"
170
+ end
171
+
172
+ hooks = hooks_with_scope(scope)
173
+
174
+ if hooks[target_method].nil?
175
+ hooks[target_method] = {
176
+ # We need to keep track of which class in the Inheritance chain the
177
+ # method was declared hookable in. Every time a child declares a new
178
+ # hook for the method, the hook stack invocations need to be redefined
179
+ # in the original Class. See #define_hook_stack_execution_methods
180
+ :before => [], :after => [], :in => self
181
+ }
182
+
183
+ define_hook_stack_execution_methods(target_method, scope)
184
+ define_advised_method(target_method, scope)
185
+ end
186
+ end
187
+
188
+ # Is the method registered as a hookable in the given scope.
189
+ def registered_as_hook?(target_method, scope)
190
+ ! hooks_with_scope(scope)[target_method].nil?
191
+ end
192
+
193
+ # Generates names for the various utility methods. We need to do this because
194
+ # the various utility methods should not end in = so, while we're at it, we
195
+ # might as well get rid of all punctuation.
196
+ def hook_method_name(target_method, prefix, suffix)
197
+ target_method = target_method.to_s
198
+
199
+ case target_method[-1,1]
200
+ when '?' then "#{prefix}_#{target_method[0..-2]}_ques_#{suffix}"
201
+ when '!' then "#{prefix}_#{target_method[0..-2]}_bang_#{suffix}"
202
+ when '=' then "#{prefix}_#{target_method[0..-2]}_eq_#{suffix}"
203
+ # I add a _nan_ suffix here so that we don't ever encounter
204
+ # any naming conflicts.
205
+ else "#{prefix}_#{target_method[0..-1]}_nan_#{suffix}"
206
+ end
207
+ end
208
+
209
+ # This will need to be refactored
210
+ def process_method_added(method_name, scope)
211
+ hooks_with_scope(scope).each do |target_method, hooks|
212
+ if hooks[:before].any? { |hook| hook[:name] == method_name }
213
+ define_hook_stack_execution_methods(target_method, scope)
214
+ end
215
+
216
+ if hooks[:after].any? { |hook| hook[:name] == method_name }
217
+ define_hook_stack_execution_methods(target_method, scope)
218
+ end
219
+ end
220
+ end
221
+
222
+ # Defines two methods. One method executes the before hook stack. The other executes
223
+ # the after hook stack. This method will be called many times during the Class definition
224
+ # process. It should be called for each hook that is defined. It will also be called
225
+ # when a hook is redefined (to make sure that the arity hasn't changed).
226
+ def define_hook_stack_execution_methods(target_method, scope)
227
+ unless registered_as_hook?(target_method, scope)
228
+ raise ArgumentError, "#{target_method} has not be registered as a hookable #{scope} method"
229
+ end
230
+
231
+ hooks = hooks_with_scope(scope)
232
+
233
+ before_hooks = hooks[target_method][:before]
234
+ before_hooks = before_hooks.map{ |info| inline_call(info, scope) }.join("\n")
235
+
236
+ after_hooks = hooks[target_method][:after]
237
+ after_hooks = after_hooks.map{ |info| inline_call(info, scope) }.join("\n")
238
+
239
+ before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
240
+ after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack')
241
+
242
+ hooks[target_method][:in].class_eval <<-RUBY, __FILE__, __LINE__ + 1
243
+ #{scope == :class ? 'class << self' : ''}
244
+
245
+ private
246
+
247
+ remove_method :#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} }
248
+ def #{before_hook_name}(*args)
249
+ #{before_hooks}
250
+ end
251
+
252
+ remove_method :#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{after_hook_name} }
253
+ def #{after_hook_name}(*args)
254
+ #{after_hooks}
255
+ end
256
+
257
+ #{scope == :class ? 'end' : ''}
258
+ RUBY
259
+ end
260
+
261
+ # Returns ruby code that will invoke the hook. It checks the arity of the hook method
262
+ # and passes arguments accordingly.
263
+ def inline_call(method_info, scope)
264
+ DataMapper::Hook::ClassMethods.hook_scopes << method_info[:from]
265
+ name = method_info[:name]
266
+ if scope == :instance
267
+ args = method_defined?(name) && instance_method(name).arity != 0 ? '*args' : ''
268
+ %(#{name}(#{args}) if self.class <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id}))
269
+ else
270
+ args = respond_to?(name) && method(name).arity != 0 ? '*args' : ''
271
+ %(#{name}(#{args}) if self <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id}))
272
+ end
273
+ end
274
+
275
+ def define_advised_method(target_method, scope)
276
+ args = args_for(method_with_scope(target_method, scope))
277
+
278
+ renamed_target = hook_method_name(target_method, 'hookable_', 'before_advised')
279
+
280
+ source = <<-EOD
281
+ def #{target_method}(#{args})
282
+ retval = nil
283
+ catch(:halt) do
284
+ #{hook_method_name(target_method, 'execute_before', 'hook_stack')}(#{args})
285
+ retval = #{renamed_target}(#{args})
286
+ #{hook_method_name(target_method, 'execute_after', 'hook_stack')}(retval, #{args})
287
+ retval
288
+ end
289
+ end
290
+ EOD
291
+
292
+ if scope == :instance && !instance_methods(false).any? { |m| m.to_sym == target_method }
293
+ send(:alias_method, renamed_target, target_method)
294
+
295
+ proxy_module = Module.new
296
+ proxy_module.class_eval(source, __FILE__, __LINE__)
297
+ self.send(:include, proxy_module)
298
+ else
299
+ source = %{alias_method :#{renamed_target}, :#{target_method}\n#{source}}
300
+ source = %{class << self\n#{source}\nend} if scope == :class
301
+ class_eval(source, __FILE__, __LINE__)
302
+ end
303
+ end
304
+
305
+ # --- Add a hook ---
306
+
307
+ def install_hook(type, target_method, method_sym, scope, &block)
308
+ assert_kind_of 'target_method', target_method, Symbol
309
+ assert_kind_of 'method_sym', method_sym, Symbol unless method_sym.nil?
310
+ assert_kind_of 'scope', scope, Symbol
311
+
312
+ if !block_given? and method_sym.nil?
313
+ raise ArgumentError, "You need to pass 2 arguments to \"#{type}\"."
314
+ end
315
+
316
+ if method_sym.to_s[-1,1] == '='
317
+ raise ArgumentError, "Methods ending in = cannot be hooks"
318
+ end
319
+
320
+ unless [ :class, :instance ].include?(scope)
321
+ raise ArgumentError, 'You need to pass :class or :instance as scope'
322
+ end
323
+
324
+ if registered_as_hook?(target_method, scope)
325
+ hooks = hooks_with_scope(scope)
326
+
327
+ #if this hook is previously declared in a sibling or cousin we must move the :in class
328
+ #to the common ancestor to get both hooks to run.
329
+ if !(hooks[target_method][:in] <=> self)
330
+ before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
331
+ after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack')
332
+
333
+ hooks[target_method][:in].class_eval <<-RUBY, __FILE__, __LINE__ + 1
334
+ remove_method :#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} }
335
+ def #{before_hook_name}(*args)
336
+ super
337
+ end
338
+
339
+ remove_method :#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} }
340
+ def #{after_hook_name}(*args)
341
+ super
342
+ end
343
+ RUBY
344
+
345
+ while !(hooks[target_method][:in] <=> self) do
346
+ hooks[target_method][:in] = hooks[target_method][:in].superclass
347
+ end
348
+
349
+ define_hook_stack_execution_methods(target_method, scope)
350
+ hooks[target_method][:in].class_eval{define_advised_method(target_method, scope)}
351
+ end
352
+ else
353
+ register_hook(target_method, scope)
354
+ hooks = hooks_with_scope(scope)
355
+ end
356
+
357
+ #if we were passed a block, create a method out of it.
358
+ if block
359
+ method_sym = "__hooks_#{type}_#{quote_method(target_method)}_#{hooks[target_method][type].length}".to_sym
360
+ if scope == :class
361
+ singleton_class.instance_eval do
362
+ define_method(method_sym, &block)
363
+ end
364
+ else
365
+ define_method(method_sym, &block)
366
+ end
367
+ end
368
+
369
+ # Adds method to the stack an redefines the hook invocation method
370
+ hooks[target_method][type] << { :name => method_sym, :from => self }
371
+ define_hook_stack_execution_methods(target_method, scope)
372
+ end
373
+
374
+ # --- Helpers ---
375
+
376
+ def args_for(method)
377
+ if method.arity == 0
378
+ "&block"
379
+ elsif method.arity > 0
380
+ "_" << (1 .. method.arity).to_a.join(", _") << ", &block"
381
+ elsif (method.arity + 1) < 0
382
+ "_" << (1 .. (method.arity).abs - 1).to_a.join(", _") << ", *args, &block"
383
+ else
384
+ "*args, &block"
385
+ end
386
+ end
387
+
388
+ def method_with_scope(name, scope)
389
+ case scope
390
+ when :class then method(name)
391
+ when :instance then instance_method(name)
392
+ else raise ArgumentError, 'You need to pass :class or :instance as scope'
393
+ end
394
+ end
395
+
396
+ def quote_method(name)
397
+ name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_').gsub(/=$/, '_eq_')
398
+ end
399
+ end
400
+
401
+ end
402
+ end
@@ -0,0 +1,60 @@
1
+ module DataMapper
2
+ Inflector.inflections do |inflect|
3
+ inflect.plural(/$/, 's')
4
+ inflect.plural(/s$/i, 's')
5
+ inflect.plural(/(ax|test)is$/i, '\1es')
6
+ inflect.plural(/(octop|vir)us$/i, '\1i')
7
+ inflect.plural(/(octop|vir)i$/i, '\1i')
8
+ inflect.plural(/(alias|status)$/i, '\1es')
9
+ inflect.plural(/(bu)s$/i, '\1ses')
10
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
11
+ inflect.plural(/([ti])um$/i, '\1a')
12
+ inflect.plural(/([ti])a$/i, '\1a')
13
+ inflect.plural(/sis$/i, 'ses')
14
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
15
+ inflect.plural(/(hive)$/i, '\1s')
16
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
17
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
18
+ inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
19
+ inflect.plural(/([m|l])ouse$/i, '\1ice')
20
+ inflect.plural(/([m|l])ice$/i, '\1ice')
21
+ inflect.plural(/^(ox)$/i, '\1en')
22
+ inflect.plural(/^(oxen)$/i, '\1')
23
+ inflect.plural(/(quiz)$/i, '\1zes')
24
+
25
+ inflect.singular(/s$/i, '')
26
+ inflect.singular(/(n)ews$/i, '\1ews')
27
+ inflect.singular(/([ti])a$/i, '\1um')
28
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
29
+ inflect.singular(/(^analy)ses$/i, '\1sis')
30
+ inflect.singular(/([^f])ves$/i, '\1fe')
31
+ inflect.singular(/(hive)s$/i, '\1')
32
+ inflect.singular(/(tive)s$/i, '\1')
33
+ inflect.singular(/([lr])ves$/i, '\1f')
34
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
35
+ inflect.singular(/(s)eries$/i, '\1eries')
36
+ inflect.singular(/(m)ovies$/i, '\1ovie')
37
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
38
+ inflect.singular(/([m|l])ice$/i, '\1ouse')
39
+ inflect.singular(/(bus)es$/i, '\1')
40
+ inflect.singular(/(o)es$/i, '\1')
41
+ inflect.singular(/(shoe)s$/i, '\1')
42
+ inflect.singular(/(cris|ax|test)es$/i, '\1is')
43
+ inflect.singular(/(octop|vir)i$/i, '\1us')
44
+ inflect.singular(/(alias|status)es$/i, '\1')
45
+ inflect.singular(/^(ox)en/i, '\1')
46
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
47
+ inflect.singular(/(matr)ices$/i, '\1ix')
48
+ inflect.singular(/(quiz)zes$/i, '\1')
49
+ inflect.singular(/(database)s$/i, '\1')
50
+
51
+ inflect.irregular('person', 'people')
52
+ inflect.irregular('man', 'men')
53
+ inflect.irregular('child', 'children')
54
+ inflect.irregular('sex', 'sexes')
55
+ inflect.irregular('move', 'moves')
56
+ inflect.irregular('cow', 'kine')
57
+
58
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
59
+ end
60
+ end