File Coverage

blib/lib/Catalyst/Model/Data/MuForm.pm
Criterion Covered Total %
statement 63 68 92.6
branch 1 2 50.0
condition n/a
subroutine 19 20 95.0
pod n/a
total 83 90 92.2


line stmt bran cond sub pod time code
1             package Catalyst::Model::Data::MuForm;
2              
3 1     1   684 use Moo;
  1         2  
  1         6  
4 1     1   2450 use Module::Pluggable::Object;
  1         2  
  1         25  
5 1     1   440 use Template::Tiny;
  1         1069  
  1         451  
6              
7             our $VERSION = '0.001';
8              
9             extends 'Catalyst::Model';
10              
11             has _application => (is => 'ro', required=>1);
12              
13             has 'form_namespace' => (
14             is=>'ro',
15             required=>1,
16             lazy=>1,
17             builder=>'_build_form_namespace');
18            
19 1     1   12 sub _default_form_namespace_part { return 'Form' }
20            
21             sub _build_form_namespace {
22 1     1   17 return $_[0]->_application .'::'. $_[0]->_default_form_namespace_part;
23             }
24            
25             has 'form_packages' => (
26             is=>'ro',
27             required=>1,
28             lazy=>1,
29             builder=>'_build_form_packages');
30            
31             sub _build_form_packages {
32 1     1   15 my $self = shift;
33 1         5 my @forms = Module::Pluggable::Object->new(
34             require => 1,
35             search_path => [ $self->form_namespace ],
36             )->plugins;
37 1         710 return \@forms;
38             }
39              
40             has 'template_string' => (
41             is=>'ro',
42             required=>1,
43             lazy=>1,
44             builder=>'_build_template_string');
45              
46 1     1   30 sub _build_template_string { local $/; return <DATA> }
  1         41  
