Get rid of all messing with action registration by just stuffing the attributes onto...
[catagits/CatalystX-DynamicComponent.git] / lib / CatalystX / DynamicComponent.pm
CommitLineData
59fc9d16 1package CatalystX::DynamicComponent;
53a42ae0 2use MooseX::Role::Parameterized;
ac8aab7d 3use MooseX::Types::Moose qw/Str CodeRef HashRef ArrayRef/;
4use Catalyst::Utils;
5use Moose::Util::TypeConstraints;
6use List::MoreUtils qw/uniq/;
046d763d 7use namespace::autoclean;
59fc9d16 8
ac8aab7d 9enum __PACKAGE__ . '::ResolveStrategy' => qw/
10 merge
11 replace
12/;
13
104abdae 14our $VERSION = 0.000001;
15
53a42ae0 16parameter 'name' => (
0b07685c 17 isa => Str,
53a42ae0 18 required => 1,
19);
20
21parameter 'pre_immutable_hook' => (
92c28c42 22 isa => CodeRef|Str,
53a42ae0 23 predicate => 'has_pre_immutable_hook',
24);
25
ac8aab7d 26my %parameters = (
27 methods => {
28 isa =>HashRef,
29 default => sub { {} },
30 resolve_strategy => 'merge',
31 },
32 roles => {
33 isa => ArrayRef,
34 default => sub { [] },
35 resolve_strategy => 'merge',
36 },
37 superclasses => {
38 isa => ArrayRef,
39 default => sub { [] },
40 resolve_strategy => 'replace',
41 },
42);
43
44# Shameless metaprogramming.
45foreach my $name (keys %parameters) {
46 my $resolve_strategy = delete $parameters{$name}->{resolve_strategy};
47
48 parameter $name, %{ $parameters{$name} };
49
50 parameter $name . '_resolve_strategy' => (
51 isa => __PACKAGE__ . '::ResolveStrategy',
52 default => $resolve_strategy,
53 );
54}
55
56# Code refs to implement the strategy types
57my %strategies = ( # Right hand precedence where appropriate
58 replace => sub { $_[1]; },
59 merge => sub {
60 if (ref($_[0]) eq 'ARRAY') {
61 [ uniq( @{ $_[0] }, @{ $_[1] } ) ];
62 }
63 else {
64 Catalyst::Utils::merge_hashes(shift, shift);
65 }
66 },
cd6bd40d 67);
68
ac8aab7d 69# Wrap all the crazy up in a method to generically merge configs.
70my $get_resolved_config = sub {
71 my ($name, $p, $config) = @_;
72 my $get_strategy_method_name = $name . '_resolve_strategy';
73 my $strategy = $strategies{$p->$get_strategy_method_name()};
74 $strategy->($p->$name, $config->{$name})
75 || $parameters{$name}->{default}->();
76};
77
53a42ae0 78role {
79 my $p = shift;
80 my $name = $p->name;
cd6bd40d 81 my $pre_immutable_hook = $p->pre_immutable_hook;
0b07685c 82
53a42ae0 83 method $name => sub {
c52d8688 84 my ($app, $name, $config) = @_;
85
86 $config ||= {};
53a42ae0 87
88 my $appclass = blessed($app) || $app;
89 my $type = $name;
90 $type =~ s/^${appclass}:://; # FIXME - I think there is shit in C::Utils to do this.
91 $type =~ s/::.*$//;
92
93 my $meta = Moose->init_meta( for_class => $name );
279c014c 94
ac8aab7d 95 my @superclasses = @{ $get_resolved_config->('superclasses', $p, $config) };
0b07685c 96 push(@superclasses, 'Catalyst::' . $type) unless @superclasses;
97 $meta->superclasses(@superclasses);
98
ac8aab7d 99 if (my @roles = @{ $get_resolved_config->('roles', $p, $config) }) {
279c014c 100 Moose::Util::apply_all_roles( $name, @roles);
101 }
00b934f1 102
ac8aab7d 103 my $methods = $get_resolved_config->('methods', $p, $config);
104 foreach my $name (keys %$methods) {
105 $meta->add_method($name => $methods->{$name});
549d6abc 106 }
92c28c42 107
108 if ($p->has_pre_immutable_hook) {
109 if (!ref($pre_immutable_hook)) {
110 $app->$pre_immutable_hook($meta, $config);
111 }
112 else {
113 $pre_immutable_hook->($meta, $config);
114 }
115 }
116
53a42ae0 117 $meta->make_immutable;
118
119 my $instance = $app->setup_component($name);
120 $app->components->{ $name } = $instance;
121 };
122};
59fc9d16 123
1241;
125
dc7781e3 126__END__
127
128=head1 NAME
129
130CatalystX::DynamicComponent - Parameterised Moose role providing functionality to build Catalyst components at runtime.
131
132=head1 SYNOPSIS
133
134 package My::DynamicComponentType;
135 use Moose::Role;
136 use namespace::autoclean;
137
138 with 'CatalystX::DynamicComponent' => {
139 name => '_setup_one_of_my_components', # Name of injected method
140 };
141
142 after setup_components => sub { shift->_setup_all_my_components(@_); };
143
144 sub _setup_all_my_components {
145 my ($self, $c) = @_;
146 foreach my $component_name ('MyApp::Component1') {
147 my $component_config = $c->config->{$component_name};
148 # Calling this method creates a component, and registers it in your application
149 $self->_setup_one_of_my_components($component_name, $component_config);
150 }
151 }
152
153=head1 DESCRIPTION
154
155CatalystX::DynamicComponent aims to provide a flexible and reuseable method of building generic
156Catalyst components and registering them with your application.
157
158To give you this flexibility, it is implemented as a parametrised role which curries a
159component builder into your current package at application time.
160
161Authors of specific dynamic component builders are expected to be implemented as application class
162roles which compose this role, but provide their own advice around the C<< setup_compontens >>
163method, and call the curried method from this role once for each component you wish to setup.
164
165=head1 PARAMETERS
166
167=head2 name
168
169B<Required> - The name of the component generator method to curry.
170
171=head2 COMPONENT
172
173Optional, either a L<Class::MOP::Method>, or a plain code ref of a COMPONENT method to apply to
174the dynamically generated package before making it immutable.
175
176=head2 pre_immutable_hook
177
178Optional, method to call after a component has been generated, but before it is made immutable,
179constructed, and added to your component registry.
180
181=head1 CURRIED COMPONENT GENERATOR
182
183=head2 ARGUMENTS
184
185=over
186
6ba37fba 187=item *
188
189$component_name (E.g. C<< MyApp::Controller::Foo >>)
190
191=item *
192
193$config (E.g. C<< $c->config->{$component_name} >>)
dc7781e3 194
6ba37fba 195=back
dc7781e3 196
197=head2 OPERATION
198
199FIXME
200
201=head1 TODO
202
203=over
204
205=item *
206
207Better default handling of config - by default component should get config from where it normally
208does!
209
210=item *
211
212Abstract handling of role application / class name. This should not just be the component config
213by default.
214
215=item *
216
92c28c42 217Test pre_immutable hook in tests
218
219=item *
220
221More tests fixme?
dc7781e3 222
223=back
224
225=head1 BUGS
226
227Probably plenty, test suite certainly isn't comprehensive.. Patches welcome.
228
229=head1 AUTHOR
230
231Tomas Doran (t0m) <bobtfish@bobtfish.net>
232
233=head1 LICENSE
234
235This code is copyright (c) 2009 Tomas Doran. This code is licensed on the same terms as perl
236itself.
237
238=cut
239