File Coverage

blib/lib/Catalyst/Plugin/CurrentComponents.pm
Criterion Covered Total %
statement 30 37 81.0
branch 12 32 37.5
condition n/a
subroutine 7 9 77.7
pod n/a
total 49 78 62.8


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::CurrentComponents;
2              
3 1     1   5007 use Moo::Role;
  1         25658  
  1         5  
4 1     1   4904 use Scalar::Util ();
  1         2  
  1         1016  
5              
6             requires 'model', 'view', 'stash';
7              
8             our $VERSION = '0.008';
9              
10             has 'model_instance_from_return' => (is=>'lazy');
11              
12             sub _build_model_instance_from_return {
13 1 50   1   19 if(my $config = shift->config->{'Plugin::CurrentComponents'}) {
14 1 50       96 return exists $config->{model_instance_from_return} ? $config->{model_instance_from_return} : 0;
15             } else {
16 0         0 return 0;
17             }
18             }
19              
20             has 'model_instance_from_state' => (is=>'lazy');
21              
22             sub _build_model_instance_from_state {
23 0 0   0   0 if(my $config = shift->config->{'Plugin::CurrentComponents'}) {
24 0 0       0 return exists $config->{model_instance_from_state} ? $config->{model_instance_from_state} : 0;
25             } else {
26 0         0 return 0;
27             }
28             }
29              
30             has 'view_instance_from_return' => (is=>'lazy');
31              
32             sub _build_view_instance_from_return {
33 0 0   0   0 if(my $config = shift->config->{'Plugin::CurrentComponents'}) {
34 0 0       0 return exists $config->{view_instance_from_return} ? $config->{view_instance_from_return} : 0;
35             } else {
36 0         0 return 0;
37             }
38             }
39              
40             sub current_model {
41 3     3   477 my ($self, $model) = @_;
42 3 50       12 return unless ref $self;
43 3 50       12 if(defined($model)) {
44 3         9 $self->stash->{current_model} = $model;
45             }
46 3         203 return $self->stash->{current_model};
47             }
48              
49             sub current_model_instance {
50 3     3   136 my ($self, $model, @args) = @_;
51 3 50       12 return unless ref $self;
52 3 50       9 if(defined($model)) {
53 3 50       9 $model = $self->model($model, @args) unless ref $model;
54 3         9 $self->stash->{current_model_instance} = $model;
55             }
56 3         276 return $self->stash->{current_model_instance};
57             }
58              
59             sub current_view {
60 1     1   114 my ($self, $view) = @_;
61 1 50       6 return unless ref $self;
62 1 50       5 if(defined($view)) {
63 1         5 $self->stash->{current_view} = $view;
64             }
65 1         71 return $self->stash->{current_view};
66             }
67              
68             sub current_view_instance {
69 2     2   107 my ($self, $view, @args) = @_;
70 2 50       8 return unless ref $self;
71 2 50       7 if(defined($view)) {
72 2 50       7 $view = $self->view($view, @args) unless ref $view;
73 2         7 $self->stash->{current_view_instance} = $view;
74             }
75 2         142 return $self->stash->{current_view_instance};
76             }
77              
78             around 'execute', sub {
79             my ($orig, $self, $class, $code, @rest ) = @_;
80             my $state = $self->$orig($class, $code, @rest);
81              
82             if(
83             defined $state &&
84             Scalar::Util::blessed($state) &&
85             ($self->model_instance_from_return || $self->view_instance_from_return)
86             ) {
87             my $state_class = ref($state);
88             my $app_class = ref($self);
89             $state_class =~s/^$app_class\:\:(Model|View)\:\://;
90            
91             if($self->model_instance_from_return && $self->model($state_class)) {
92             $self->current_model_instance($state);
93             } elsif($self->view_instance_from_return && $self->view($state_class)) {
94             $self->current_view_instance($state);
95             } elsif($self->model_instance_from_state) {
96             # Its an object but its not a view, but allow it anyway. Maybe terrible
97             # idea but for backcompat at least.
98             $self->current_model_instance($state);
99             }
100             }
101              
102             return $state;
103             };
104              
105             around 'model', sub {
106             my ($orig, $self, $name, @args) = @_;
107             if(!defined($name) && ref($self)) {
108             if(
109             !defined($self->stash->{current_model_instance}) &&
110             $self->controller->can('current_model_instance')
111             ) {
112             $self->current_model_instance(
113             $self->controller->current_model_instance($self));
114             } elsif(
115             !defined($self->stash->{current_model}) &&
116             $self->controller->can('current_model')
117             ) {
118             $self->current_model($self->controller->current_model($self));
119             }
120             }
121             return $self->$orig($name, @args);
122             };
123              
124             around 'view', sub {
125             my ($orig, $self, $name, @args) = @_;
126             if(!defined($name) && ref($self)) {
127             if(
128             !defined($self->stash->{current_view_instance}) &&
129             $self->controller->can('current_view_instance')
130             ) {
131             $self->current_view_instance(
132             $self->controller->current_view_instance($self));
133             } elsif(
134             !defined($self->stash->{current_view}) &&
135             $self->controller->can('current_view')
136             ) {
137             $self->current_view($self->controller->current_view($self));
138             }
139             }
140             return $self->$orig($name, @args);
141             };
142              
143             1;
144              
145             =head1 NAME
146              
147             Catalyst::Plugin::CurrentComponents - Declare current components more easily.
148              
149             =head1 SYNOPSIS
150              
151             Use the plugin in your application class:
152              
153             package MyApp;
154             use Catalyst 'CurrentComponents';
155              
156             # Optional configuration
157             MyApp->config(
158             'Plugin::CurrentComponents' => {
159             model_instance_from_return => 1,
160             view_instance_from_return => 1,
161             },
162             );
163              
164             MyApp->setup;
165              
166             Then you can use it in your controllers:
167              
168             package MyApp::Controller::Example;
169              
170             use base 'Catalyst::Controller';
171              
172             sub current_model_instance {
173             my ($self, $c) = @_;
174             return $c->model("Form::Login", user_database => $c->model('Users'));
175             }
176              
177             sub myaction :Local {
178             my ($self, $c) = @_;
179             my $c->model; # Isa 'MyApp::Model::Form::Login', or whatever that returns;
180             }
181              
182             sub set_model :Local {
183             my ($self, $c) = @_;
184             $c->current_model_instance($c->model('Foo')); # $c->model ISA 'MyApp::Model::Foo
185             }
186              
187             sub set_view :Local {
188             my ($self, $c) = @_;
189             $c->current_view_instance($c->view('Bar')); # $c->view ISA 'MyApp::View::Bar
190             }
191              
192             =head1 DESCRIPTION
193              
194             This plugin gives you an alternative to setting the current_view|model(_instance)
195             via a controller method or via context helper methods. You may find this a
196             more readable approach than setting it via the stash.
197              
198             You may also enable a global option to set the current_model_instance or the
199             current_view_instance via the return value of an action. See L</CONFIGURATION>
200              
201             Please Seee documention about Views and Models in L<Catalyst>.
202              
203             =head1 METHODS
204              
205             This plugin adds the following methods to your context.
206              
207             =head2 current_model
208              
209             Sets $c->stash->{current_model} if an argument is passed. Always returns the
210             current value of this stash key. Expects the string name of a model.
211              
212             =head2 current_model_instance
213              
214             Sets $c->stash->{current_model_instance} if an argument is passed. Always returns the
215             current value of this stash key. Expects either the instance of an already created
216             model or can accept arguments that can be validly submitted to $c->model.
217              
218             =head2 current_view
219              
220             Sets $c->stash->{current_view} if an argument is passed. Always returns the
221             current value of this stash key. Expects the string new of a view.
222              
223             =head2 current_view_instance
224              
225             Sets $c->stash->{current_view_instance} if an argument is passed. Always returns the
226             current value of this stash key. Expects either the instance of an already created
227             view or can accept arguments that can be validly submitted to $c->view.
228              
229             =head1 CONTROLLER METHODS
230              
231             This plugin will inspect the current controller for the following methods
232              
233             =head2 current_model
234              
235             =head2 current_model_instance
236              
237             Same as the context methods, but lets you set this at a controller level. Useful
238             for base classes or roles. Example:
239              
240              
241             =head1 CONFIGURATION
242              
243             This plugin supports configuration under the "Plugin::CurrentComponents" key.
244             For example:
245              
246             MyApp->config(
247             'Plugin::CurrentComponents' => {
248             model_instance_from_return => 1,
249             view_instance_from_return => 1,
250             },
251             );
252              
253             =head2 model_instance_from_return
254              
255             Allows one to set the current_model_instance from the return value of a matched
256             action. Please note this is an experimental option which is off by default.
257             The return value must be a defined, blessed objected that ISA L<Catalyst::Model>
258             for this to work. Example:
259              
260             sub set_model_by_return :Chained(/) CaptureArgs(0) {
261             my ($self, $c) = @_;
262             return $c->model('CurrentModel'); # $c->model ISA 'MyApp::Model::CurrentModel'
263             }
264              
265             =head2 view_instance_from_return
266              
267             Allows one to set the current_view_instance from the return value of a matched
268             action. Please note this is an experimental option which is off by default.
269             The return value must be a defined, blessed objected that ISA L<Catalyst::View>
270             for this to work. Example:
271              
272             sub set_view_by_return :Chained(/) CaptureArgs(0) {
273             my ($self, $c) = @_;
274             return $c->view('CurrentView'); # $c->view ISA 'MyApp::View::CurrentView'
275             }
276              
277             =head2 model_instance_from_state
278              
279             Often you want to set your current model instance to 'any type of object'. The
280             configuration L</model_instance_from_return> expects the object to be something
281             in the 'MyApp::Model' namespace. If this is not the case you can use this option.
282              
283             sub set_model_from_resultset :Chained CaptureArgs(1) {
284             my ($self, $c, $id) = @_;
285             return $c->model("Schema::User")->find($id);
286             }
287              
288             In this case the object returned is probably a 'MyApp::Schema::Result::User' so
289             the option L</model_instance_from_return> would not have worked.
290              
291             =head1 AUTHOR
292              
293             John Napiorkowski L<email:jjnapiork@cpan.org>
294            
295             =head1 SEE ALSO
296            
297             L<Catalyst>, L<Catalyst::Response>
298              
299             =head1 COPYRIGHT & LICENSE
300            
301             Copyright 2017, John Napiorkowski L<email:jjnapiork@cpan.org>
302            
303             This library is free software; you can redistribute it and/or modify it under
304             the same terms as Perl itself.
305            
306             =cut