47              
48             has 'template_processor' => (
49             is=>'ro',
50             required=>1,
51             lazy=>1,
52             builder=>'_build_template_processor');
53              
54 1     1   20 sub _build_template_processor { return Template::Tiny->new(TRIM => 1) }
55              
56             around 'BUILDARGS' => sub {
57             my ($orig, $self, $app, @args) = @_;
58             my $args = $self->$orig($app, @args);
59             $args->{_application} = $app;
60             return $args;
61             };
62              
63             sub expand_modules {
64 1     1   903 my ($self, $config) = @_;
65 1         3 my @model_packages;
66 1         3 foreach my $form_package (@{$self->form_packages}) {
  1         5  
67 2         16 my $model_package = $self->construct_model_package($form_package);
68 2         52 my $model_name = $self->construct_model_name($form_package);
69 2         82 $self->build_model_adaptor($model_package, $form_package, $model_name);
70 2         8 push @model_packages, $model_package;
71             }
72 1         6 return @model_packages;
73             }
74              
75             sub construct_model_package {
76 2     2   6 my ($self, $form_package) = @_;
77 2         8 return $self->_application .'::Model'. ($form_package=~m/${\$self->_application}(::.+$)/)[0];
  2         15  
78             }
79            
80             sub construct_model_name {
81 2     2   5 my ($self, $form_package) = @_;
82 2         4 return ($form_package=~m/${\$self->_application}::(.+$)/)[0];
  2         6  
83             }
84            
85             sub build_model_adaptor {
86 2     2   7 my ($self, $model_package, $form_package, $model_name) = @_;
87 2         11 my $input = $self->template_string;
88 2         14 my $output = '';
89 2         9 $self->template_processor->process(
90             \$input,
91             +{
92             model_package=>$model_package,
93             form_package=>$form_package,
94             },
95             \$output );
96              
97 1     1   9 eval $output;
  1     1   2  
  1     1   8  
  1     1   2324  
  1     5   3  
  1     0   6  
  1     1   7  
  1     1   2  
  1         5  
  1         2339  
  1         3  
  1         4  
  2         1434  
  5         235398  
  5         24  
  5         13  
  5         51  
  5         6282  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         241  
  1         15  
  1         14015  
  1         167804  
  1         4213  
  1         18  
  1         17568  
  1         31190  
98 2 50       12 die $@ if $@;
99             }
100              
101             1;
102              
103             =head1 NAME
104            
105             Catalyst::Model::Data::MuForm - Proxy a directory of Data::MuFormr forms
106            
107             =head1 SYNOPSIS
108            
109             package MyApp::Model::Form;
110            
111             use Moo; # Or Moose, etc.
112             extends 'Catalyst::Model::Data::MuForm';
113            
114             __PACKAGE__->config( form_namespace=>'MyApp::Form' ); # This is the default BTW
115            
116             And then using it in a controller:
117            
118             my $form = $c->model("Form::Email"); # Maps to MyApp::Email via MyApp:Model::Email
119            
120             # If the request is a POST, we process parameters automatically
121             if($form->validated) {
122             ...
123             } else {
124             ...
125             }
126            
127             =head1 DESCRIPTION
128            
129             Assuming a project namespace 'MyApp::Form' with L<HTML::Formhandler> forms. like
130             the following example:
131            
132             package MyApp::Form::Email;
133            
134             use HTML::FormHandler::Moose;
135            
136             extends 'HTML::FormHandler';
137              
138             has 'invalid_domains' => (is=>'ro', required=>1);
139            
140             has_field 'email' => (
141             type=>'Email',
142             size => 96,
143             required => 1);
144            
145             You create a single L<Catalyst> model like this:
146            
147             package MyApp::Model::Form;
148            
149             use Moo; # Or Moose, etc.
150             extends 'Catalyst::Model::Data::MuForm';
151            
152             __PACKAGE__->config( form_namespace=>'MyApp::Form' );
153            
154             (Setting 'form_namespace' is optional, it defaults to the application
155             namespace plus "::Form" (in this example case that would be "MyApp::Form").
156            
157             When you start your application it will register one model for each form
158             in the declared namespace. So in the above example you should see a model
159             'MyApp::Model::Form::Email'.
160            
161             You can set model configuration in the normal way, in your application general
162             configuration:
163            
164             package MyApp;
165             use Catalyst;
166            
167             MyApp->config(
168             'Model::Form::Email' => {
169             invalid_domains => [qw(foo.com wack.org)],
170             },
171             );
172            
173             MyApp->setup;
174            
175             And you can pass additional args to the 'process' call of the form when you request
176             the form model:
177            
178             my $email_form = $c->model('Form::Email',
179             model => $user_model,
180             params => $c->req->body_parameters);
181            
182             Basically you can pass anything you'd pass to 'process' in L<Data::MuForm>.
183            
184             The generated proxy will also add the ctx argument based on the current value of
185             $c, although using this may not be a good way to build well, decoupled applications.
186            
187             By default if the request is a POST, we will process the request arguments and
188             return a form object that you can test for validity. So you don't need to set
189             the 'params' if the parameters are just the existing L<Catalyst> body_parameters.
190             If you don't want this behavior you can disable it by passing 'no_auto_process'.
191             For example:
192            
193             my $form = $c->model("Form::XXX", no_auto_process=>1);
194            
195             =head1 ATTRIBUTES
196            
197             This class defines the following attributes you may set via
198             standard L<Catalyst> configuration.
199            
200             =head2 form_namespace
201            
202             This is the target namespace that L<Module::Pluggable> uses to look for forms.
203             It defaults to 'MyApp::Form' (where 'MyApp' is you application namespace).
204            
205             =head2 body_method
206            
207             This is the name of the method called on L<Catalyst::Request> used to access any
208             POSTed data. Required field, the options are 'body_data' and 'body_parameters.
209             The default is 'body_data'.
210            
211             =head2 no_auto_process
212            
213             By default when createing the perrequest form if the request is a POST we
214             just go ahead and process those args. Setting this to true will disable
215             this behavior globally if you prefer more control.
216            
217             =head1 SPECIAL ARGUMENTS
218            
219             You may pass the following special arguments to $c->model("Form::XXX") to
220             influence how the form object is setup.
221            
222             =head2 no_auto_process
223            
224             Turns off the call to ->process when the request is a POST.
225            
226             =head1 AUTHOR
227            
228             John Napiorkowski L<email:jjnapiork@cpan.org>
229            
230             =head1 SEE ALSO
231            
232             L<Catalyst>, L<Catalyst::Model>, L<Data::MuForm>
233            
234             =head1 COPYRIGHT & LICENSE
235            
236             Copyright 2017, John Napiorkowski L<email:jjnapiork@cpan.org>
237            
238             This library is free software; you can redistribute it and/or modify it under
239             the same terms as Perl itself.
240            
241             =cut
242              
243             __DATA__
244             package [% model_package %];
245              
246             use Moo;
247             use Module::Runtime;
248             extends 'Catalyst::Model';
249              
250             has _args => (
251             is=>'ro',
252             required=>1);
253              
254             has body_method => (
255             is=>'ro',
256             required=>1,
257             default=>'body_data');
258              
259             has auto_process => (is=>'ro', required=>1, default=>1);
260              
261             has form => (
262             is=>'ro',
263             required=>1);
264              
265             sub COMPONENT {
266             my ($class, $app, $args) = @_;
267             my $merged_args = $class->merge_config_hashes($class->config, $args);
268             my $form = Module::Runtime::use_module("[% form_package %]")->new($merged_args);
269             return $class->new(_args=>$merged_args, form=>$form);
270             }
271              
272             # If its a POST we grab params automagically
273             my $prepare_post_params = sub {
274             my ($self, $c, %process_args) = @_;
275             if(
276             ($c->req->method=~m/post/i)
277             and (not exists($process_args{params}))
278             and (not $process_args{no_auto_process})
279             and ($self->auto_process)
280             ) {
281             my $body_method = $self->body_method;
282             $process_args{params} = $c->req->$body_method;
283             $process_args{submitted} = 1 unless exists($process_args{submitted});
284             }
285             return %process_args;
286             };
287              
288             # If there are odd args, that means the first one is either the
289             # model object or model_id
290             my $normalize_process_args = sub {
291             my ($self, $c, %process_args) = (shift, shift, ());
292             if(scalar(@_) % 2) {
293             my $item_proto = shift;
294             %process_args = @_;
295             if(ref $item_proto) { # assume its blessed
296             $process_args{model} = $item_proto;
297             } else {
298             $process_args{model_id} = $item_proto;
299             }
300             } else {
301             %process_args = @_;
302             }
303             return $self->$prepare_post_params($c, %process_args);
304             };
305              
306              
307             sub ACCEPT_CONTEXT {
308             my ($self, $c, @process_args) = @_;
309             my %process_args = $self->$normalize_process_args($c, @process_args);
310             local $_; #WHY?
311             $self->form->process(%process_args, ctx=>$c);
312             return $self->form;
313             }
314              
315             1;