File Coverage

blib/lib/Catalyst/Model/HTMLFormhandler.pm
Criterion Covered Total %
statement 113 160 70.6
branch 33 84 39.2
condition 4 14 28.5
subroutine 24 27 88.8
pod 7 10 70.0
total 181 295 61.3


line stmt bran cond sub pod time code
1             package Catalyst::Model::HTMLFormhandler;
2              
3 1     1   545 use Moose;
  1         1  
  1         5  
4 1     1   4618 use Module::Pluggable::Object;
  1         1  
  1         21  
5 1     1   3 use Moose::Util::TypeConstraints ();
  1         6  
  1         12  
6 1     1   3 use Catalyst::Utils;
  1         1  
  1         484  
7              
8             extends 'Catalyst::Model';
9             with 'Catalyst::Component::ApplicationAttribute';
10              
11             our $VERSION = '0.009';
12              
13             has 'roles' => (
14             is=>'ro',
15             isa=>'ArrayRef',
16             predicate=>'has_roles');
17              
18             has 'body_method' => (
19             is=>'ro',
20             isa=> Moose::Util::TypeConstraints::enum([qw/body_data body_parameters/]),
21             required=>1,
22             default=>'body_data');
23              
24             has 'schema_model_name' => (is=>'ro',
25             isa=>'Str',
26             predicate=>'has_schema_model_name');
27              
28             has 'form_namespace' => (
29             is=>'ro',
30             required=>1,
31             lazy=>1,
32             builder=>'_build_form_namespace');
33              
34 1     1   38 sub _default_form_namespace_part { 'Form' }
35              
36             sub _build_form_namespace {
37 1     1   2 my $self = shift;
38 1         33 return $self->_application .'::'. $self->_default_form_namespace_part;
39             }
40              
41             has 'form_packages' => (
42             is=>'ro',
43             required=>1,
44             lazy=>1,
45             builder=>'_build_form_packages');
46              
47             sub _build_form_packages {
48 1     1   1 my $self = shift;
49 1         32 my @forms = Module::Pluggable::Object->new(
50             require => 1,
51             search_path => [ $self->form_namespace ],
52             )->plugins;
53              
54 1         395 return \@forms;
55             }
56              
57             has 'no_auto_process' => (is=>'ro', isa=>'Bool', required=>1, default=>0);
58              
59             sub build_model_adaptor {
60 2     2 0 3 my ($self, $model_package, $form_package, $model_name) = @_;
61 2 50       63 my $roles = join( ',', map { "'$_'"} @{$self->roles||[]}) if $self->has_roles;
  4 50       10  
  2         59  
62              
63 2 50       69 my $schema_args = $self->has_schema_model_name ?
64             '$args{schema} = $c->model("'.$self->schema_model_name.'");' : '';
65              
66 2   50     66 my $package = "package $model_package;\n" . q(
67            
68             use Moose;
69             use Moose::Util;
70             use ). $form_package . q! ;
71             extends 'Catalyst::Model';
72              
73             sub COMPONENT {
74             my ($class, $app, @args) = @_;
75             # Don't call new, we don't want to merge config now since this is a per-request
76             # model, that way we call for new configuration each time we bless (that way we
77             # can use models that are context sensitive.)
78              
79             return bless +{}, $class;
80             }
81              
82             sub ACCEPT_CONTEXT {
83             my ($self, $c, @args) = @_;
84             my $id = '__'. ref $self;
85              
86             # If there are odd args, that means the first one is either the item object
87             # or item_id (assuming someone is using the DBIC model trait.
88             my %args = ();
89             if(scalar(@args) % 2) {
90             # args are odd, so shift off the first one and figure it out.
91             my $item_proto = shift @args;
92             %args = @args;
93             if(ref $item_proto eq 'HASH') {
94             $args{params} = $item_proto;
95             } elsif(ref $item_proto) {
96             $args{item} = $item_proto;
97             } else {
98             $args{item_id} = $item_proto;
99             }
100             } else {
101             %args = @args;
102             }
103            
104             #If an action arg is passed and its a Catalyst::Action, make it a URL
105             if(my $action = delete $args{action_from}) {
106             my @action = ref $action eq 'ARRAY' ? @$action : ($action);
107             $args{action} = ref $action ? $c->uri_for(@action) : $c->uri_for_action(@action);
108             }
109              
110             if(my $form = $c->stash->{$id}) {
111             $form->process( %args ) if keys(%args);
112             return $form;
113             }
114             my $set = 0;
115             unless($args{action}) {
116             foreach my $action ($c->controller->get_action_methods) {
117             my @attrs = map {($_ =~m/^FormModelTarget\((.+)\)$/)[0]} @{$action->attributes||[]};
118             foreach my $attr(@attrs) {
119             my @parts = (@{$c->req->captures}, @{$c->req->args});
120             $set=$c->uri_for($c->controller->action_for($action), (scalar @parts ? \@parts : ())) if ref($self) =~/$attr$/;
121             }
122             }
123             }
124             $args{action} = $set if $set;
125              
126             #If there is a schema model name use it
127             !. $schema_args .q!
128              
129             # If its a POST, set the request params (you can always override
130             # later.
131             if($c->req->method=~m/post/i) {
132             $args{params} = $c->req->! .$self->body_method. q! unless exists $args{params};
133             $args{posted} = 1 unless $args{posts};
134             }
135              
136             my $no_auto_process = exists $args{no_auto_process} ?
137             delete($args{no_auto_process}) : ! .$self->no_auto_process. q!;
138              
139             $c->stash->{$id} ||= do {
140             %args = %{$self->merge_config_hashes($c->config_for($self->catalyst_component_name), \%args)};
141             my $form = $self->_build_per_request_form(%args, ctx=>$c);
142             $form->process() if
143             $c->req->method=~m/post/i && \!$no_auto_process;
144             $form;
145             };
146              
147             return $c->stash->{$id};
148             }
149              
150             sub _build_per_request_form {
151             my ($self, %args) = @_;
152             my $composed = Moose::Util::with_traits( '! .$form_package. q!' , (! .($roles||'').q!));
153             my $form = $composed->new(%args);
154             }
155              
156             __PACKAGE__->meta->make_immutable;
157              
158             package ! .$model_package. q!::IsValid;
159            
160             use Moose;
161             extends 'Catalyst::Model';
162              
163             sub ACCEPT_CONTEXT {
164             my ($self, $c, @args) = @_;
165             my $form = $c->model('! .$model_name. q!', @args);
166             return $form->is_valid ? $form : undef;
167             }
168              
169             __PACKAGE__->meta->make_immutable;
170              
171             !;
172              
173 1 50 0 1 1 5 eval $package or die "Trouble creating model: \n\n$@\n\n$package";
  1 0 0 1 1 1  
  1 0 66 1 1 4  
  1 0 33 1 1 3798  
  1 0   1 1 2  
  1 0   1 1 6  
  1 0   1   164  
  1 0   1   1  
  1 0   0   480  
  1 0   2   5  
  1 0   0   1  
  1 0   8   3  
  1 0   1   9  
  1 0   1   1  
  1 0   2   7  
  1 0   0   4969  
  1 0       2  
  1 0       4  
  1 0       165  
  1 100       2  
  1 0       625  
  1 100       5  
  1 50       1  
  1 100       4  
  2 50       183  
  0 50       0  
  0 100       0  
  0 100       0  
  0 100       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 100       0  
  0 100       0  
  0 50       0  
  0 50       0  
  0 100       0  
  0 50       0  
  0 100       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  2         503  
  2         9  
  2         6  
  0         0  
  0         0  
  0         0  
  8         169830  
  8         16  
  8         12  
  8         18  
  4         5  
  4         6  
  4         14  
  3         6  
  1         3  
  0         0  
  4         7  
  8         24  
  1         27  
  1         8  
  8         670  
  6         447  
  6         29694  
  2         175  
  2         7  
  1         4  
  7         3416  
  12         195  
  7         168  
  7         9  
  1         1  
  1         4  
  1         60  
  1         73  
  2         460  
  2         28  
  1         62  
  1         2254  
  2         70  
  2         5  
  2         134  
  2         15  
  2         398  
  2         560602  
  2         13235  
  2         9  
  1         200  
  1         3  
  1         175  
  1         3  
  2         7  
  2         7  
  2         9957  
  0         0  
  0         0  
  0         0  
174             }
175              
176             sub construct_model_package {
177 2     2 0 4 my ($self, $form_package) = @_;
178 2         73 return $self->_application .'::Model'. ($form_package=~m/${\$self->_application}(::.+$)/)[0];
  2         56  
179             }
180              
181             sub construct_model_name {
182 2     2 0 2 my ($self, $form_package) = @_;
183 2         2 return ($form_package=~m/${\$self->_application}::(.+$)/)[0];
  2         48  
184             }
185              
186             sub expand_modules {
187 1     1 1 1795 my ($self, $config) = @_;
188 1         2 my @model_packages;
189 1         1 foreach my $form_package (@{$self->form_packages}) {
  1         37  
190 2         8 my $model_package = $self->construct_model_package($form_package);
191 2         30 my $model_name = $self->construct_model_name($form_package);
192 2         24 $self->build_model_adaptor($model_package, $form_package, $model_name);
193 2         531 push @model_packages, $model_package;
194             }
195              
196 1         5 return @model_packages;
197             }
198              
199             __PACKAGE__->meta->make_immutable;
200              
201             =head1 NAME
202              
203             Catalyst::Model::HTMLFormhandler - Proxy a directory of HTML::Formhandler forms
204              
205             =head1 SYNOPSIS
206              
207             package MyApp::Model::Form;
208              
209             use Moose;
210             extends 'Catalyst::Model::HTMLFormhandler';
211              
212             __PACKAGE__->config( form_namespace=>'MyApp::Form' );
213              
214             And then using it in a controller:
215              
216             my $form = $c->model("Form::Email"); # Maps to MyApp::Email via MyApp:Model::Email
217              
218             # If the request is a POST, we process parameters automatically
219             if($form->is_valid) {
220             ...
221             } else {
222             ...
223             }
224              
225             =head1 DESCRIPTION
226              
227             Assuming a project namespace 'MyApp::Form' with L<HTML::Formhandler> forms. like
228             the following example:
229              
230             package MyApp::Form::Email;
231              
232             use HTML::FormHandler::Moose;
233              
234             extends 'HTML::FormHandler';
235              
236             has aaa => (is=>'ro', required=>1);
237             has bbb => (is=>'ro', required=>1);
238              
239             has_field 'email' => (
240             type=>'Email',
241             size => 96,
242             required => 1);
243              
244             You create a single L<Catalyst> model like this:
245              
246             package MyApp::Model::Form;
247              
248             use Moose;
249             extends 'Catalyst::Model::HTMLFormhandler';
250              
251             __PACKAGE__->config( form_namespace=>'MyApp::Form' );
252              
253             (Setting 'form_namespace' is optional, it defaults to the application
254             namespace plus "::Form" (in this example case that would be "MyApp::Form").
255              
256             When you start your application it will register one model for each form
257             in the declared namespace. So in the above example you should see a model
258             'MyApp::Model::Form::Email'. This is a 'PerRequest' model since it does
259             ACCEPT_CONTEXT, it will generate a new instance of the form object once
260             per request scope.
261              
262             It will also create one model with the ::IsValid suffix, which is a shortcut
263             to return a form only if its valid and undef otherwise.
264              
265             You can set model configuration in the normal way, in your application general
266             configuration:
267              
268             package MyApp;
269             use Catalyst;
270              
271             MyApp->config(
272             'Model::Form::Email' => { aaa => 1000 }
273             );
274            
275             MyApp->setup;
276              
277             And you can pass additional args to the 'new' call of the form when you request
278             the form model:
279              
280             my $email = $c->model('Form::Email', bbb=>2000);
281              
282             Additional args should be in the form of a hash, as in the above example OR you can
283             pass a single argument which is either an object, hashref or id followed by a hash
284             of remaining arguements. These first argument gets set to the item or item_id
285             since its common to need:
286              
287             my $email = $c->model('Form::Email', $dbic_email_row, %args);
288              
289             Or if its a HashRef, these are set to the params for processing.
290              
291             The generated proxy will also add the ctx argument based on the current value of
292             $c, although using this may not be a good way to build well, decoupled applications.
293             It also will add the schema argument if you set a schema_model_name.
294              
295             We offer two additional bit of useful suger:
296              
297             If you pass argument 'action_from' with a value of an action object or an action
298             private name that will set the form action value. If 'action_from' is an arrayref
299             we dereference it when building the url.
300              
301             By default if the request is a POST, we will process the request arguments and
302             return a form object that you can test for validity. If you don't want this
303             behavior you can disable it by passing 'no_auto_process'. For example:
304              
305             my $form = $c->model("Form::XXX", no_auto_process=>1)
306              
307             =head1 ATTRIBUTES
308              
309             This class defines the following attributes you may set via
310             standard L<Catalyst> configuration.
311              
312             =head2 form_namespace
313              
314             This is the target namespace that L<Module::Pluggable> uses to look for forms.
315             It defaults to 'MyApp::Form' (where 'MyApp' is you application namespace).
316              
317             =head2 schema_model_name
318              
319             The name of your DBIC Schema model (if you have one). If you set this, we will
320             automatically instantiate your form classes with as schema => $model argument.
321             Useful if you are using L<HTML::FormHandler::Model::DBIC>.
322              
323             =head2 roles
324              
325             A list of L<Moose::Role>s that get applied automatically to each form model.
326              
327             =head2 post_method
328              
329             This is the name of the method called on L<Catalyst::Request> used to access any
330             POSTed data. Required field, the options are 'body_data' and 'body_parameters.
331             The default is 'body_data'.
332              
333             =head2 no_auto_process
334              
335             By default when createing the perrequest form if the request is a POST we
336             just go ahead and process those args. Setting this to true will disable
337             this behavior globally if you prefer more control.
338              
339             =head1 SPECIAL ARGUMENTS
340              
341             You may pass the following special arguments to $c->model("Form::XXX") to
342             influence how the form object is setup.
343              
344             =head2 no_auto_process
345              
346             Turns off the call to ->process when the request is a POST.
347              
348             =head2 action_from
349              
350             Shortcut to create the action value of the form. If an object, we set 'action'
351             from $c->uri_for($object). If its an arrayref from $c->uri_for( @$action_from).
352              
353             =head1 ACTION ATTRIBUTES.
354              
355             =head2 FormModelTarget( $model)
356              
357             When used on an action, sets that action as the target of the form action. This
358             is a bit experimental. We get any needed captures and arguments from the current
359             request, this this only works if the target action has the same number of needed
360             args and captures.
361              
362             =head1 AUTHOR
363            
364             John Napiorkowski L<email:jjnapiork@cpan.org>
365            
366             =head1 SEE ALSO
367            
368             L<Catalyst>, L<Catalyst::Model>, L<HTML::Formhandler>, L<Module::Pluggable>
369              
370             =head1 COPYRIGHT & LICENSE
371            
372             Copyright 2015, John Napiorkowski L<email:jjnapiork@cpan.org>
373            
374             This library is free software; you can redistribute it and/or modify it under
375             the same terms as Perl itself.
376              
377             =cut