dm-core 0.10.2 → 1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. data/.gitignore +10 -1
  2. data/Gemfile +143 -0
  3. data/Rakefile +9 -5
  4. data/VERSION +1 -1
  5. data/dm-core.gemspec +160 -57
  6. data/lib/dm-core.rb +131 -56
  7. data/lib/dm-core/adapters.rb +98 -14
  8. data/lib/dm-core/adapters/abstract_adapter.rb +24 -4
  9. data/lib/dm-core/adapters/in_memory_adapter.rb +7 -2
  10. data/lib/dm-core/associations/many_to_many.rb +19 -30
  11. data/lib/dm-core/associations/many_to_one.rb +58 -42
  12. data/lib/dm-core/associations/one_to_many.rb +33 -23
  13. data/lib/dm-core/associations/one_to_one.rb +27 -11
  14. data/lib/dm-core/associations/relationship.rb +4 -4
  15. data/lib/dm-core/collection.rb +23 -16
  16. data/lib/dm-core/core_ext/array.rb +36 -0
  17. data/lib/dm-core/core_ext/hash.rb +30 -0
  18. data/lib/dm-core/core_ext/module.rb +46 -0
  19. data/lib/dm-core/core_ext/object.rb +31 -0
  20. data/lib/dm-core/core_ext/pathname.rb +20 -0
  21. data/lib/dm-core/core_ext/string.rb +22 -0
  22. data/lib/dm-core/core_ext/try_dup.rb +44 -0
  23. data/lib/dm-core/model.rb +88 -27
  24. data/lib/dm-core/model/hook.rb +75 -18
  25. data/lib/dm-core/model/property.rb +50 -9
  26. data/lib/dm-core/model/relationship.rb +31 -31
  27. data/lib/dm-core/model/scope.rb +3 -3
  28. data/lib/dm-core/property.rb +196 -516
  29. data/lib/dm-core/property/binary.rb +7 -0
  30. data/lib/dm-core/property/boolean.rb +35 -0
  31. data/lib/dm-core/property/class.rb +24 -0
  32. data/lib/dm-core/property/date.rb +47 -0
  33. data/lib/dm-core/property/date_time.rb +48 -0
  34. data/lib/dm-core/property/decimal.rb +43 -0
  35. data/lib/dm-core/property/discriminator.rb +48 -0
  36. data/lib/dm-core/property/float.rb +24 -0
  37. data/lib/dm-core/property/integer.rb +32 -0
  38. data/lib/dm-core/property/numeric.rb +43 -0
  39. data/lib/dm-core/property/object.rb +32 -0
  40. data/lib/dm-core/property/serial.rb +8 -0
  41. data/lib/dm-core/property/string.rb +49 -0
  42. data/lib/dm-core/property/text.rb +12 -0
  43. data/lib/dm-core/property/time.rb +48 -0
  44. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  45. data/lib/dm-core/property/typecast/time.rb +28 -0
  46. data/lib/dm-core/property_set.rb +10 -4
  47. data/lib/dm-core/query.rb +14 -37
  48. data/lib/dm-core/query/conditions/comparison.rb +8 -6
  49. data/lib/dm-core/query/conditions/operation.rb +33 -2
  50. data/lib/dm-core/query/operator.rb +2 -5
  51. data/lib/dm-core/query/path.rb +4 -6
  52. data/lib/dm-core/repository.rb +21 -6
  53. data/lib/dm-core/resource.rb +316 -133
  54. data/lib/dm-core/resource/state.rb +79 -0
  55. data/lib/dm-core/resource/state/clean.rb +40 -0
  56. data/lib/dm-core/resource/state/deleted.rb +30 -0
  57. data/lib/dm-core/resource/state/dirty.rb +86 -0
  58. data/lib/dm-core/resource/state/immutable.rb +34 -0
  59. data/lib/dm-core/resource/state/persisted.rb +29 -0
  60. data/lib/dm-core/resource/state/transient.rb +70 -0
  61. data/lib/dm-core/spec/lib/adapter_helpers.rb +52 -0
  62. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  63. data/{spec → lib/dm-core/spec}/lib/counter_adapter.rb +5 -1
  64. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  65. data/lib/dm-core/spec/lib/spec_helper.rb +68 -0
  66. data/lib/dm-core/spec/setup.rb +165 -0
  67. data/lib/dm-core/spec/{adapter_shared_spec.rb → shared/adapter_spec.rb} +21 -7
  68. data/{spec/public/shared/resource_shared_spec.rb → lib/dm-core/spec/shared/resource_spec.rb} +120 -83
  69. data/{spec/public/shared/sel_shared_spec.rb → lib/dm-core/spec/shared/sel_spec.rb} +5 -6
  70. data/lib/dm-core/support/assertions.rb +8 -0
  71. data/lib/dm-core/support/equalizer.rb +1 -0
  72. data/lib/dm-core/support/hook.rb +420 -0
  73. data/lib/dm-core/support/lazy_array.rb +453 -0
  74. data/lib/dm-core/support/local_object_space.rb +12 -0
  75. data/lib/dm-core/support/logger.rb +193 -6
  76. data/lib/dm-core/support/naming_conventions.rb +8 -8
  77. data/lib/dm-core/support/subject.rb +33 -0
  78. data/lib/dm-core/type.rb +4 -0
  79. data/lib/dm-core/types/boolean.rb +2 -0
  80. data/lib/dm-core/types/decimal.rb +9 -0
  81. data/lib/dm-core/types/discriminator.rb +2 -0
  82. data/lib/dm-core/types/object.rb +3 -0
  83. data/lib/dm-core/types/serial.rb +2 -0
  84. data/lib/dm-core/types/text.rb +2 -0
  85. data/lib/dm-core/version.rb +1 -1
  86. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +67 -0
  87. data/spec/public/model/hook_spec.rb +209 -0
  88. data/spec/public/model/property_spec.rb +35 -0
  89. data/spec/public/model/relationship_spec.rb +33 -20
  90. data/spec/public/model_spec.rb +142 -10
  91. data/spec/public/property/binary_spec.rb +14 -0
  92. data/spec/public/property/boolean_spec.rb +14 -0
  93. data/spec/public/property/class_spec.rb +20 -0
  94. data/spec/public/property/date_spec.rb +14 -0
  95. data/spec/public/property/date_time_spec.rb +14 -0
  96. data/spec/public/property/decimal_spec.rb +14 -0
  97. data/spec/public/{types → property}/discriminator_spec.rb +2 -12
  98. data/spec/public/property/float_spec.rb +14 -0
  99. data/spec/public/property/integer_spec.rb +14 -0
  100. data/spec/public/property/object_spec.rb +9 -17
  101. data/spec/public/property/serial_spec.rb +14 -0
  102. data/spec/public/property/string_spec.rb +14 -0
  103. data/spec/public/property/text_spec.rb +52 -0
  104. data/spec/public/property/time_spec.rb +14 -0
  105. data/spec/public/property_spec.rb +28 -87
  106. data/spec/public/resource_spec.rb +101 -0
  107. data/spec/public/sel_spec.rb +5 -15
  108. data/spec/public/shared/collection_shared_spec.rb +16 -30
  109. data/spec/public/shared/finder_shared_spec.rb +2 -4
  110. data/spec/public/shared/property_shared_spec.rb +176 -0
  111. data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
  112. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +2 -2
  113. data/spec/semipublic/associations/many_to_many_spec.rb +89 -0
  114. data/spec/semipublic/associations/many_to_one_spec.rb +24 -1
  115. data/spec/semipublic/associations/one_to_many_spec.rb +51 -0
  116. data/spec/semipublic/associations/one_to_one_spec.rb +49 -0
  117. data/spec/semipublic/associations/relationship_spec.rb +3 -3
  118. data/spec/semipublic/associations_spec.rb +1 -1
  119. data/spec/semipublic/property/binary_spec.rb +13 -0
  120. data/spec/semipublic/property/boolean_spec.rb +65 -0
  121. data/spec/semipublic/property/class_spec.rb +33 -0
  122. data/spec/semipublic/property/date_spec.rb +43 -0
  123. data/spec/semipublic/property/date_time_spec.rb +46 -0
  124. data/spec/semipublic/property/decimal_spec.rb +82 -0
  125. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  126. data/spec/semipublic/property/float_spec.rb +82 -0
  127. data/spec/semipublic/property/integer_spec.rb +82 -0
  128. data/spec/semipublic/property/serial_spec.rb +13 -0
  129. data/spec/semipublic/property/string_spec.rb +13 -0
  130. data/spec/semipublic/property/text_spec.rb +31 -0
  131. data/spec/semipublic/property/time_spec.rb +50 -0
  132. data/spec/semipublic/property_spec.rb +2 -532
  133. data/spec/semipublic/query/conditions/comparison_spec.rb +171 -169
  134. data/spec/semipublic/query/conditions/operation_spec.rb +53 -51
  135. data/spec/semipublic/query/path_spec.rb +17 -17
  136. data/spec/semipublic/query_spec.rb +47 -78
  137. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  138. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  139. data/spec/semipublic/resource/state/dirty_spec.rb +133 -0
  140. data/spec/semipublic/resource/state/immutable_spec.rb +99 -0
  141. data/spec/semipublic/resource/state/transient_spec.rb +128 -0
  142. data/spec/semipublic/resource/state_spec.rb +226 -0
  143. data/spec/semipublic/shared/property_shared_spec.rb +143 -0
  144. data/spec/semipublic/shared/resource_shared_spec.rb +16 -15
  145. data/spec/semipublic/shared/resource_state_shared_spec.rb +78 -0
  146. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  147. data/spec/spec_helper.rb +21 -97
  148. data/spec/support/types/huge_integer.rb +17 -0
  149. data/spec/unit/array_spec.rb +48 -0
  150. data/spec/unit/hash_spec.rb +35 -0
  151. data/spec/unit/hook_spec.rb +1234 -0
  152. data/spec/unit/lazy_array_spec.rb +1959 -0
  153. data/spec/unit/module_spec.rb +70 -0
  154. data/spec/unit/object_spec.rb +37 -0
  155. data/spec/unit/try_dup_spec.rb +45 -0
  156. data/tasks/local_gemfile.rake +18 -0
  157. data/tasks/spec.rake +0 -3
  158. metadata +197 -71
  159. data/deps.rip +0 -2
  160. data/lib/dm-core/adapters/data_objects_adapter.rb +0 -712
  161. data/lib/dm-core/adapters/mysql_adapter.rb +0 -42
  162. data/lib/dm-core/adapters/oracle_adapter.rb +0 -229
  163. data/lib/dm-core/adapters/postgres_adapter.rb +0 -22
  164. data/lib/dm-core/adapters/sqlite3_adapter.rb +0 -17
  165. data/lib/dm-core/adapters/sqlserver_adapter.rb +0 -114
  166. data/lib/dm-core/adapters/yaml_adapter.rb +0 -111
  167. data/lib/dm-core/core_ext/enumerable.rb +0 -28
  168. data/lib/dm-core/migrations.rb +0 -1427
  169. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +0 -366
  170. data/lib/dm-core/transaction.rb +0 -508
  171. data/lib/dm-core/types/paranoid_boolean.rb +0 -42
  172. data/lib/dm-core/types/paranoid_datetime.rb +0 -41
  173. data/spec/lib/adapter_helpers.rb +0 -105
  174. data/spec/lib/collection_helpers.rb +0 -18
  175. data/spec/lib/pending_helpers.rb +0 -46
  176. data/spec/public/migrations_spec.rb +0 -503
  177. data/spec/public/transaction_spec.rb +0 -153
  178. data/spec/semipublic/adapters/mysql_adapter_spec.rb +0 -17
  179. data/spec/semipublic/adapters/oracle_adapter_spec.rb +0 -194
  180. data/spec/semipublic/adapters/postgres_adapter_spec.rb +0 -17
  181. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +0 -17
  182. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +0 -17
  183. data/spec/semipublic/adapters/yaml_adapter_spec.rb +0 -12
