sbf-dm-core 1.3.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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,1216 @@
1
+ require_relative '../spec_helper'
2
+ require 'dm-core/support/hook'
3
+
4
+ describe DataMapper::Hook do
5
+
6
+ before do
7
+ @module = Module.new do
8
+ def greet; greetings_from_module; end;
9
+ end
10
+
11
+ @class = Class.new do
12
+ include DataMapper::Hook
13
+
14
+ def hookable; end;
15
+ def self.clakable; end;
16
+ def ambiguous; hi_mom!; end;
17
+ def self.ambiguous; hi_dad!; end;
18
+ end
19
+
20
+ @another_class = Class.new do
21
+ include DataMapper::Hook
22
+ end
23
+
24
+ @other = Class.new do
25
+ include DataMapper::Hook
26
+
27
+ def hookable; end
28
+ def self.clakable; end;
29
+ end
30
+
31
+ @class.register_instance_hooks :hookable
32
+ @class.register_class_hooks :clakable
33
+ end
34
+
35
+ #
36
+ # Specs out how hookable methods are registered
37
+ #
38
+ describe 'explicit hookable method registration' do
39
+ describe 'for class methods' do
40
+ it "doesn't confuse instance method hooks and class method hooks" do
41
+ @class.register_instance_hooks :ambiguous
42
+ @class.register_class_hooks :ambiguous
43
+
44
+ expect(@class).to receive(:hi_dad!)
45
+ @class.ambiguous
46
+ end
47
+
48
+ it 'is able to register multiple hook-able methods at once' do
49
+ %w(method_one method_two method_three).each do |method|
50
+ @another_class.class_eval %(def self.#{method}; end;)
51
+ end
52
+
53
+ @another_class.register_class_hooks :method_one, :method_two, :method_three
54
+ expect(@another_class.class_hooks).to have_key(:method_one)
55
+ expect(@another_class.class_hooks).to have_key(:method_two)
56
+ expect(@another_class.class_hooks).to have_key(:method_three)
57
+ end
58
+
59
+ it 'does not allow a method that does not exist to be registered as hookable' do
60
+ expect { @another_class.register_class_hooks :method_one }.to raise_error(ArgumentError)
61
+ end
62
+
63
+ it 'does allow hooks to be registered on methods from module extensions' do
64
+ @class.extend(@module)
65
+ @class.register_class_hooks :greet
66
+ expect(@class.class_hooks[:greet]).not_to be_nil
67
+ end
68
+
69
+ it 'allows modules to register hooks in the self.extended method' do
70
+ @module.class_eval do
71
+ def self.extended(base)
72
+ base.register_class_hooks :greet
73
+ end
74
+ end
75
+ @class.extend(@module)
76
+ expect(@class.class_hooks[:greet]).not_to be_nil
77
+ end
78
+
79
+ it 'is able to register protected methods as hooks' do
80
+ @class.class_eval %{protected; def self.protected_hookable; end;}
81
+ expect { @class.register_class_hooks(:protected_hookable) }.not_to raise_error
82
+ end
83
+
84
+ it 'is not able to register private methods as hooks' do
85
+ @class.class_eval %{class << self; private; def private_hookable; end; end;}
86
+ expect { @class.register_class_hooks(:private_hookable) }.to raise_error(ArgumentError)
87
+ end
88
+
89
+ it 'allows advising methods ending in ? or !' do
90
+ @class.class_eval do
91
+ def self.hookable!; two!; end;
92
+ def self.hookable?; three!; end;
93
+ register_class_hooks :hookable!, :hookable?
94
+ end
95
+ @class.before_class_method(:hookable!) { one! }
96
+ @class.after_class_method(:hookable?) { four! }
97
+
98
+ expect(@class).to receive(:one!).once.ordered
99
+ expect(@class).to receive(:two!).once.ordered
100
+ expect(@class).to receive(:three!).once.ordered
101
+ expect(@class).to receive(:four!).once.ordered
102
+
103
+ @class.hookable!
104
+ @class.hookable?
105
+ end
106
+
107
+ it 'allows hooking methods ending in ?, ! or = with method hooks' do
108
+ @class.class_eval do
109
+ def self.before_hookable!; one!; end;
110
+ def self.hookable!; two!; end;
111
+ def self.hookable?; three!; end;
112
+ def self.after_hookable?; four!; end;
113
+ register_class_hooks :hookable!, :hookable?
114
+ end
115
+ @class.before_class_method(:hookable!, :before_hookable!)
116
+ @class.after_class_method(:hookable?, :after_hookable?)
117
+
118
+ expect(@class).to receive(:one!).once.ordered
119
+ expect(@class).to receive(:two!).once.ordered
120
+ expect(@class).to receive(:three!).once.ordered
121
+ expect(@class).to receive(:four!).once.ordered
122
+
123
+ @class.hookable!
124
+ @class.hookable?
125
+ end
126
+
127
+ it 'allows hooking methods that have single character names' do
128
+ @class.class_eval do
129
+ def self.a; end;
130
+ def self.b; end;
131
+ end
132
+
133
+ @class.before_class_method(:a) { omg! }
134
+ @class.before_class_method(:b) { hi2u! }
135
+
136
+ expect(@class).to receive(:omg!).once.ordered
137
+ expect(@class).to receive(:hi2u!).once.ordered
138
+ @class.a
139
+ @class.b
140
+ end
141
+ end
142
+
143
+ describe 'for instance methods' do
144
+ it "doesn't confuse instance method hooks and class method hooks" do
145
+ @class.register_instance_hooks :ambiguous
146
+ @class.register_class_hooks :ambiguous
147
+
148
+ inst = @class.new
149
+ expect(inst).to receive(:hi_mom!)
150
+ inst.ambiguous
151
+ end
152
+
153
+ it 'is able to register multiple hook-able methods at once' do
154
+ %w(method_one method_two method_three).each do |method|
155
+ @another_class.send(:define_method, method) {}
156
+ end
157
+
158
+ @another_class.register_instance_hooks :method_one, :method_two, :method_three
159
+ expect(@another_class.instance_hooks).to have_key(:method_one)
160
+ expect(@another_class.instance_hooks).to have_key(:method_two)
161
+ expect(@another_class.instance_hooks).to have_key(:method_three)
162
+ end
163
+
164
+ it 'does not allow a method that does not exist to be registered as hookable' do
165
+ expect { @another_class.register_instance_hooks :method_one }.to raise_error(ArgumentError)
166
+ end
167
+
168
+ it 'does allow hooks to be registered on included module methods' do
169
+ @class.send(:include, @module)
170
+ @class.register_instance_hooks :greet
171
+ expect(@class.instance_hooks[:greet]).not_to be_nil
172
+ end
173
+
174
+ it 'allows modules to register hooks in the self.included method' do
175
+ @module.class_eval do
176
+ def self.included(base)
177
+ base.register_instance_hooks :greet
178
+ end
179
+ end
180
+ @class.send(:include, @module)
181
+ expect(@class.instance_hooks[:greet]).not_to be_nil
182
+ end
183
+
184
+ it 'is able to register protected methods as hooks' do
185
+ @class.class_eval %(protected; def protected_hookable; end;), __FILE__, __LINE__
186
+ expect { @class.register_instance_hooks(:protected_hookable) }.not_to raise_error
187
+ end
188
+
189
+ it 'is not able to register private methods as hooks' do
190
+ @class.class_eval %(private; def private_hookable; end;), __FILE__, __LINE__
191
+ expect { @class.register_instance_hooks(:private_hookable) }.to raise_error(ArgumentError)
192
+ end
193
+
194
+ it 'allows hooking methods ending in ? or ! with block hooks' do
195
+ @class.class_eval do
196
+ def hookable!; two!; end;
197
+ def hookable?; three!; end;
198
+ register_instance_hooks :hookable!, :hookable?
199
+ end
200
+ @class.before(:hookable!) { one! }
201
+ @class.after(:hookable?) { four! }
202
+
203
+ inst = @class.new
204
+ expect(inst).to receive(:one!).once.ordered
205
+ expect(inst).to receive(:two!).once.ordered
206
+ expect(inst).to receive(:three!).once.ordered
207
+ expect(inst).to receive(:four!).once.ordered
208
+
209
+ inst.hookable!
210
+ inst.hookable?
211
+ end
212
+
213
+ it 'allows hooking methods ending in ?, ! or = with method hooks' do
214
+ @class.class_eval do
215
+ def before_hookable(val); one!; end;
216
+ def hookable=(val); two!; end;
217
+ def hookable?; three!; end;
218
+ def after_hookable?; four!; end;
219
+ register_instance_hooks :hookable=, :hookable?
220
+ end
221
+ @class.before(:hookable=, :before_hookable)
222
+ @class.after(:hookable?, :after_hookable?)
223
+
224
+ inst = @class.new
225
+ expect(inst).to receive(:one!).once.ordered
226
+ expect(inst).to receive(:two!).once.ordered
227
+ expect(inst).to receive(:three!).once.ordered
228
+ expect(inst).to receive(:four!).once.ordered
229
+
230
+ inst.hookable = 'hello'
231
+ inst.hookable?
232
+ end
233
+
234
+ it 'allows hooking methods that have single character names' do
235
+ @class.class_eval do
236
+ def a; end;
237
+ def b; end;
238
+ end
239
+
240
+ @class.before(:a) { omg! }
241
+ @class.before(:b) { hi2u! }
242
+
243
+ inst = @class.new
244
+ expect(inst).to receive(:omg!).once.ordered
245
+ expect(inst).to receive(:hi2u!).once.ordered
246
+ inst.a
247
+ inst.b
248
+ end
249
+ end
250
+
251
+ end
252
+
253
+ describe 'implicit hook-able method registration' do
254
+ describe 'for class methods' do
255
+ it 'implicitly registers the method as hook-able' do
256
+ @class.class_eval %{def self.implicit_hook; end;}
257
+ @class.before_class_method(:implicit_hook) { hello }
258
+
259
+ expect(@class).to receive(:hello)
260
+ @class.implicit_hook
261
+ end
262
+ end
263
+
264
+ describe 'for instance methods' do
265
+ it 'implicitly registers the method as hook-able' do
266
+ @class.class_eval %{def implicit_hook; end;}
267
+ @class.before(:implicit_hook) { hello }
268
+
269
+ inst = @class.new
270
+ expect(inst).to receive(:hello)
271
+ inst.implicit_hook
272
+ end
273
+
274
+ it 'does not overwrite methods included by modules after the hook is declared' do
275
+ my_module = Module.new do
276
+ # Just another module
277
+ @another_module = Module.new do
278
+ def some_method; "Hello " + super; end;
279
+ end
280
+
281
+ def some_method; "world"; end;
282
+
283
+ def self.included(base)
284
+ base.before(:some_method, :a_method)
285
+ base.send(:include, @another_module)
286
+ end
287
+ end
288
+
289
+ @class.class_eval { include my_module }
290
+
291
+ inst = @class.new
292
+ expect(inst).to receive(:a_method)
293
+ expect(inst.some_method).to eq 'Hello world'
294
+ end
295
+ end
296
+
297
+ end
298
+
299
+ describe 'hook method registration' do
300
+ describe 'for class methods' do
301
+ it 'complains when only one argument is passed' do
302
+ expect { @class.before_class_method(:clakable) }.to raise_error(ArgumentError)
303
+ expect { @class.after_class_method(:clakable) }.to raise_error(ArgumentError)
304
+ end
305
+
306
+ it 'complains when target_method is not a symbol' do
307
+ expect { @class.before_class_method('clakable', :ambiguous) }.to raise_error(ArgumentError)
308
+ expect { @class.after_class_method('clakable', :ambiguous) }.to raise_error(ArgumentError)
309
+ end
310
+
311
+ it 'complains when method_sym is not a symbol' do
312
+ expect { @class.before_class_method(:clakable, 'ambiguous') }.to raise_error(ArgumentError)
313
+ expect { @class.after_class_method(:clakable, 'ambiguous') }.to raise_error(ArgumentError)
314
+ end
315
+
316
+ it 'does not allow methods ending in = to be hooks' do
317
+ expect { @class.before_class_method(:clakable, :annoying=) }.to raise_error(ArgumentError)
318
+ expect { @class.after_class_method(:clakable, :annoying=) }.to raise_error(ArgumentError)
319
+ end
320
+ end
321
+
322
+ describe 'for instance methods' do
323
+ it 'complains when only one argument is passed' do
324
+ expect { @class.before(:hookable) }.to raise_error(ArgumentError)
325
+ expect { @class.after(:hookable) }.to raise_error(ArgumentError)
326
+ end
327
+
328
+ it 'complains when target_method is not a symbol' do
329
+ expect { @class.before('hookable', :ambiguous) }.to raise_error(ArgumentError)
330
+ expect { @class.after('hookable', :ambiguous) }.to raise_error(ArgumentError)
331
+ end
332
+
333
+ it 'complains when method_sym is not a symbol' do
334
+ expect { @class.before(:hookable, 'ambiguous') }.to raise_error(ArgumentError)
335
+ expect { @class.after(:hookable, 'ambiguous') }.to raise_error(ArgumentError)
336
+ end
337
+
338
+ it 'does not allow methods ending in = to be hooks' do
339
+ expect { @class.before(:hookable, :annoying=) }.to raise_error(ArgumentError)
340
+ expect { @class.after(:hookable, :annoying=) }.to raise_error(ArgumentError)
341
+ end
342
+ end
343
+
344
+ end
345
+
346
+ #
347
+ # Specs out how hook methods / blocks are invoked when there is no inheritance
348
+ # involved
349
+ #
350
+ describe 'hook invocation without inheritance' do
351
+ describe 'for class methods' do
352
+ it 'runs an advice block' do
353
+ @class.before_class_method(:clakable) { hi_mom! }
354
+ expect(@class).to receive(:hi_mom!)
355
+ @class.clakable
356
+ end
357
+
358
+ it 'runs an advice method' do
359
+ @class.class_eval %{def self.before_method; hi_mom!; end;}
360
+ @class.before_class_method(:clakable, :before_method)
361
+
362
+ expect(@class).to receive(:hi_mom!)
363
+ @class.clakable
364
+ end
365
+
366
+ it "does not pass any of the hookable method's arguments if the hook block does not accept arguments" do
367
+ @class.class_eval do
368
+ def self.method_with_args(one, two, three); end;
369
+ before_class_method(:method_with_args) { hi_mom! }
370
+ end
371
+
372
+ expect(@class).to receive(:hi_mom!)
373
+ @class.method_with_args(1, 2, 3)
374
+ end
375
+
376
+ it "does not pass any of the hookable method's arguments if the hook method does not accept arguments" do
377
+ @class.class_eval do
378
+ def self.method_with_args(one, two, three); end;
379
+ def self.before_method_with_args; hi_mom!; end;
380
+ before_class_method(:method_with_args, :before_method_with_args)
381
+ end
382
+
383
+ expect(@class).to receive(:hi_mom!)
384
+ @class.method_with_args(1, 2, 3)
385
+ end
386
+
387
+ it "does not pass any of the hookable method's arguments if the hook is declared after it is registered and does not accept arguments" do
388
+ @class.class_eval do
389
+ def self.method_with_args(one, two, three); end;
390
+ before_class_method(:method_with_args, :before_method_with_args)
391
+ def self.before_method_with_args; hi_mom!; end;
392
+ end
393
+
394
+ expect(@class).to receive(:hi_mom!)
395
+ @class.method_with_args(1, 2, 3)
396
+ end
397
+
398
+ it "does not pass any of the hookable method's arguments if the hook has been re-defined not to accept arguments" do
399
+ @class.class_eval do
400
+ def self.method_with_args(one, two, three); end;
401
+ def self.before_method_with_args(one, two, three); hi_mom!; end;
402
+ before_class_method(:method_with_args, :before_method_with_args)
403
+ orig_verbose, $VERBOSE = $VERBOSE, false
404
+ def self.before_method_with_args; hi_dad!; end;
405
+ $VERBOSE = orig_verbose
406
+ end
407
+
408
+ expect(@class).not_to receive(:hi_mom1)
409
+ expect(@class).to receive(:hi_dad!)
410
+ @class.method_with_args(1, 2, 3)
411
+ end
412
+
413
+ it 'passes the hookable method arguments to the hook method if the hook method takes arguments' do
414
+ @class.class_eval do
415
+ def self.hook_this(word, lol); end;
416
+ register_class_hooks(:hook_this)
417
+ def self.before_hook_this(word, lol); hi_mom!(word, lol); end;
418
+ before_class_method(:hook_this, :before_hook_this)
419
+ end
420
+
421
+ expect(@class).to receive(:hi_mom!).with('omg', 'hi2u')
422
+ @class.hook_this('omg', 'hi2u')
423
+ end
424
+
425
+ it 'passes the hookable method arguments to the hook block if the hook block takes arguments' do
426
+ @class.class_eval do
427
+ def self.method_with_args(word, lol); end;
428
+ before_class_method(:method_with_args) { |one, two| hi_mom!(one, two) }
429
+ end
430
+
431
+ expect(@class).to receive(:hi_mom!).with('omg', 'hi2u')
432
+ @class.method_with_args('omg', 'hi2u')
433
+ end
434
+
435
+ it 'passes the hookable method arguments to the hook method if the hook method was re-defined to accept arguments' do
436
+ @class.class_eval do
437
+ def self.method_with_args(word, lol); end;
438
+ def self.before_method_with_args; hi_mom!; end;
439
+ before_class_method(:method_with_args, :before_method_with_args)
440
+ orig_verbose, $VERBOSE = $VERBOSE, false
441
+ def self.before_method_with_args(word, lol); hi_dad!(word, lol); end;
442
+ $VERBOSE = orig_verbose
443
+ end
444
+
445
+ expect(@class).not_to receive(:hi_mom!)
446
+ expect(@class).to receive(:hi_dad!).with('omg', 'hi2u')
447
+ @class.method_with_args('omg', 'hi2u')
448
+ end
449
+
450
+ it 'works with glob arguments (or whatever you call em)' do
451
+ @class.class_eval do
452
+ def self.hook_this(*args); end;
453
+ def self.before_hook_this(*args); hi_mom!(*args); end;
454
+ before_class_method(:hook_this, :before_hook_this)
455
+ end
456
+
457
+ expect(@class).to receive(:hi_mom!).with('omg', 'hi2u', 'lolercoaster')
458
+ @class.hook_this('omg', 'hi2u', 'lolercoaster')
459
+ end
460
+
461
+ it 'allows the use of before and after together' do
462
+ @class.class_eval %{def self.before_hook; first!; end;}
463
+ @class.before_class_method(:clakable, :before_hook)
464
+ @class.after_class_method(:clakable) { last! }
465
+
466
+ expect(@class).to receive(:first!).once.ordered
467
+ expect(@class).to receive(:last!).once.ordered
468
+ @class.clakable
469
+ end
470
+
471
+ it 'are able to use private methods as hooks' do
472
+ @class.class_eval do
473
+ class << self
474
+ private
475
+ def nike; doit!; end;
476
+ end
477
+ before_class_method(:clakable, :nike)
478
+ end
479
+
480
+ expect(@class).to receive(:doit!)
481
+ @class.clakable
482
+ end
483
+ end
484
+
485
+ describe 'for instance methods' do
486
+ it 'runs an advice block' do
487
+ @class.before(:hookable) { hi_mom! }
488
+
489
+ inst = @class.new
490
+ expect(inst).to receive(:hi_mom!)
491
+ inst.hookable
492
+ end
493
+
494
+ it 'runs an advice method' do
495
+ @class.send(:define_method, :before_method) { hi_mom! }
496
+ @class.before(:hookable, :before_method)
497
+
498
+ inst = @class.new
499
+ expect(inst).to receive(:hi_mom!)
500
+ inst.hookable
501
+ end
502
+
503
+ it "does not pass any of the hookable method's arguments if the hook block does not accept arguments" do
504
+ @class.class_eval do
505
+ def method_with_args(one, two, three); end;
506
+ before(:method_with_args) { hi_mom! }
507
+ end
508
+
509
+ inst = @class.new
510
+ expect(inst).to receive(:hi_mom!)
511
+ inst.method_with_args(1, 2, 3)
512
+ end
513
+
514
+ it "does not pass any of the hookable method's arguments if the hook method does not accept arguments" do
515
+ @class.class_eval do
516
+ def method_with_args(one, two, three); end;
517
+ def before_method_with_args; hi_mom!; end;
518
+ before(:method_with_args, :before_method_with_args)
519
+ end
520
+
521
+ inst = @class.new
522
+ expect(inst).to receive(:hi_mom!)
523
+ inst.method_with_args(1, 2, 3)
524
+ end
525
+
526
+ it "does not pass any of the hookable method's arguments if the hook is declared after it is registered and does not accept arguments" do
527
+ @class.class_eval do
528
+ def method_with_args(one, two, three); end;
529
+ before(:method_with_args, :before_method_with_args)
530
+ def before_method_with_args; hi_mom!; end;
531
+ end
532
+
533
+ inst = @class.new
534
+ expect(inst).to receive(:hi_mom!)
535
+ inst.method_with_args(1, 2, 3)
536
+ end
537
+
538
+ it "does not pass any of the hookable method's arguments if the hook has been re-defined not to accept arguments" do
539
+ @class.class_eval do
540
+ def method_with_args(one, two, three); end;
541
+ def before_method_with_args(one, two, three); hi_mom!; end;
542
+ before(:method_with_args, :before_method_with_args)
543
+ orig_verbose, $VERBOSE = $VERBOSE, false
544
+ def before_method_with_args; hi_dad!; end;
545
+ $VERBOSE = orig_verbose
546
+ end
547
+
548
+ inst = @class.new
549
+ expect(inst).not_to receive(:hi_mom1)
550
+ expect(inst).to receive(:hi_dad!)
551
+ inst.method_with_args(1, 2, 3)
552
+ end
553
+
554
+ it 'passes the hookable method arguments to the hook method if the hook method takes arguments' do
555
+ @class.class_eval do
556
+ def method_with_args(word, lol); end;
557
+ def before_method_with_args(one, two); hi_mom!(one, two); end;
558
+ before(:method_with_args, :before_method_with_args)
559
+ end
560
+
561
+ inst = @class.new
562
+ inst.method_with_args("omg", "hi2u")
563
+ expect(inst).to receive(:hi_mom!).with('omg', 'hi2u')
564
+ end
565
+
566
+ it 'passes the hookable method arguments to the hook block if the hook block takes arguments' do
567
+ @class.class_eval do
568
+ def method_with_args(word, lol); end;
569
+ before(:method_with_args) { |one, two| hi_mom!(one, two) }
570
+ end
571
+
572
+ inst = @class.new
573
+ expect(inst).to receive(:hi_mom!).with('omg', 'hi2u')
574
+ inst.method_with_args('omg', 'hi2u')
575
+ end
576
+
577
+ it 'passes the hookable method arguments to the hook method if the hook method was re-defined to accept arguments' do
578
+ @class.class_eval do
579
+ def method_with_args(word, lol); end;
580
+ def before_method_with_args; hi_mom!; end;
581
+ before(:method_with_args, :before_method_with_args)
582
+ orig_verbose, $VERBOSE = $VERBOSE, false
583
+ def before_method_with_args(word, lol); hi_dad!(word, lol); end;
584
+ $VERBOSE = orig_verbose
585
+ end
586
+
587
+ inst = @class.new
588
+ expect(inst).not_to receive(:hi_mom!)
589
+ expect(inst).to receive(:hi_dad!).with('omg', 'hi2u')
590
+ inst.method_with_args('omg', 'hi2u')
591
+ end
592
+
593
+ it 'does not pass the method return value to the after hook if the method does not take arguments' do
594
+ @class.class_eval do
595
+ def method_with_ret_val; 'hello'; end;
596
+ def after_method_with_ret_val; hi_mom!; end;
597
+ after(:method_with_ret_val, :after_method_with_ret_val)
598
+ end
599
+
600
+ inst = @class.new
601
+ expect(inst).to receive(:hi_mom!)
602
+ inst.method_with_ret_val
603
+ end
604
+
605
+ it 'works with glob arguments (or whatever you call em)' do
606
+ @class.class_eval do
607
+ def hook_this(*args); end;
608
+ def before_hook_this(*args); hi_mom!(*args) end;
609
+ before(:hook_this, :before_hook_this)
610
+ end
611
+
612
+ inst = @class.new
613
+ expect(inst).to receive(:hi_mom!).with('omg', 'hi2u', 'lolercoaster')
614
+ inst.hook_this('omg', 'hi2u', 'lolercoaster')
615
+ end
616
+
617
+ it 'allows the use of before and after together' do
618
+ @class.class_eval %(def after_hook; last!; end;), __FILE__, __LINE__
619
+ @class.before(:hookable) { first! }
620
+ @class.after(:hookable, :after_hook)
621
+
622
+ inst = @class.new
623
+ expect(inst).to receive(:first!).once.ordered
624
+ expect(inst).to receive(:last!).once.ordered
625
+ inst.hookable
626
+ end
627
+
628
+ it 'is able to use private methods as hooks' do
629
+ @class.class_eval %(private; def nike; doit!; end;), __FILE__, __LINE__
630
+ @class.before(:hookable, :nike)
631
+
632
+ inst = @class.new
633
+ expect(inst).to receive(:doit!)
634
+ inst.hookable
635
+ end
636
+ end
637
+
638
+ end
639
+
640
+ describe 'hook invocation with class inheritance' do
641
+ describe 'for class methods' do
642
+ it 'runs an advice block when the class is inherited' do
643
+ @class.before_class_method(:clakable) { hi_mom! }
644
+ @child = Class.new(@class)
645
+ expect(@child).to receive(:hi_mom!)
646
+ @child.clakable
647
+ end
648
+
649
+ it 'runs an advice block on child class when hook is registered in parent after inheritance' do
650
+ @child = Class.new(@class)
651
+ @class.before_class_method(:clakable) { hi_mom! }
652
+ expect(@child).to receive(:hi_mom!)
653
+ @child.clakable
654
+ end
655
+
656
+ it 'is able to declare advice methods in child classes' do
657
+ @class.class_eval %{def self.before_method; hi_dad!; end;}
658
+ @class.before_class_method(:clakable, :before_method)
659
+
660
+ @child = Class.new(@class) do
661
+ def self.child; hi_mom!; end;
662
+ before_class_method(:clakable, :child)
663
+ end
664
+
665
+ expect(@child).to receive(:hi_dad!).once.ordered
666
+ expect(@child).to receive(:hi_mom!).once.ordered
667
+ @child.clakable
668
+ end
669
+
670
+ it 'does not execute hooks added in the child classes when in the parent class' do
671
+ @child = Class.new(@class) { def self.child; hi_mom!; end; }
672
+ @child.before_class_method(:clakable, :child)
673
+ expect(@class).not_to receive(:hi_mom!)
674
+ @class.clakable
675
+ end
676
+
677
+ it 'does not call the hook stack if the hookable method is overwritten and does not call super' do
678
+ @class.before_class_method(:clakable) { hi_mom! }
679
+ @child = Class.new(@class) do
680
+ def self.clakable; end;
681
+ end
682
+
683
+ expect(@child).not_to receive(:hi_mom!)
684
+ @child.clakable
685
+ end
686
+
687
+ it 'does not call hooks defined in the child class for a hookable method in a parent if the child overwrites the hookable method without calling super' do
688
+ @child = Class.new(@class) do
689
+ before_class_method(:clakable) { hi_mom! }
690
+ def self.clakable; end;
691
+ end
692
+
693
+ expect(@child).not_to receive(:hi_mom!)
694
+ @child.clakable
695
+ end
696
+
697
+ it 'does not call hooks defined in child class even if hook method exists in parent' do
698
+ @class.class_eval %{def self.hello_world; hello_world!; end;}
699
+ @child = Class.new(@class) do
700
+ before_class_method(:clakable, :hello_world)
701
+ end
702
+
703
+ expect(@class).not_to receive(:hello_world!)
704
+ @class.clakable
705
+ end
706
+ end
707
+
708
+ describe 'for instance methods' do
709
+ it 'runs an advice block when the class is inherited' do
710
+ @inherited_class = Class.new(@class)
711
+ @class.before(:hookable) { hi_dad! }
712
+
713
+ inst = @inherited_class.new
714
+ expect(inst).to receive(:hi_dad!)
715
+ inst.hookable
716
+ end
717
+
718
+ it 'runs an advice block on child class when hook is registered in parent after inheritance' do
719
+ @child = Class.new(@class)
720
+ @class.before(:hookable) { hi_mom! }
721
+
722
+ inst = @child.new
723
+ expect(inst).to receive(:hi_mom!)
724
+ inst.hookable
725
+ end
726
+
727
+ it 'is able to declare advice methods in child classes' do
728
+ @class.send(:define_method, :before_method) { hi_dad! }
729
+ @class.before(:hookable, :before_method)
730
+
731
+ @child = Class.new(@class) do
732
+ def child; hi_mom!; end;
733
+ before :hookable, :child
734
+ end
735
+
736
+ inst = @child.new
737
+ expect(inst).to receive(:hi_dad!).once.ordered
738
+ expect(inst).to receive(:hi_mom!).once.ordered
739
+ inst.hookable
740
+ end
741
+
742
+ it 'does not execute hooks added in the child classes when in parent class' do
743
+ @child = Class.new(@class)
744
+ @child.send(:define_method, :child) { hi_mom! }
745
+ @child.before(:hookable, :child)
746
+
747
+ inst = @class.new
748
+ expect(inst).not_to receive(:hi_mom!)
749
+ inst.hookable
750
+ end
751
+
752
+ it 'does not call the hook stack if the hookable method is overwritten and does not call super' do
753
+ @class.before(:hookable) { hi_mom! }
754
+ @child = Class.new(@class) do
755
+ def hookable; end;
756
+ end
757
+
758
+ inst = @child.new
759
+ expect(inst).not_to receive(:hi_mom!)
760
+ inst.hookable
761
+ end
762
+
763
+ it 'does not call hooks defined in the child class for a hookable method in a parent if the child overwrites the hookable method without calling super' do
764
+ @child = Class.new(@class) do
765
+ before(:hookable) { hi_mom! }
766
+ def hookable; end;
767
+ end
768
+
769
+ inst = @child.new
770
+ expect(inst).not_to receive(:hi_mom!)
771
+ inst.hookable
772
+ end
773
+
774
+ it 'does not call hooks defined in child class even if hook method exists in parent' do
775
+ @class.send(:define_method, :hello_world) { hello_world! }
776
+ @child = Class.new(@class) do
777
+ before(:hookable, :hello_world)
778
+ end
779
+
780
+ inst = @class.new
781
+ expect(inst).not_to receive(:hello_world!)
782
+ inst.hookable
783
+ end
784
+
785
+ it 'calls different hooks in different children when they are defined there' do
786
+ @class.send(:define_method, :hello_world) {}
787
+
788
+ @child1 = Class.new(@class) do
789
+ before(:hello_world){ hi_dad! }
790
+ end
791
+
792
+ @child2 = Class.new(@class) do
793
+ before(:hello_world){ hi_mom! }
794
+ end
795
+
796
+ @child3 = Class.new(@child1) do
797
+ before(:hello_world){ hi_grandma! }
798
+ end
799
+
800
+ inst1 = @child1.new
801
+ inst2 = @child2.new
802
+ inst3 = @child3.new
803
+ expect(inst1).to receive(:hi_dad!).once
804
+ expect(inst2).to receive(:hi_mom!).once
805
+ expect(inst3).to receive(:hi_dad!).once
806
+ expect(inst3).to receive(:hi_grandma!).once
807
+ inst1.hello_world
808
+ inst2.hello_world
809
+ inst3.hello_world
810
+ end
811
+
812
+ end
813
+
814
+ end
815
+
816
+ describe 'hook invocation with module inclusions / extensions' do
817
+ describe 'for class methods' do
818
+ it 'does not overwrite methods included by extensions after the hook is declared' do
819
+ @module.class_eval do
820
+ @another_module = Module.new do
821
+ def greet; greetings_from_another_module; super; end;
822
+ end
823
+
824
+ def self.extended(base)
825
+ base.before_class_method(:clakable, :greet)
826
+ base.extend(@another_module)
827
+ end
828
+ end
829
+
830
+ @class.extend(@module)
831
+ expect(@class).to receive(:greetings_from_another_module).once.ordered
832
+ expect(@class).to receive(:greetings_from_module).once.ordered
833
+ @class.clakable
834
+ end
835
+ end
836
+
837
+ describe 'for instance methods' do
838
+ it 'does not overwrite methods included by modules after the hook is declared' do
839
+ @module.class_eval do
840
+ @another_module = Module.new do
841
+ def greet; greetings_from_another_module; super; end;
842
+ end
843
+
844
+ def self.included(base)
845
+ base.before(:hookable, :greet)
846
+ base.send(:include, @another_module)
847
+ end
848
+ end
849
+
850
+ @class.send(:include, @module)
851
+
852
+ inst = @class.new
853
+ expect(inst).to receive(:greetings_from_another_module).once.ordered
854
+ expect(inst).to receive(:greetings_from_module).once.ordered
855
+ inst.hookable
856
+ end
857
+ end
858
+
859
+ end
860
+
861
+ describe 'hook invocation with unrelated classes' do
862
+ describe 'for class methods' do
863
+ it 'does not execute hooks registered in an unrelated class' do
864
+ @class.before_class_method(:clakable) { hi_mom! }
865
+
866
+ expect(@other).not_to receive(:hi_mom!)
867
+ @other.clakable
868
+ end
869
+ end
870
+
871
+ describe 'for instance methods' do
872
+ it 'does not execute hooks registered in an unrelated class' do
873
+ @class.before(:hookable) { hi_mom! }
874
+
875
+ inst = @other.new
876
+ expect(inst).not_to receive(:hi_mom!)
877
+ inst.hookable
878
+ end
879
+ end
880
+
881
+ end
882
+
883
+ describe 'using before hook' do
884
+ describe 'for class methods' do
885
+ it 'runs the advice before the advised method' do
886
+ @class.class_eval %{def self.hook_me; second!; end;}
887
+ @class.register_class_hooks(:hook_me)
888
+ @class.before_class_method(:hook_me, :first!)
889
+
890
+ expect(@class).to receive(:first!).ordered
891
+ expect(@class).to receive(:second!).ordered
892
+ @class.hook_me
893
+ end
894
+
895
+ it 'executes all advices once in order' do
896
+ @class.before_class_method(:clakable, :hook_1)
897
+ @class.before_class_method(:clakable, :hook_2)
898
+ @class.before_class_method(:clakable, :hook_3)
899
+
900
+ expect(@class).to receive(:hook_1).once.ordered
901
+ expect(@class).to receive(:hook_2).once.ordered
902
+ expect(@class).to receive(:hook_3).once.ordered
903
+ @class.clakable
904
+ end
905
+ end
906
+
907
+ describe 'for instance methods' do
908
+ it 'runs the advice before the advised method' do
909
+ @class.class_eval %{
910
+ def hook_me; second!; end;
911
+ }
912
+ @class.register_instance_hooks(:hook_me)
913
+ @class.before(:hook_me, :first!)
914
+
915
+ inst = @class.new
916
+ expect(inst).to receive(:first!).ordered
917
+ expect(inst).to receive(:second!).ordered
918
+ inst.hook_me
919
+ end
920
+
921
+ it 'executes all advices once in order' do
922
+ @class.before(:hookable, :hook_1)
923
+ @class.before(:hookable, :hook_2)
924
+ @class.before(:hookable, :hook_3)
925
+
926
+ inst = @class.new
927
+ expect(inst).to receive(:hook_1).once.ordered
928
+ expect(inst).to receive(:hook_2).once.ordered
929
+ expect(inst).to receive(:hook_3).once.ordered
930
+ inst.hookable
931
+ end
932
+ end
933
+
934
+ end
935
+
936
+ describe 'using after hook' do
937
+ describe "for class methods" do
938
+ it 'runs the advice after the advised method' do
939
+ @class.class_eval %{def self.hook_me; first!; end;}
940
+ @class.register_class_hooks(:hook_me)
941
+ @class.after_class_method(:hook_me, :second!)
942
+
943
+ expect(@class).to receive(:first!).ordered
944
+ expect(@class).to receive(:second!).ordered
945
+ @class.hook_me
946
+ end
947
+
948
+ it 'executes all advices once in order' do
949
+ @class.after_class_method(:clakable, :hook_1)
950
+ @class.after_class_method(:clakable, :hook_2)
951
+ @class.after_class_method(:clakable, :hook_3)
952
+
953
+ expect(@class).to receive(:hook_1).once.ordered
954
+ expect(@class).to receive(:hook_2).once.ordered
955
+ expect(@class).to receive(:hook_3).once.ordered
956
+ @class.clakable
957
+ end
958
+
959
+ it 'the advised method still returns its normal value' do
960
+ @class.class_eval %{def self.hello; "hello world"; end;}
961
+ @class.register_class_hooks(:hello)
962
+ @class.after_class_method(:hello) { 'BAM' }
963
+
964
+ expect(@class.hello).to eq 'hello world'
965
+ end
966
+
967
+ it 'passes the return value to a hook method' do
968
+ @class.class_eval do
969
+ def self.with_return_val; 'hello'; end;
970
+
971
+ def self.after_with_return_val(retval)
972
+ expect(retval).to eq 'hello'
973
+ end
974
+ after_class_method(:with_return_val, :after_with_return_val)
975
+ end
976
+
977
+ @class.with_return_val
978
+ end
979
+
980
+ it 'passes the return value to a hook block' do
981
+ @class.class_eval do
982
+ def self.with_return_val; 'hello'; end;
983
+ after_class_method(:with_return_val) { |ret| expect(ret).to eq 'hello' }
984
+ end
985
+
986
+ @class.with_return_val
987
+ end
988
+
989
+ it 'passes the return value and method arguments to a hook block' do
990
+ @class.class_eval do
991
+ def self.with_args_and_return_val(world); 'hello'; end;
992
+ after_class_method(:with_args_and_return_val) do |hello, world|
993
+ expect(hello).to eq 'hello'
994
+ expect(world).to eq 'world'
995
+ end
996
+ end
997
+
998
+ @class.with_args_and_return_val('world')
999
+ end
1000
+ end
1001
+
1002
+ describe 'for instance methods' do
1003
+ it 'runs the advice after the advised method' do
1004
+ @class.class_eval %{def hook_me; first!; end;}
1005
+ @class.register_instance_hooks(:hook_me)
1006
+ @class.after(:hook_me, :second!)
1007
+
1008
+ inst = @class.new
1009
+ expect(inst).to receive(:first!).ordered
1010
+ expect(inst).to receive(:second!).ordered
1011
+ inst.hook_me
1012
+ end
1013
+
1014
+ it 'executes all advices once in order' do
1015
+ @class.after(:hookable, :hook_1)
1016
+ @class.after(:hookable, :hook_2)
1017
+ @class.after(:hookable, :hook_3)
1018
+
1019
+ inst = @class.new
1020
+ expect(inst).to receive(:hook_1).once.ordered
1021
+ expect(inst).to receive(:hook_2).once.ordered
1022
+ expect(inst).to receive(:hook_3).once.ordered
1023
+ inst.hookable
1024
+ end
1025
+
1026
+ it 'the advised method still returns its normal value' do
1027
+ @class.class_eval %{def hello; "hello world"; end;}
1028
+ @class.register_instance_hooks(:hello)
1029
+ @class.after(:hello) { "BAM" }
1030
+
1031
+ expect(@class.new.hello).to eq 'hello world'
1032
+ end
1033
+
1034
+ it 'returns nil if an after hook throws :halt without a return value' do
1035
+ @class.class_eval %{def with_value; "hello"; end;}
1036
+ @class.register_instance_hooks(:with_value)
1037
+ @class.after(:with_value) { throw :halt }
1038
+
1039
+ expect(@class.new.with_value).to be_nil
1040
+ end
1041
+
1042
+ it 'passes the return value to a hook method ' do
1043
+ @class.class_eval do
1044
+ def with_return_val; 'hello'; end;
1045
+
1046
+ def after_with_return_val(retval)
1047
+ expect(retval).to eq 'hello'
1048
+ end
1049
+ after(:with_return_val, :after_with_return_val)
1050
+ end
1051
+
1052
+ @class.new.with_return_val
1053
+ end
1054
+
1055
+ it 'passes the return value to a hook block' do
1056
+ @class.class_eval do
1057
+ def with_return_val; 'hello'; end;
1058
+ after(:with_return_val) { |ret| expect(ret).to eq 'hello' }
1059
+ end
1060
+
1061
+ @class.new.with_return_val
1062
+ end
1063
+
1064
+ it 'passes the return value and method arguments to a hook block' do
1065
+ @class.class_eval do
1066
+ def with_args_and_return_val(world); 'hello'; end;
1067
+ after(:with_args_and_return_val) do |hello, world|
1068
+ expect(hello).to eq 'hello'
1069
+ expect(world).to eq 'world'
1070
+ end
1071
+ end
1072
+
1073
+ @class.new.with_args_and_return_val('world')
1074
+ end
1075
+ end
1076
+ end
1077
+
1078
+ describe 'aborting' do
1079
+ describe 'for class methods' do
1080
+ it 'catches :halt from a before hook and abort the advised method' do
1081
+ @class.class_eval %{def self.no_love; love_me!; end;}
1082
+ @class.register_class_hooks :no_love
1083
+ @class.before_class_method(:no_love) { maybe! }
1084
+ @class.before_class_method(:no_love) { throw :halt }
1085
+ @class.before_class_method(:no_love) { what_about_me? }
1086
+
1087
+ expect(@class).to receive(:maybe!)
1088
+ expect(@class).not_to receive(:what_about_me?)
1089
+ expect(@class).not_to receive(:love_me!)
1090
+ expect { @class.no_love }.not_to throw_symbol(:halt)
1091
+ end
1092
+
1093
+ it 'does not run after hooks if a before hook throws :halt' do
1094
+ @class.before_class_method(:clakable) { throw :halt }
1095
+ @class.after_class_method(:clakable) { bam! }
1096
+
1097
+ expect(@class).not_to receive(:bam!)
1098
+ expect { @class.clakable }.not_to throw_symbol(:halt)
1099
+ end
1100
+
1101
+ it 'returns nil from the hookable method if a before hook throws :halt' do
1102
+ @class.class_eval %{def self.with_value; "hello"; end;}
1103
+ @class.register_class_hooks(:with_value)
1104
+ @class.before_class_method(:with_value) { throw :halt }
1105
+
1106
+ expect(@class.with_value).to be_nil
1107
+ end
1108
+
1109
+ it 'catches :halt from an after hook and cease the advice' do
1110
+ @class.after_class_method(:clakable) { throw :halt }
1111
+ @class.after_class_method(:clakable) { never_see_me! }
1112
+
1113
+ expect(@class).not_to receive(:never_see_me!)
1114
+ expect { @class.clakable }.not_to throw_symbol(:halt)
1115
+ end
1116
+
1117
+ it 'returns nil if an after hook throws :halt without a return value' do
1118
+ @class.class_eval %{def self.with_value; "hello"; end;}
1119
+ @class.register_class_hooks(:with_value)
1120
+ @class.after_class_method(:with_value) { throw :halt }
1121
+
1122
+ expect(@class.with_value).to be_nil
1123
+ end
1124
+ end
1125
+
1126
+ describe 'for instance methods' do
1127
+ it 'catches :halt from a before hook and abort the advised method' do
1128
+ @class.class_eval %{def no_love; love_me!; end;}
1129
+ @class.register_instance_hooks :no_love
1130
+ @class.before(:no_love) { maybe! }
1131
+ @class.before(:no_love) { throw :halt }
1132
+ @class.before(:no_love) { what_about_me? }
1133
+
1134
+ inst = @class.new
1135
+ expect(inst).to receive(:maybe!)
1136
+ expect(inst).not_to receive(:what_about_me?)
1137
+ expect(inst).not_to receive(:love_me!)
1138
+ expect { inst.no_love }.not_to throw_symbol(:halt)
1139
+ end
1140
+
1141
+ it 'does not run after hooks if a before hook throws :halt' do
1142
+ @class.before(:hookable) { throw :halt }
1143
+ @class.after(:hookable) { bam! }
1144
+
1145
+ inst = @class.new
1146
+ expect(inst).not_to receive(:bam!)
1147
+ expect { inst.hookable }.not_to throw_symbol(:halt)
1148
+ end
1149
+
1150
+ it 'returns nil from the hookable method if a before hook throws :halt' do
1151
+ @class.class_eval %{def with_value; "hello"; end;}
1152
+ @class.register_instance_hooks(:with_value)
1153
+ @class.before(:with_value) { throw :halt }
1154
+
1155
+ expect(@class.new.with_value).to be_nil
1156
+ end
1157
+
1158
+ it 'catches :halt from an after hook and cease the advice' do
1159
+ @class.after(:hookable) { throw :halt }
1160
+ @class.after(:hookable) { never_see_me! }
1161
+
1162
+ inst = @class.new
1163
+ expect(inst).not_to receive(:never_see_me!)
1164
+ expect { inst.hookable }.not_to throw_symbol(:halt)
1165
+ end
1166
+ end
1167
+ end
1168
+
1169
+ describe 'aborting with return values' do
1170
+ describe 'for class methods' do
1171
+ it 'is able to abort from a before hook with a return value' do
1172
+ @class.before_class_method(:clakable) { throw :halt, 'omg' }
1173
+ expect(@class.clakable).to eq 'omg'
1174
+ end
1175
+
1176
+ it 'is able to abort from an after hook with a return value' do
1177
+ @class.after_class_method(:clakable) { throw :halt, 'omg' }
1178
+ expect(@class.clakable).to eq 'omg'
1179
+ end
1180
+ end
1181
+
1182
+ describe 'for instance methods' do
1183
+ it 'is able to abort from a before hook with a return value' do
1184
+ @class.before(:hookable) { throw :halt, 'omg' }
1185
+
1186
+ inst = @class.new
1187
+ expect(inst.hookable).to eq 'omg'
1188
+ end
1189
+
1190
+ it 'is able to abort from an after hook with a return value' do
1191
+ @class.after(:hookable) { throw :halt, 'omg' }
1192
+
1193
+ inst = @class.new
1194
+ expect(inst.hookable).to eq 'omg'
1195
+ end
1196
+ end
1197
+ end
1198
+
1199
+ describe 'helper methods' do
1200
+ it 'generates the correct argument signature' do
1201
+ @class.class_eval do
1202
+ def some_method(a, b, c)
1203
+ [a, b, c]
1204
+ end
1205
+
1206
+ def yet_another(a, *heh)
1207
+ [a, *heh]
1208
+ end
1209
+ end
1210
+
1211
+ expect(@class.args_for(@class.instance_method(:hookable))).to eq '&block'
1212
+ expect(@class.args_for(@class.instance_method(:some_method))).to eq '_1, _2, _3, &block'
1213
+ expect(@class.args_for(@class.instance_method(:yet_another))).to eq '_1, *args, &block'
1214
+ end
1215
+ end
1216
+ end