1 package CatalystX::DynamicComponent;
2 use MooseX::Role::Parameterized;
3 use MooseX::Types::Moose qw/Str CodeRef HashRef ArrayRef/;
5 use Moose::Util::TypeConstraints;
6 use List::MoreUtils qw/uniq/;
7 use namespace::autoclean;
9 enum __PACKAGE__ . '::ResolveStrategy' => qw/
14 our $VERSION = 0.000001;
21 parameter 'pre_immutable_hook' => (
23 predicate => 'has_pre_immutable_hook',
29 default => sub { {} },
30 resolve_strategy => 'merge',
34 default => sub { [] },
35 resolve_strategy => 'merge',
39 default => sub { [] },
40 resolve_strategy => 'replace',
44 # Shameless metaprogramming.
45 foreach my $name (keys %parameters) {
46 my $resolve_strategy = delete $parameters{$name}->{resolve_strategy};
48 parameter $name, %{ $parameters{$name} };
50 parameter $name . '_resolve_strategy' => (
51 isa => __PACKAGE__ . '::ResolveStrategy',
52 default => $resolve_strategy,
56 # Code refs to implement the strategy types
57 my %strategies = ( # Right hand precedence where appropriate
58 replace => sub { $_[1]; },
60 if (ref($_[0]) eq 'ARRAY') {
61 [ uniq( @{ $_[0] }, @{ $_[1] } ) ];
64 Catalyst::Utils::merge_hashes(shift, shift);
69 # Wrap all the crazy up in a method to generically merge configs.
70 my $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}->();
81 my $pre_immutable_hook = $p->pre_immutable_hook;
84 my ($app, $name, $config) = @_;
88 my $appclass = blessed($app) || $app;
90 $type =~ s/^${appclass}:://; # FIXME - I think there is shit in C::Utils to do this.
93 my $meta = Moose->init_meta( for_class => $name );
95 my @superclasses = @{ $get_resolved_config->('superclasses', $p, $config) };
96 push(@superclasses, 'Catalyst::' . $type) unless @superclasses;
97 $meta->superclasses(@superclasses);
99 if (my @roles = @{ $get_resolved_config->('roles', $p, $config) }) {
100 Moose::Util::apply_all_roles( $name, @roles);
103 $app->$pre_immutable_hook($meta) if $p->has_pre_immutable_hook;
105 my $methods = $get_resolved_config->('methods', $p, $config);
106 foreach my $name (keys %$methods) {
107 $meta->add_method($name => $methods->{$name});
109 $meta->make_immutable;
111 my $instance = $app->setup_component($name);
112 $app->components->{ $name } = $instance;
122 CatalystX::DynamicComponent - Parameterised Moose role providing functionality to build Catalyst components at runtime.
126 package My::DynamicComponentType;
128 use namespace::autoclean;
130 with 'CatalystX::DynamicComponent' => {
131 name => '_setup_one_of_my_components', # Name of injected method
134 after setup_components => sub { shift->_setup_all_my_components(@_); };
136 sub _setup_all_my_components {
138 foreach my $component_name ('MyApp::Component1') {
139 my $component_config = $c->config->{$component_name};
140 # Calling this method creates a component, and registers it in your application
141 $self->_setup_one_of_my_components($component_name, $component_config);
147 CatalystX::DynamicComponent aims to provide a flexible and reuseable method of building generic
148 Catalyst components and registering them with your application.
150 To give you this flexibility, it is implemented as a parametrised role which curries a
151 component builder into your current package at application time.
153 Authors of specific dynamic component builders are expected to be implemented as application class
154 roles which compose this role, but provide their own advice around the C<< setup_compontens >>
155 method, and call the curried method from this role once for each component you wish to setup.
161 B<Required> - The name of the component generator method to curry.
165 Optional, either a L<Class::MOP::Method>, or a plain code ref of a COMPONENT method to apply to
166 the dynamically generated package before making it immutable.
168 =head2 pre_immutable_hook
170 Optional, method to call after a component has been generated, but before it is made immutable,
171 constructed, and added to your component registry.
173 =head1 CURRIED COMPONENT GENERATOR
179 =item $component_name (E.g. C<< MyApp::Controller::Foo >>)
181 =item $config (E.g. C<< $c->config->{$component_name} >>)
193 Better default handling of config - by default component should get config from where it normally
198 Abstract handling of role application / class name. This should not just be the component config
203 Have some actual tests which test just this crap, and not all the other classes together.
209 Probably plenty, test suite certainly isn't comprehensive.. Patches welcome.
213 Tomas Doran (t0m) <bobtfish@bobtfish.net>
217 This code is copyright (c) 2009 Tomas Doran. This code is licensed on the same terms as perl