@@ -20,7 +20,7 @@ share_examples_for 'A Collection supporting Strategic Eager Loading' do
20
20
  before :all do
21
21
  @original_adapter = @adapter
22
22
 
23
- @adapter.meta_class.class_eval do
23
+ @adapter.singleton_class.class_eval do
24
24
  def eql?(other)
25
25
  super || self == other
26
26
  end
@@ -28,6 +28,7 @@ share_examples_for 'A Collection supporting Strategic Eager Loading' do
28
28
 
29
29
  @adapter = DataMapper::Repository.adapters[@adapter.name] = CounterAdapter.new(@adapter)
30
30
  @repository.instance_variable_set(:@adapter, @adapter)
31
+ @articles.instance_variable_get(:@query).instance_variable_set(:@repository, @repository)
31
32
  end
32
33
 
33
34
  before :all do
@@ -75,7 +76,7 @@ share_examples_for 'A Resource supporting Strategic Eager Loading' do
75
76
  before :all do
76
77
  @original_adapter = @adapter
77
78
 
78
- @adapter.meta_class.class_eval do
79
+ @adapter.singleton_class.class_eval do
79
80
  def eql?(other)
80
81
  super || other == self
81
82
  end
@@ -86,10 +87,8 @@ share_examples_for 'A Resource supporting Strategic Eager Loading' do
86
87
  end
