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] ? $_[1] : $_[0]; },
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 my $methods = $get_resolved_config->('methods', $p, $config);
104 foreach my $name (keys %$methods) {
105 $meta->add_method($name => $methods->{$name});
108 if ($p->has_pre_immutable_hook) {
109 if (!ref($pre_immutable_hook)) {
110 $app->$pre_immutable_hook($meta, $config);
113 $pre_immutable_hook->($meta, $config);
117 $meta->make_immutable;
119 my $instance = $app->setup_component($name);
120 $app->components->{ $name } = $instance;
130 CatalystX::DynamicComponent - Parameterised Moose role providing functionality to build Catalyst components at runtime.
134 package My::DynamicComponentType;
136 use namespace::autoclean;
138 with 'CatalystX::DynamicComponent' => {
139 name => '_setup_one_of_my_components', # Name of injected method
142 after setup_components => sub { shift->_setup_all_my_components(@_); };
144 sub _setup_all_my_components {
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);
155 CatalystX::DynamicComponent aims to provide a flexible and reuseable method of building generic
156 Catalyst components and registering them with your application.
158 To give you this flexibility, it is implemented as a parametrised role which curries a
159 component builder into your current package at application time.
161 Authors of specific dynamic component builders are expected to be implemented as application class
162 roles which compose this role, but provide their own advice around the C<< setup_compontens >>
163 method, and call the curried method from this role once for each component you wish to setup.
169 B<Required> - The name of the component generator method to curry.
173 Optional, either a L<Class::MOP::Method>, or a plain code ref of a COMPONENT method to apply to
174 the dynamically generated package before making it immutable.
176 =head2 pre_immutable_hook
178 Optional, method to call after a component has been generated, but before it is made immutable,
179 constructed, and added to your component registry.
181 =head1 CURRIED COMPONENT GENERATOR
189 $component_name (E.g. C<< MyApp::Controller::Foo >>)
193 $config (E.g. C<< $c->config->{$component_name} >>)
207 Better default handling of config - by default component should get config from where it normally
212 Abstract handling of role application / class name. This should not just be the component config
217 Test pre_immutable hook in tests
227 Probably plenty, test suite certainly isn't comprehensive.. Patches welcome.
231 Tomas Doran (t0m) <bobtfish@bobtfish.net>
235 This code is copyright (c) 2009 Tomas Doran. This code is licensed on the same terms as perl