Commit | Line | Data |
452bac1b |
1 | #!/usr/bin/perl |
2 | |
3 | use strict; |
4 | use warnings; |
5 | |
a28e50e4 |
6 | use Test::More; |
452bac1b |
7 | use Test::Exception; |
8 | |
7ff56534 |
9 | |
f4f3e701 |
10 | # ------------------------------------------------------------------- |
11 | # HASH handles |
12 | # ------------------------------------------------------------------- |
452bac1b |
13 | # the canonical form of of the 'handles' |
d03bd989 |
14 | # option is the hash ref mapping a |
452bac1b |
15 | # method name to the delegated method name |
16 | |
17 | { |
18 | package Foo; |
452bac1b |
19 | use Moose; |
20 | |
d03bd989 |
21 | has 'bar' => (is => 'rw', default => 10); |
452bac1b |
22 | |
227373b3 |
23 | sub baz { 42 } |
24 | |
8aeb5bfd |
25 | sub quux { confess } |
26 | |
452bac1b |
27 | package Bar; |
d03bd989 |
28 | use Moose; |
29 | |
452bac1b |
30 | has 'foo' => ( |
31 | is => 'rw', |
32 | default => sub { Foo->new }, |
2de18801 |
33 | handles => { |
34 | 'foo_bar' => 'bar', |
6f94c802 |
35 | foo_baz => 'baz', |
8aeb5bfd |
36 | foo_quux => 'quux', |
3c573ca4 |
37 | 'foo_bar_to_20' => [ bar => 20 ], |
6f94c802 |
38 | }, |
452bac1b |
39 | ); |
40 | } |
41 | |
8aeb5bfd |
42 | sub test_hash_handles { |
43 | my $bar = shift; |
44 | isa_ok($bar, 'Bar'); |
45 | |
46 | ok($bar->foo, '... we have something in bar->foo'); |
47 | isa_ok($bar->foo, 'Foo'); |
48 | |
49 | my $meth = Bar->meta->get_method('foo_bar'); |
50 | isa_ok($meth, 'Moose::Meta::Method::Delegation'); |
51 | is($meth->associated_attribute->name, 'foo', |
52 | 'associated_attribute->name for this method is foo'); |
53 | |
54 | is($bar->foo->bar, 10, '... bar->foo->bar returned the right default'); |
55 | |
56 | can_ok($bar, 'foo_bar'); |
57 | is($bar->foo_bar, 10, '... bar->foo_bar delegated correctly'); |
452bac1b |
58 | |
8aeb5bfd |
59 | # change the value ... |
452bac1b |
60 | |
8aeb5bfd |
61 | $bar->foo->bar(30); |
a05f85c1 |
62 | |
8aeb5bfd |
63 | # and make sure the delegation picks it up |
452bac1b |
64 | |
8aeb5bfd |
65 | is($bar->foo->bar, 30, '... bar->foo->bar returned the right (changed) value'); |
66 | is($bar->foo_bar, 30, '... bar->foo_bar delegated correctly'); |
452bac1b |
67 | |
8aeb5bfd |
68 | # change the value through the delegation ... |
f4f3e701 |
69 | |
8aeb5bfd |
70 | $bar->foo_bar(50); |
f4f3e701 |
71 | |
8aeb5bfd |
72 | # and make sure everyone sees it |
f4f3e701 |
73 | |
8aeb5bfd |
74 | is($bar->foo->bar, 50, '... bar->foo->bar returned the right (changed) value'); |
75 | is($bar->foo_bar, 50, '... bar->foo_bar delegated correctly'); |
f4f3e701 |
76 | |
8aeb5bfd |
77 | # change the object we are delegating too |
f4f3e701 |
78 | |
8aeb5bfd |
79 | my $foo = Foo->new(bar => 25); |
80 | isa_ok($foo, 'Foo'); |
f4f3e701 |
81 | |
8aeb5bfd |
82 | is($foo->bar, 25, '... got the right foo->bar'); |
f4f3e701 |
83 | |
8aeb5bfd |
84 | lives_ok { |
85 | $bar->foo($foo); |
86 | } '... assigned the new Foo to Bar->foo'; |
f4f3e701 |
87 | |
8aeb5bfd |
88 | is($bar->foo, $foo, '... assigned bar->foo with the new Foo'); |
f4f3e701 |
89 | |
8aeb5bfd |
90 | is($bar->foo->bar, 25, '... bar->foo->bar returned the right result'); |
91 | is($bar->foo_bar, 25, '... and bar->foo_bar delegated correctly again'); |
452bac1b |
92 | |
8aeb5bfd |
93 | # curried handles |
94 | $bar->foo_bar_to_20; |
95 | is($bar->foo_bar, 20, '... correctly curried a single argument'); |
96 | } |
452bac1b |
97 | |
8aeb5bfd |
98 | # Works with a mutable class |
452bac1b |
99 | |
8aeb5bfd |
100 | { |
101 | test_hash_handles(Bar->new); |
102 | } |
452bac1b |
103 | |
8aeb5bfd |
104 | # Works with an immutable class and provides a meaningful backtrace |
452bac1b |
105 | |
8aeb5bfd |
106 | { |
107 | Bar->meta->make_immutable; |
108 | my $filename = __FILE__; |
109 | my $bar = Bar->new; |
110 | test_hash_handles($bar); |
111 | throws_ok { $bar->foo_quux } qr/delegation of foo_quux to foo->quux/, |
112 | 'error location describes the delegation'; |
113 | throws_ok { $bar->foo_quux } qr/defined at $filename line \d+/, |
114 | '... and points at where the handler is defined'; |
115 | } |
2de18801 |
116 | |
f4f3e701 |
117 | # ------------------------------------------------------------------- |
d03bd989 |
118 | # ARRAY handles |
f4f3e701 |
119 | # ------------------------------------------------------------------- |
452bac1b |
120 | # we also support an array based format |
d03bd989 |
121 | # which assumes that the name is the same |
452bac1b |
122 | # on either end |
123 | |
124 | { |
125 | package Engine; |
452bac1b |
126 | use Moose; |
127 | |
128 | sub go { 'Engine::go' } |
d03bd989 |
129 | sub stop { 'Engine::stop' } |
452bac1b |
130 | |
131 | package Car; |
d03bd989 |
132 | use Moose; |
133 | |
452bac1b |
134 | has 'engine' => ( |
135 | is => 'rw', |
136 | default => sub { Engine->new }, |
137 | handles => [ 'go', 'stop' ] |
138 | ); |
139 | } |
140 | |
141 | my $car = Car->new; |
142 | isa_ok($car, 'Car'); |
143 | |
144 | isa_ok($car->engine, 'Engine'); |
145 | can_ok($car->engine, 'go'); |
146 | can_ok($car->engine, 'stop'); |
147 | |
148 | is($car->engine->go, 'Engine::go', '... got the right value from ->engine->go'); |
149 | is($car->engine->stop, 'Engine::stop', '... got the right value from ->engine->stop'); |
150 | |
151 | can_ok($car, 'go'); |
152 | can_ok($car, 'stop'); |
153 | |
154 | is($car->go, 'Engine::go', '... got the right value from ->go'); |
155 | is($car->stop, 'Engine::stop', '... got the right value from ->stop'); |
156 | |
f4f3e701 |
157 | # ------------------------------------------------------------------- |
d03bd989 |
158 | # REGEXP handles |
f4f3e701 |
159 | # ------------------------------------------------------------------- |
452bac1b |
160 | # and we support regexp delegation |
161 | |
162 | { |
163 | package Baz; |
452bac1b |
164 | use Moose; |
165 | |
166 | sub foo { 'Baz::foo' } |
d03bd989 |
167 | sub bar { 'Baz::bar' } |
168 | sub boo { 'Baz::boo' } |
452bac1b |
169 | |
170 | package Baz::Proxy1; |
d03bd989 |
171 | use Moose; |
172 | |
452bac1b |
173 | has 'baz' => ( |
174 | is => 'ro', |
175 | isa => 'Baz', |
176 | default => sub { Baz->new }, |
177 | handles => qr/.*/ |
178 | ); |
d03bd989 |
179 | |
452bac1b |
180 | package Baz::Proxy2; |
d03bd989 |
181 | use Moose; |
182 | |
452bac1b |
183 | has 'baz' => ( |
184 | is => 'ro', |
185 | isa => 'Baz', |
186 | default => sub { Baz->new }, |
187 | handles => qr/.oo/ |
d03bd989 |
188 | ); |
189 | |
452bac1b |
190 | package Baz::Proxy3; |
d03bd989 |
191 | use Moose; |
192 | |
452bac1b |
193 | has 'baz' => ( |
194 | is => 'ro', |
195 | isa => 'Baz', |
196 | default => sub { Baz->new }, |
197 | handles => qr/b.*/ |
d03bd989 |
198 | ); |
452bac1b |
199 | } |
200 | |
201 | { |
202 | my $baz_proxy = Baz::Proxy1->new; |
203 | isa_ok($baz_proxy, 'Baz::Proxy1'); |
204 | |
205 | can_ok($baz_proxy, 'baz'); |
206 | isa_ok($baz_proxy->baz, 'Baz'); |
207 | |
208 | can_ok($baz_proxy, 'foo'); |
209 | can_ok($baz_proxy, 'bar'); |
210 | can_ok($baz_proxy, 'boo'); |
d03bd989 |
211 | |
452bac1b |
212 | is($baz_proxy->foo, 'Baz::foo', '... got the right proxied return value'); |
213 | is($baz_proxy->bar, 'Baz::bar', '... got the right proxied return value'); |
d03bd989 |
214 | is($baz_proxy->boo, 'Baz::boo', '... got the right proxied return value'); |
452bac1b |
215 | } |
216 | { |
217 | my $baz_proxy = Baz::Proxy2->new; |
218 | isa_ok($baz_proxy, 'Baz::Proxy2'); |
219 | |
220 | can_ok($baz_proxy, 'baz'); |
221 | isa_ok($baz_proxy->baz, 'Baz'); |
222 | |
223 | can_ok($baz_proxy, 'foo'); |
224 | can_ok($baz_proxy, 'boo'); |
d03bd989 |
225 | |
452bac1b |
226 | is($baz_proxy->foo, 'Baz::foo', '... got the right proxied return value'); |
d03bd989 |
227 | is($baz_proxy->boo, 'Baz::boo', '... got the right proxied return value'); |
452bac1b |
228 | } |
229 | { |
230 | my $baz_proxy = Baz::Proxy3->new; |
231 | isa_ok($baz_proxy, 'Baz::Proxy3'); |
232 | |
233 | can_ok($baz_proxy, 'baz'); |
234 | isa_ok($baz_proxy->baz, 'Baz'); |
235 | |
236 | can_ok($baz_proxy, 'bar'); |
237 | can_ok($baz_proxy, 'boo'); |
d03bd989 |
238 | |
452bac1b |
239 | is($baz_proxy->bar, 'Baz::bar', '... got the right proxied return value'); |
d03bd989 |
240 | is($baz_proxy->boo, 'Baz::boo', '... got the right proxied return value'); |
452bac1b |
241 | } |
242 | |
f4f3e701 |
243 | # ------------------------------------------------------------------- |
244 | # ROLE handles |
245 | # ------------------------------------------------------------------- |
246 | |
c84f324f |
247 | { |
248 | package Foo::Bar; |
249 | use Moose::Role; |
d03bd989 |
250 | |
c84f324f |
251 | requires 'foo'; |
252 | requires 'bar'; |
d03bd989 |
253 | |
c84f324f |
254 | package Foo::Baz; |
255 | use Moose; |
d03bd989 |
256 | |
c84f324f |
257 | sub foo { 'Foo::Baz::FOO' } |
258 | sub bar { 'Foo::Baz::BAR' } |
d03bd989 |
259 | sub baz { 'Foo::Baz::BAZ' } |
260 | |
c84f324f |
261 | package Foo::Thing; |
262 | use Moose; |
d03bd989 |
263 | |
c84f324f |
264 | has 'thing' => ( |
d03bd989 |
265 | is => 'rw', |
c84f324f |
266 | isa => 'Foo::Baz', |
267 | handles => 'Foo::Bar', |
268 | ); |
269 | |
270 | } |
271 | |
272 | { |
273 | my $foo = Foo::Thing->new(thing => Foo::Baz->new); |
274 | isa_ok($foo, 'Foo::Thing'); |
275 | isa_ok($foo->thing, 'Foo::Baz'); |
d03bd989 |
276 | |
c84f324f |
277 | ok($foo->meta->has_method('foo'), '... we have the method we expect'); |
278 | ok($foo->meta->has_method('bar'), '... we have the method we expect'); |
d03bd989 |
279 | ok(!$foo->meta->has_method('baz'), '... we dont have the method we expect'); |
280 | |
281 | is($foo->foo, 'Foo::Baz::FOO', '... got the right value'); |
c84f324f |
282 | is($foo->bar, 'Foo::Baz::BAR', '... got the right value'); |
d03bd989 |
283 | is($foo->thing->baz, 'Foo::Baz::BAZ', '... got the right value'); |
c84f324f |
284 | } |
285 | |
e902b1a5 |
286 | # ------------------------------------------------------------------- |
287 | # AUTOLOAD & handles |
288 | # ------------------------------------------------------------------- |
289 | |
290 | { |
291 | package Foo::Autoloaded; |
292 | use Moose; |
293 | |
294 | sub AUTOLOAD { |
295 | my $self = shift; |
296 | |
297 | my $name = our $AUTOLOAD; |
f4fce6c8 |
298 | $name =~ s/.*://; # strip fully-qualified portion |
e902b1a5 |
299 | |
300 | if (@_) { |
301 | return $self->{$name} = shift; |
302 | } else { |
303 | return $self->{$name}; |
304 | } |
305 | } |
306 | |
307 | package Bar::Autoloaded; |
d03bd989 |
308 | use Moose; |
309 | |
e902b1a5 |
310 | has 'foo' => ( |
311 | is => 'rw', |
312 | default => sub { Foo::Autoloaded->new }, |
313 | handles => { 'foo_bar' => 'bar' } |
314 | ); |
d03bd989 |
315 | |
e902b1a5 |
316 | package Baz::Autoloaded; |
d03bd989 |
317 | use Moose; |
318 | |
e902b1a5 |
319 | has 'foo' => ( |
320 | is => 'rw', |
321 | default => sub { Foo::Autoloaded->new }, |
322 | handles => ['bar'] |
d03bd989 |
323 | ); |
324 | |
e902b1a5 |
325 | package Goorch::Autoloaded; |
d03bd989 |
326 | use Moose; |
327 | |
e902b1a5 |
328 | ::dies_ok { |
329 | has 'foo' => ( |
330 | is => 'rw', |
331 | default => sub { Foo::Autoloaded->new }, |
332 | handles => qr/bar/ |
d03bd989 |
333 | ); |
e902b1a5 |
334 | } '... you cannot delegate to AUTOLOADED class with regexp'; |
335 | } |
336 | |
337 | # check HASH based delegation w/ AUTOLOAD |
338 | |
339 | { |
340 | my $bar = Bar::Autoloaded->new; |
341 | isa_ok($bar, 'Bar::Autoloaded'); |
342 | |
343 | ok($bar->foo, '... we have something in bar->foo'); |
344 | isa_ok($bar->foo, 'Foo::Autoloaded'); |
345 | |
346 | # change the value ... |
347 | |
348 | $bar->foo->bar(30); |
349 | |
350 | # and make sure the delegation picks it up |
351 | |
352 | is($bar->foo->bar, 30, '... bar->foo->bar returned the right (changed) value'); |
353 | is($bar->foo_bar, 30, '... bar->foo_bar delegated correctly'); |
354 | |
355 | # change the value through the delegation ... |
356 | |
357 | $bar->foo_bar(50); |
358 | |
d03bd989 |
359 | # and make sure everyone sees it |
e902b1a5 |
360 | |
361 | is($bar->foo->bar, 50, '... bar->foo->bar returned the right (changed) value'); |
362 | is($bar->foo_bar, 50, '... bar->foo_bar delegated correctly'); |
363 | |
364 | # change the object we are delegating too |
365 | |
366 | my $foo = Foo::Autoloaded->new; |
367 | isa_ok($foo, 'Foo::Autoloaded'); |
368 | |
369 | $foo->bar(25); |
d03bd989 |
370 | |
e902b1a5 |
371 | is($foo->bar, 25, '... got the right foo->bar'); |
372 | |
373 | lives_ok { |
374 | $bar->foo($foo); |
375 | } '... assigned the new Foo to Bar->foo'; |
376 | |
377 | is($bar->foo, $foo, '... assigned bar->foo with the new Foo'); |
378 | |
379 | is($bar->foo->bar, 25, '... bar->foo->bar returned the right result'); |
380 | is($bar->foo_bar, 25, '... and bar->foo_bar delegated correctly again'); |
381 | } |
382 | |
383 | # check ARRAY based delegation w/ AUTOLOAD |
384 | |
385 | { |
386 | my $baz = Baz::Autoloaded->new; |
387 | isa_ok($baz, 'Baz::Autoloaded'); |
388 | |
389 | ok($baz->foo, '... we have something in baz->foo'); |
390 | isa_ok($baz->foo, 'Foo::Autoloaded'); |
391 | |
392 | # change the value ... |
393 | |
394 | $baz->foo->bar(30); |
395 | |
396 | # and make sure the delegation picks it up |
397 | |
398 | is($baz->foo->bar, 30, '... baz->foo->bar returned the right (changed) value'); |
399 | is($baz->bar, 30, '... baz->foo_bar delegated correctly'); |
400 | |
401 | # change the value through the delegation ... |
402 | |
403 | $baz->bar(50); |
404 | |
d03bd989 |
405 | # and make sure everyone sees it |
e902b1a5 |
406 | |
407 | is($baz->foo->bar, 50, '... baz->foo->bar returned the right (changed) value'); |
408 | is($baz->bar, 50, '... baz->foo_bar delegated correctly'); |
409 | |
410 | # change the object we are delegating too |
411 | |
412 | my $foo = Foo::Autoloaded->new; |
413 | isa_ok($foo, 'Foo::Autoloaded'); |
414 | |
415 | $foo->bar(25); |
d03bd989 |
416 | |
e902b1a5 |
417 | is($foo->bar, 25, '... got the right foo->bar'); |
418 | |
419 | lives_ok { |
420 | $baz->foo($foo); |
421 | } '... assigned the new Foo to Baz->foo'; |
422 | |
423 | is($baz->foo, $foo, '... assigned baz->foo with the new Foo'); |
424 | |
425 | is($baz->foo->bar, 25, '... baz->foo->bar returned the right result'); |
426 | is($baz->bar, 25, '... and baz->foo_bar delegated correctly again'); |
427 | } |
e1d6f0a3 |
428 | |
429 | # Check that removing attributes removes their handles methods also. |
430 | { |
431 | { |
432 | package Quux; |
433 | use Moose; |
d03bd989 |
434 | has foo => ( |
435 | isa => 'Foo', |
e1d6f0a3 |
436 | default => sub { Foo->new }, |
437 | handles => { 'foo_bar' => 'bar' } |
438 | ); |
439 | } |
440 | my $i = Quux->new; |
441 | ok($i->meta->has_method('foo_bar'), 'handles method foo_bar is present'); |
442 | $i->meta->remove_attribute('foo'); |
443 | ok(!$i->meta->has_method('foo_bar'), 'handles method foo_bar is removed'); |
444 | } |
445 | |
ad5e9d0d |
446 | # Make sure that a useful error message is thrown when the delegation target is |
447 | # not an object |
448 | { |
6148c167 |
449 | my $i = Bar->new(foo => undef); |
450 | throws_ok { $i->foo_bar } qr/is not defined/, |
451 | 'useful error from unblessed reference'; |
452 | |
453 | my $j = Bar->new(foo => []); |
454 | throws_ok { $j->foo_bar } qr/is not an object \(got 'ARRAY/, |
ad5e9d0d |
455 | 'useful error from unblessed reference'; |
227373b3 |
456 | |
457 | my $k = Bar->new(foo => "Foo"); |
458 | lives_ok { $k->foo_baz } "but not for class name"; |
ad5e9d0d |
459 | } |
a28e50e4 |
460 | |
461 | done_testing; |