sbf-dm-core 1.3.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +44 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +468 -0
  7. data/.travis.yml +57 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +70 -0
  10. data/LICENSE +20 -0
  11. data/README.md +269 -0
  12. data/Rakefile +4 -0
  13. data/dm-core.gemspec +21 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
  15. data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
  16. data/lib/dm-core/adapters.rb +249 -0
  17. data/lib/dm-core/associations/many_to_many.rb +477 -0
  18. data/lib/dm-core/associations/many_to_one.rb +282 -0
  19. data/lib/dm-core/associations/one_to_many.rb +332 -0
  20. data/lib/dm-core/associations/one_to_one.rb +84 -0
  21. data/lib/dm-core/associations/relationship.rb +650 -0
  22. data/lib/dm-core/backwards.rb +11 -0
  23. data/lib/dm-core/collection.rb +1486 -0
  24. data/lib/dm-core/core_ext/kernel.rb +21 -0
  25. data/lib/dm-core/core_ext/pathname.rb +4 -0
  26. data/lib/dm-core/core_ext/symbol.rb +10 -0
  27. data/lib/dm-core/identity_map.rb +6 -0
  28. data/lib/dm-core/model/hook.rb +99 -0
  29. data/lib/dm-core/model/is.rb +30 -0
  30. data/lib/dm-core/model/property.rb +244 -0
  31. data/lib/dm-core/model/relationship.rb +366 -0
  32. data/lib/dm-core/model/scope.rb +87 -0
  33. data/lib/dm-core/model.rb +876 -0
  34. data/lib/dm-core/property/binary.rb +19 -0
  35. data/lib/dm-core/property/boolean.rb +35 -0
  36. data/lib/dm-core/property/class.rb +23 -0
  37. data/lib/dm-core/property/date.rb +45 -0
  38. data/lib/dm-core/property/date_time.rb +44 -0
  39. data/lib/dm-core/property/decimal.rb +47 -0
  40. data/lib/dm-core/property/discriminator.rb +40 -0
  41. data/lib/dm-core/property/float.rb +27 -0
  42. data/lib/dm-core/property/integer.rb +32 -0
  43. data/lib/dm-core/property/invalid_value_error.rb +17 -0
  44. data/lib/dm-core/property/lookup.rb +26 -0
  45. data/lib/dm-core/property/numeric.rb +35 -0
  46. data/lib/dm-core/property/object.rb +33 -0
  47. data/lib/dm-core/property/serial.rb +13 -0
  48. data/lib/dm-core/property/string.rb +47 -0
  49. data/lib/dm-core/property/text.rb +12 -0
  50. data/lib/dm-core/property/time.rb +46 -0
  51. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  52. data/lib/dm-core/property/typecast/time.rb +33 -0
  53. data/lib/dm-core/property.rb +856 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query/conditions/comparison.rb +886 -0
  56. data/lib/dm-core/query/conditions/operation.rb +710 -0
  57. data/lib/dm-core/query/direction.rb +33 -0
  58. data/lib/dm-core/query/operator.rb +34 -0
  59. data/lib/dm-core/query/path.rb +113 -0
  60. data/lib/dm-core/query/sort.rb +38 -0
  61. data/lib/dm-core/query.rb +1352 -0
  62. data/lib/dm-core/relationship_set.rb +69 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
  65. data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
  66. data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
  67. data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
  68. data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
  69. data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
  70. data/lib/dm-core/resource/persistence_state.rb +70 -0
  71. data/lib/dm-core/resource.rb +1220 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +164 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +388 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +13 -0
  102. data/lib/dm-core/support/logger.rb +201 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +109 -0
  105. data/lib/dm-core/support/ordered_set.rb +381 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +251 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/lib/dm-core.rb +274 -0
  110. data/script/performance.rb +275 -0
  111. data/script/profile.rb +218 -0
  112. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  113. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +69 -0
  114. data/spec/public/associations/many_to_many_spec.rb +197 -0
  115. data/spec/public/associations/many_to_one_spec.rb +83 -0
  116. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  117. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  118. data/spec/public/associations/one_to_many_spec.rb +81 -0
  119. data/spec/public/associations/one_to_one_spec.rb +176 -0
  120. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  121. data/spec/public/collection_spec.rb +69 -0
  122. data/spec/public/finalize_spec.rb +77 -0
  123. data/spec/public/model/hook_spec.rb +245 -0
  124. data/spec/public/model/property_spec.rb +91 -0
  125. data/spec/public/model/relationship_spec.rb +1040 -0
  126. data/spec/public/model_spec.rb +456 -0
  127. data/spec/public/property/binary_spec.rb +43 -0
  128. data/spec/public/property/boolean_spec.rb +21 -0
  129. data/spec/public/property/class_spec.rb +27 -0
  130. data/spec/public/property/date_spec.rb +21 -0
  131. data/spec/public/property/date_time_spec.rb +21 -0
  132. data/spec/public/property/decimal_spec.rb +23 -0
  133. data/spec/public/property/discriminator_spec.rb +134 -0
  134. data/spec/public/property/float_spec.rb +22 -0
  135. data/spec/public/property/integer_spec.rb +22 -0
  136. data/spec/public/property/object_spec.rb +117 -0
  137. data/spec/public/property/serial_spec.rb +22 -0
  138. data/spec/public/property/string_spec.rb +21 -0
  139. data/spec/public/property/text_spec.rb +62 -0
  140. data/spec/public/property/time_spec.rb +21 -0
  141. data/spec/public/property_spec.rb +333 -0
  142. data/spec/public/resource/state_spec.rb +72 -0
  143. data/spec/public/resource_spec.rb +289 -0
  144. data/spec/public/sel_spec.rb +53 -0
  145. data/spec/public/setup_spec.rb +145 -0
  146. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  147. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  148. data/spec/public/shared/collection_shared_spec.rb +1637 -0
  149. data/spec/public/shared/finder_shared_spec.rb +1647 -0
  150. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  151. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  152. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  153. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  154. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  155. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  156. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  157. data/spec/semipublic/associations_spec.rb +177 -0
  158. data/spec/semipublic/collection_spec.rb +110 -0
  159. data/spec/semipublic/model_spec.rb +96 -0
  160. data/spec/semipublic/property/binary_spec.rb +13 -0
  161. data/spec/semipublic/property/boolean_spec.rb +47 -0
  162. data/spec/semipublic/property/class_spec.rb +33 -0
  163. data/spec/semipublic/property/date_spec.rb +43 -0
  164. data/spec/semipublic/property/date_time_spec.rb +46 -0
  165. data/spec/semipublic/property/decimal_spec.rb +83 -0
  166. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  167. data/spec/semipublic/property/float_spec.rb +82 -0
  168. data/spec/semipublic/property/integer_spec.rb +82 -0
  169. data/spec/semipublic/property/lookup_spec.rb +29 -0
  170. data/spec/semipublic/property/serial_spec.rb +13 -0
  171. data/spec/semipublic/property/string_spec.rb +13 -0
  172. data/spec/semipublic/property/text_spec.rb +31 -0
  173. data/spec/semipublic/property/time_spec.rb +50 -0
  174. data/spec/semipublic/property_spec.rb +114 -0
  175. data/spec/semipublic/query/conditions/comparison_spec.rb +1502 -0
  176. data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
  177. data/spec/semipublic/query/path_spec.rb +471 -0
  178. data/spec/semipublic/query_spec.rb +3665 -0
  179. data/spec/semipublic/resource/state/clean_spec.rb +89 -0
  180. data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
  181. data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
  182. data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
  183. data/spec/semipublic/resource/state/transient_spec.rb +163 -0
  184. data/spec/semipublic/resource/state_spec.rb +230 -0
  185. data/spec/semipublic/resource_spec.rb +23 -0
  186. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  187. data/spec/semipublic/shared/resource_shared_spec.rb +198 -0
  188. data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
  189. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  190. data/spec/spec_helper.rb +34 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +27 -0
  248. data/spec/unit/hook_spec.rb +1216 -0
  249. data/spec/unit/inflections_spec.rb +14 -0
  250. data/spec/unit/lazy_array_spec.rb +1949 -0
  251. data/spec/unit/mash_spec.rb +289 -0
  252. data/spec/unit/module_spec.rb +70 -0
  253. data/spec/unit/object_spec.rb +38 -0
  254. data/spec/unit/try_dup_spec.rb +46 -0
  255. data/tasks/ci.rake +1 -0
  256. data/tasks/spec.rake +18 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +323 -0
@@ -0,0 +1,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