87
88
 
88
89
  before :all do
89
- @results = []
90
-
91
- @user_model.all.each do |user|
92
- @results << [ user, user.referrer ]
90
+ @results = @user_model.all.map do |user|
91
+ [ user, user.referrer ]
93
92
  end
94
93
 
95
94
  # some storage engines return the data in a different order
@@ -0,0 +1,8 @@
1
+ module DataMapper
2
+ module Assertions
3
+ def assert_kind_of(name, value, *klasses)
4
+ klasses.each { |k| return if value.kind_of?(k) }
5
+ raise ArgumentError, "+#{name}+ should be #{klasses.map { |k| k.name } * ' or '}, but was #{value.class.name}", caller(2)
6
+ end
7
+ end
8
+ end
@@ -30,6 +30,7 @@ module DataMapper
30
30
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
31
  def ==(other)
32
32
  return true if equal?(other)
33
+ return false unless kind_of?(other.class) || other.kind_of?(self.class)
33
34
  #{respond_to.join(' && ')} &&
34
35
  #{equivalent.join(' && ')}
35
36
  end
@@ -0,0 +1,420 @@
1
+ require 'dm-core/support/assertions'
2
+ require 'dm-core/support/local_object_space'
3
+
4
+ # Mainly done here to support spec/unit/hook_spec without
5
+ # needing to require dm-core to setup either extlib or AS
6
+
7
+ begin
8
+ require 'active_support/core_ext/object/singleton_class'
9
+ rescue LoadError
10
+ class Object
11
+ unless respond_to?(:singleton_class)
12
+ def singleton_class
13
+ class << self; self end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ module DataMapper
20
+ #
21
+ # TODO: Write more documentation!
22
+ #
23
+ # Overview
24
+ # ========
25
+ #
26
+ # The Hook module is a very simple set of AOP helpers. Basically, it
27
+ # allows the developer to specify a method or block that should run
28
+ # before or after another method.
29
+ #
30
+ # Usage
31
+ # =====
32
+ #
33
+ # Halting The Hook Stack
34
+ #
35
+ # Inheritance
36
+ #
37
+ # Other Goodies
38
+ #
39
+ # Please bring up any issues regarding Hooks with carllerche on IRC
40
+ #
41
+ module Hook
42
+
43
+ def self.included(base)
44
+ base.extend(ClassMethods)
45
+ base.const_set("CLASS_HOOKS", {}) unless base.const_defined?("CLASS_HOOKS")
46
+ base.const_set("INSTANCE_HOOKS", {}) unless base.const_defined?("INSTANCE_HOOKS")
47
+ base.class_eval do
48
+ class << self
49
+ def method_added(name)
50
+ process_method_added(name, :instance)
51
+ end
52
+
53
+ def singleton_method_added(name)
54
+ process_method_added(name, :class)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ module ClassMethods
61
+ extend DataMapper::LocalObjectSpace
62
+ include DataMapper::Assertions
63
+ # Inject code that executes before the target class method.
64
+ #
65
+ # @param target_method<Symbol> the name of the class method to inject before
66
+ # @param method_sym<Symbol> the name of the method to run before the
67
+ # target_method
68
+ # @param block<Block> the code to run before the target_method
69
+ #
70
+ # @note
71
+ # Either method_sym or block is required.
72
+ # -
73
+ # @api public
74
+ def before_class_method(target_method, method_sym = nil, &block)
75
+ install_hook :before, target_method, method_sym, :class, &block
76
+ end
77
+
78
+ #
79
+ # Inject code that executes after the target class method.
80
+ #
81
+ # @param target_method<Symbol> the name of the class method to inject after
82
+ # @param method_sym<Symbol> the name of the method to run after the target_method
83
+ # @param block<Block> the code to run after the target_method
84
+ #
85
+ # @note
86
+ # Either method_sym or block is required.
87
+ # -
88
+ # @api public
89
+ def after_class_method(target_method, method_sym = nil, &block)
90
+ install_hook :after, target_method, method_sym, :class, &block
91
+ end
92
+
93
+ #
94
+ # Inject code that executes before the target instance method.
95
+ #
96
+ # @param target_method<Symbol> the name of the instance method to inject before
97
+ # @param method_sym<Symbol> the name of the method to run before the
98
+ # target_method
99
+ # @param block<Block> the code to run before the target_method
100
+ #
101
+ # @note
102
+ # Either method_sym or block is required.
103
+ # -
104
+ # @api public
105
+ def before(target_method, method_sym = nil, &block)
106
+ install_hook :before, target_method, method_sym, :instance, &block
107
+ end
108
+
109
+ #
110
+ # Inject code that executes after the target instance method.
111
+ #
112
+ # @param target_method<Symbol> the name of the instance method to inject after
113
+ # @param method_sym<Symbol> the name of the method to run after the
114
+ # target_method
115
+ # @param block<Block> the code to run after the target_method
116
+ #
117
+ # @note
118
+ # Either method_sym or block is required.
119
+ # -
120
+ # @api public
121
+ def after(target_method, method_sym = nil, &block)
122
+ install_hook :after, target_method, method_sym, :instance, &block
123
+ end
124
+
125
+ # Register a class method as hookable. Registering a method means that
126
+ # before hooks will be run immediately before the method is invoked and
127
+ # after hooks will be called immediately after the method is invoked.
128
+ #
129
+ # @param hookable_method<Symbol> The name of the class method that should
130
+ # be hookable
131
+ # -
132
+ # @api public
133
+ def register_class_hooks(*hooks)
134
+ hooks.each { |hook| register_hook(hook, :class) }
135
+ end
136
+
137
+ # Register aninstance method as hookable. Registering a method means that
138
+ # before hooks will be run immediately before the method is invoked and
139
+ # after hooks will be called immediately after the method is invoked.
140
+ #
141
+ # @param hookable_method<Symbol> The name of the instance method that should
142
+ # be hookable
143
+ # -
144
+ # @api public
145
+ def register_instance_hooks(*hooks)
146
+ hooks.each { |hook| register_hook(hook, :instance) }
147
+ end
148
+
149
+ # Not yet implemented
150
+ def reset_hook!(target_method, scope)
151
+ raise NotImplementedError
152
+ end
153
+
154
+ # --- Alright kids... the rest is internal stuff ---
155
+
156
+ # Returns the correct HOOKS Hash depending on whether we are
157
+ # working with class methods or instance methods
158
+ def hooks_with_scope(scope)
159
+ case scope
160
+ when :class then class_hooks
161
+ when :instance then instance_hooks
162
+ else raise ArgumentError, 'You need to pass :class or :instance as scope'
163
+ end
164
+ end
165
+
166
+ def class_hooks
167
+ self.const_get("CLASS_HOOKS")
168
+ end
169
+
170
+ def instance_hooks
171
+ self.const_get("INSTANCE_HOOKS")
172
+ end
173
+
174
+ # Registers a method as hookable. Registering hooks involves the following
175
+ # process
176
+ #
177
+ # * Create a blank entry in the HOOK Hash for the method.
178
+ # * Define the methods that execute the before and after hook stack.
179
+ # These methods will be no-ops at first, but everytime a new hook is
180
+ # defined, the methods will be redefined to incorporate the new hook.
181
+ # * Redefine the method that is to be hookable so that the hook stacks
182
+ # are invoked approprietly.
183
+ def register_hook(target_method, scope)
184
+ if scope == :instance && !method_defined?(target_method)
185
+ raise ArgumentError, "#{target_method} instance method does not exist"
186
+ elsif scope == :class && !respond_to?(target_method)
187
+ raise ArgumentError, "#{target_method} class method does not exist"
188
+ end
189
+
190
+ hooks = hooks_with_scope(scope)
191
+
192
+ if hooks[target_method].nil?
193
+ hooks[target_method] = {
194
+ # We need to keep track of which class in the Inheritance chain the
195
+ # method was declared hookable in. Every time a child declares a new
196
+ # hook for the method, the hook stack invocations need to be redefined
197
+ # in the original Class. See #define_hook_stack_execution_methods
198
+ :before => [], :after => [], :in => self
199
+ }
200
+
201
+ define_hook_stack_execution_methods(target_method, scope)
202
+ define_advised_method(target_method, scope)
203
+ end
204
+ end
205
+
206
+ # Is the method registered as a hookable in the given scope.
207
+ def registered_as_hook?(target_method, scope)
208
+ ! hooks_with_scope(scope)[target_method].nil?
209
+ end
210
+
211
+ # Generates names for the various utility methods. We need to do this because
212
+ # the various utility methods should not end in = so, while we're at it, we
213
+ # might as well get rid of all punctuation.
214
+ def hook_method_name(target_method, prefix, suffix)
215
+ target_method = target_method.to_s
216
+
217
+ case target_method[-1,1]
218
+ when '?' then "#{prefix}_#{target_method[0..-2]}_ques_#{suffix}"
219
+ when '!' then "#{prefix}_#{target_method[0..-2]}_bang_#{suffix}"
220
+ when '=' then "#{prefix}_#{target_method[0..-2]}_eq_#{suffix}"
221
+ # I add a _nan_ suffix here so that we don't ever encounter
222
+ # any naming conflicts.
223
+ else "#{prefix}_#{target_method[0..-1]}_nan_#{suffix}"
224
+ end
225
+ end
226
+
227
+ # This will need to be refactored
228
+ def process_method_added(method_name, scope)
229
+ hooks_with_scope(scope).each do |target_method, hooks|
230
+ if hooks[:before].any? { |hook| hook[:name] == method_name }
231
+ define_hook_stack_execution_methods(target_method, scope)
232
+ end
233
+
234
+ if hooks[:after].any? { |hook| hook[:name] == method_name }
235
+ define_hook_stack_execution_methods(target_method, scope)
236
+ end
237
+ end
238
+ end
239
+
240
+ # Defines two methods. One method executes the before hook stack. The other executes
241
+ # the after hook stack. This method will be called many times during the Class definition
242
+ # process. It should be called for each hook that is defined. It will also be called
243
+ # when a hook is redefined (to make sure that the arity hasn't changed).
244
+ def define_hook_stack_execution_methods(target_method, scope)
245
+ unless registered_as_hook?(target_method, scope)
246
+ raise ArgumentError, "#{target_method} has not be registered as a hookable #{scope} method"
247
+ end
248
+
249
+ hooks = hooks_with_scope(scope)
250
+
251
+ before_hooks = hooks[target_method][:before]
252
+ before_hooks = before_hooks.map{ |info| inline_call(info, scope) }.join("\n")
253
+
254
+ after_hooks = hooks[target_method][:after]
255
+ after_hooks = after_hooks.map{ |info| inline_call(info, scope) }.join("\n")
256
+
257
+ before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
258
+ after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack')
259
+
260
+ hooks[target_method][:in].class_eval <<-RUBY, __FILE__, __LINE__ + 1
261
+ #{scope == :class ? 'class << self' : ''}
262
+
263
+ private
264
+
265
+ remove_method :#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} }
266
+ def #{before_hook_name}(*args)
267
+ #{before_hooks}
268
+ end
269
+
270
+ remove_method :#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{after_hook_name} }
271
+ def #{after_hook_name}(*args)
272
+ #{after_hooks}
273
+ end
274
+
275
+ #{scope == :class ? 'end' : ''}
276
+ RUBY
277
+ end
278
+
279
+ # Returns ruby code that will invoke the hook. It checks the arity of the hook method
280
+ # and passes arguments accordingly.
281
+ def inline_call(method_info, scope)
282
+ DataMapper::Hook::ClassMethods.hook_scopes << method_info[:from]
283
+ name = method_info[:name]
284
+ if scope == :instance
285
+ args = method_defined?(name) && instance_method(name).arity != 0 ? '*args' : ''
286
+ %(#{name}(#{args}) if self.class <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id}))
287
+ else
288
+ args = respond_to?(name) && method(name).arity != 0 ? '*args' : ''
289
+ %(#{name}(#{args}) if self <= DataMapper::Hook::ClassMethods.object_by_id(#{method_info[:from].object_id}))
290
+ end
291
+ end
292
+
293
+ def define_advised_method(target_method, scope)
294
+ args = args_for(method_with_scope(target_method, scope))
295
+
296
+ renamed_target = hook_method_name(target_method, 'hookable_', 'before_advised')
297
+
298
+ source = <<-EOD
299
+ def #{target_method}(#{args})
300
+ retval = nil
301
+ catch(:halt) do
302
+ #{hook_method_name(target_method, 'execute_before', 'hook_stack')}(#{args})
303
+ retval = #{renamed_target}(#{args})
304
+ #{hook_method_name(target_method, 'execute_after', 'hook_stack')}(retval, #{args})
305
+ retval
306
+ end
307
+ end
308
+ EOD
309
+
310
+ if scope == :instance && !instance_methods(false).any? { |m| m.to_sym == target_method }
311
+ send(:alias_method, renamed_target, target_method)
312
+
313
+ proxy_module = Module.new
314
+ proxy_module.class_eval(source, __FILE__, __LINE__)
315
+ self.send(:include, proxy_module)
316
+ else
317
+ source = %{alias_method :#{renamed_target}, :#{target_method}\n#{source}}
318
+ source = %{class << self\n#{source}\nend} if scope == :class
319
+ class_eval(source, __FILE__, __LINE__)
320
+ end
321
+ end
322
+
323
+ # --- Add a hook ---
324
+
325
+ def install_hook(type, target_method, method_sym, scope, &block)
326
+ assert_kind_of 'target_method', target_method, Symbol
327
+ assert_kind_of 'method_sym', method_sym, Symbol unless method_sym.nil?
328
+ assert_kind_of 'scope', scope, Symbol
329
+
330
+ if !block_given? and method_sym.nil?
331
+ raise ArgumentError, "You need to pass 2 arguments to \"#{type}\"."
332
+ end
333
+
334
+ if method_sym.to_s[-1,1] == '='
335
+ raise ArgumentError, "Methods ending in = cannot be hooks"
336
+ end
337
+
338
+ unless [ :class, :instance ].include?(scope)
339
+ raise ArgumentError, 'You need to pass :class or :instance as scope'
340
+ end
341
+
342
+ if registered_as_hook?(target_method, scope)
343
+ hooks = hooks_with_scope(scope)
344
+
345
+ #if this hook is previously declared in a sibling or cousin we must move the :in class
346
+ #to the common ancestor to get both hooks to run.
347
+ if !(hooks[target_method][:in] <=> self)
348
+ before_hook_name = hook_method_name(target_method, 'execute_before', 'hook_stack')
349
+ after_hook_name = hook_method_name(target_method, 'execute_after', 'hook_stack')
350
+
351
+ hooks[target_method][:in].class_eval <<-RUBY, __FILE__, __LINE__ + 1
352
+ remove_method :#{before_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} }
353
+ def #{before_hook_name}(*args)
354
+ super
355
+ end
356
+
357
+ remove_method :#{after_hook_name} if instance_methods(false).any? { |m| m.to_sym == :#{before_hook_name} }
358
+ def #{after_hook_name}(*args)
359
+ super
360
+ end
361
+ RUBY
362
+
363
+ while !(hooks[target_method][:in] <=> self) do
364
+ hooks[target_method][:in] = hooks[target_method][:in].superclass
365
+ end
366
+
367
+ define_hook_stack_execution_methods(target_method, scope)
368
+ hooks[target_method][:in].class_eval{define_advised_method(target_method, scope)}
369
+ end
370
+ else
371
+ register_hook(target_method, scope)
372
+ hooks = hooks_with_scope(scope)
373
+ end
374
+
375
+ #if we were passed a block, create a method out of it.
376
+ if block
377
+ method_sym = "__hooks_#{type}_#{quote_method(target_method)}_#{hooks[target_method][type].length}".to_sym
378
+ if scope == :class
379
+ singleton_class.instance_eval do
380
+ define_method(method_sym, &block)
381
+ end
382
+ else
383
+ define_method(method_sym, &block)
384
+ end
385
+ end
386
+
387
+ # Adds method to the stack an redefines the hook invocation method
388
+ hooks[target_method][type] << { :name => method_sym, :from => self }
389
+ define_hook_stack_execution_methods(target_method, scope)
390
+ end
391
+
392
+ # --- Helpers ---
393
+
394
+ def args_for(method)
395
+ if method.arity == 0
396
+ "&block"
397
+ elsif method.arity > 0
398
+ "_" << (1 .. method.arity).to_a.join(", _") << ", &block"
399
+ elsif (method.arity + 1) < 0
400
+ "_" << (1 .. (method.arity).abs - 1).to_a.join(", _") << ", *args, &block"
401
+ else
402
+ "*args, &block"
403
+ end
404
+ end
405
+
406
+ def method_with_scope(name, scope)
407
+ case scope
408
+ when :class then method(name)
409
+ when :instance then instance_method(name)
410
+ else raise ArgumentError, 'You need to pass :class or :instance as scope'
411
+ end
412
+ end
413
+
414
+ def quote_method(name)
415
+ name.to_s.gsub(/\?$/, '_q_').gsub(/!$/, '_b_').gsub(/=$/, '_eq_')
416
+ end
417
+ end
418
+
419
+ end
420
+ end