dm-core 0.10.2 → 1.0.0.rc1

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