File Coverage

blib/lib/Catalyst/View/JSON/PerRequest.pm
Criterion Covered Total %
statement 22 22 100.0
branch 2 4 50.0
condition n/a
subroutine 8 8 100.0
pod n/a
total 32 34 94.1


line stmt bran cond sub pod time code
1             package Catalyst::View::JSON::PerRequest;
2              
3 1     1   38291 use Moo;
  1         4  
  1         7  
4 1     1   3359 use CatalystX::InjectComponent;
  1         4410  
  1         31  
5 1     1   636 use Catalyst::View::JSON::_PerRequest;
  1         4  
  1         666  
6              
7             our $VERSION = 0.005;
8             our $DEFAULT_JSON_CLASS = 'JSON::MaybeXS';
9             our $DEFAULT_VIEW_MODEL = 'JSON::ViewData';
10             our %JSON_INIT_ARGS = (
11             utf8 => 1,
12             convert_blessed => 1);
13              
14             extends 'Catalyst::View';
15             with 'Catalyst::Component::InstancePerContext';
16              
17             has json => (
18             is=>'ro',
19             required=>1,
20             init_arg=>undef,
21             lazy=>1,
22             default=>sub {
23             my $self = shift;
24 1     1   121 eval "use ${\$self->json_class}; 1" ||
  1         2  
  1         132  
25             die "Can't use ${\$self->json_class}, $@";
26              
27             return $self->json_class->new(
28             $self->json_init_args);
29             });
30              
31             sub HANDLE_ENCODE_ERROR {
32 1     1   3 my ($view, $err) = @_;
33 1         37 $view->detach_internal_server_error({ error => "$err"});
34             };
35              
36             has handle_encode_error => (
37             is=>'ro',
38             predicate=>'has_handle_encode_error');
39              
40             has default_view_model => (
41             is=>'ro',
42             required=>1,
43             default=>sub {
44             return $DEFAULT_VIEW_MODEL;
45             });
46              
47             has json_class => (
48             is=>'ro',
49             require=>1,
50             default=>sub {
51             return $DEFAULT_JSON_CLASS;
52             });
53              
54             has json_init_args => (
55             is=>'ro',
56             required=>1,
57             lazy=>1,
58             default=>sub {
59             my $self = shift;
60             my %init = (%JSON_INIT_ARGS, $self->has_json_extra_init_args ?
61             %{$self->json_extra_init_args} : ());
62              
63             return \%init;
64             });
65              
66             has json_extra_init_args => (
67             is=>'ro',
68             predicate=>'has_json_extra_init_args');
69              
70             has callback_param => ( is=>'ro', predicate=>'has_callback_param');
71              
72             sub COMPONENT {
73 1     1   330259 my ($class, $app, $args) = @_;
74 1         11 $args = $class->merge_config_hashes($class->config, $args);
75 1         52477 $class->_inject_default_view_model_into($app);
76 1         9789 return $class->new($app, $args);
77             }
78              
79             sub _inject_default_view_model_into {
80 1     1   4 my ($class, $app) = @_;
81 1         12 CatalystX::InjectComponent->inject(
82             into => $app,
83             component => 'Catalyst::Model::JSON::ViewData',
84             as => 'Model::JSON::ViewData' );
85             }
86              
87             sub build_per_context_instance {
88 6     6   426397 my ($self, $c, @args) = @_;
89 6 50       49 return bless +{
    50          
90             ctx=>$c,
91             parent=>$self,
92             json=>$self->json,
93             ($self->has_handle_encode_error ? (handle_encode_error=>$self->handle_encode_error) :()),
94             ($self->has_callback_param ? (callback_param=>$self->callback_param) :())
95             }, 'Catalyst::View::JSON::_PerRequest';
96             }
97              
98             1;
99              
100             =head1 NAME
101              
102             Catalyst::View::JSON::PerRequest - JSON View that owns its data
103              
104             =head1 SYNOPSIS
105              
106             MyApp->inject_components(
107             'View::JSON' => { from_component => 'Catalyst::View::JSON::PerRequest' }
108             );
109              
110             # In a controller...
111              
112             sub root :Chained(/) CaptureArgs(0) {
113             my ($self, $c) = @_;
114             $c->view('JSON')->data->set(z=>1);
115             }
116              
117             sub midpoint :Chained(root) CaptureArgs(0) {
118             my ($self, $c) = @_;
119             $c->view('JSON')->data->set(y=>1);
120             }
121              
122             sub endpoint :Chained(midpoint) Args(0) {
123             my ($self, $c) = @_;
124             $c->view('JSON')->created({
125             a => 1,
126             b => 2,
127             c => 3,
128             });
129             }
130              
131             =head1 DESCRIPTION
132              
133             This is a L<Catalyst::View> that produces JSON response from a given model.
134             It differs from some of the more classic JSON producing views (such as
135             L<Catalyst::View::JSON> in that is is a per request view (one view for each
136             request) and it defines a 'data' method to hold information to use to produce
137             a view.
138              
139             It also generates some local response helpers. You may or may not find this
140             approach leads to cleaner code.
141              
142             =head1 METHODS
143              
144             This view defines the following methods
145              
146             =head2 data (?$model)
147              
148             Used to set the view data model, and/or to called methods on it (for example
149             to set attributes that will later be used in the JSON response.).
150              
151             The default is an injected model based on L<Catalyst::Model::JSON::ViewData>
152             which you should review for basic usage. I recommend setting it to a custom
153             model that better encapsulates your view data. You may use any model in your
154             L<Catalyst> application as long as it does the method "TO_JSON".
155              
156             You may only set the view data model once. If you don't set it and just call
157             methods on it, the default view model is automatically used.
158              
159             =head2 res
160              
161             =head2 response
162              
163             $view->response($status, @headers, \%data||$object);
164             $view->response($status, \%data||$object);
165             $view->response(\%data||$object);
166             $view->response($status);
167             $view->response($status, @headers);
168              
169             Used to setup a response. Calling this method will setup an http status, finalize
170             headers and set a body response for the JSON. Content type will be set to
171             'application/json' automatically (you don't need to set this in a header).
172              
173             =head2 Method '->response' Helpers
174              
175             We map status codes from L<HTTP::Status> into methods to make sending common
176             request types more simple and more descriptive. The following are the same:
177              
178             $c->view->response(200, @args);
179             $c->view->ok(@args);
180              
181             do { $c->view->response(200, @args); $c->detach };
182             $c->view->detach_ok(@args);
183              
184             See L<HTTP::Status> for a full list of all the status code helpers.
185              
186             =head2 render ($data)
187              
188             Given a Perl data will return the JSON encoded version.
189              
190             my $json = $c->view->render(\%data);
191              
192             Should be a reference or object that does 'TO_JSON'
193              
194             =head2 process
195              
196             used as a target for $c->forward. This is mostly here for compatibility with some
197             existing methodology. For example allows using this view with the Renderview action
198             class (common practice). I'd consider it a depracated approach, personally.
199              
200             =head1 ATTRIBUTES
201              
202             This View defines the following attributes that can be set during configuration
203              
204             =head2 callback_param
205              
206             Optional. If set, we use this to get a method name for JSONP from the query parameters.
207              
208             For example if 'callback_param' is 'callback' and the request is:
209              
210             localhost/foo/bar?callback=mymethod
211              
212             Then the JSON response will be wrapped in a function call similar to:
213              
214             mymethod({
215             'foo': 'bar',
216             'baz': 'bin});
217              
218             Which is a common technique for overcoming some cross-domain restrictions of
219             XMLHttpRequest.
220              
221             There are some restrictions to the value of the callback method, for security.
222             For more see: L<http://ajaxian.com/archives/jsonp-json-with-padding>
223              
224             =head2 default_view_model
225              
226             The L<Catalyst> model that is the default model for your JSON return. The
227             default is set to a local instance of L<Catalyst::Model::JSON::ViewData>
228              
229             =head2 json_class
230              
231             The class used to perform JSON encoding. Default is L<JSON::MaybeXS>
232              
233             =head2 json_init_args
234              
235             Arguments used to initialize the L</json_class>. Defaults to:
236              
237             our %JSON_INIT_ARGS = (
238             utf8 => 1,
239             convert_blessed => 1);
240              
241             =head2 json_extra_init_args
242              
243             Allows you to 'tack on' some arguments to the JSON initialization without
244             messing with the defaults. Unless you really need to override the defaults
245             this is the method you should use.
246              
247             =head2 handle_encode_error
248              
249             A reference to a subroutine that is called when there is a failure to encode
250             the data given into a JSON format. This can be used globally as an attribute
251             on the defined configuration for the view, and you can set it or overide the
252             global settings on a context basis.
253              
254             Setting this optional attribute will capture and handle error conditions. We
255             will NOT bubble the error up to the global L<Catalyst> error handling (we don't
256             set $c->error for example). If you want that you need to set it yourself in
257             a custom handler, or don't define one.
258              
259             The subroutine receives two arguments: the view object and the exception. You
260             must setup a new, valid response. For example:
261              
262             package MyApp::View::JSON;
263              
264             use Moo;
265             extends 'Catalyst::View::JSON::PerRequest';
266              
267             package MyApp;
268              
269             use Catalyst;
270              
271             MyApp->config(
272             default_view =>'JSON',
273             'View::JSON' => {
274             handle_encode_error => sub {
275             my ($view, $err) = @_;
276             $view->detach_bad_request({ err => "$err"});
277             },
278             },
279             );
280              
281             MyApp->setup;
282              
283             Or setup/override per context:
284              
285             sub error :Local Args(0) {
286             my ($self, $c) = @_;
287              
288             $c->view->handle_encode_error(sub {
289             my ($view, $err) = @_;
290             $view->detach_bad_request({ err => "$err"});
291             });
292              
293             $c->view->ok( $bad_data );
294             }
295              
296             B<NOTE> If you mess up the return value (you return something that can't be
297             encoded) a second exception will occur which will NOT be handled and will then
298             bubble up to the main application.
299              
300             B<NOTE> The view package contains a global function to a usable default
301             error handler, should you wish to use something consistent and reasonably
302             valid. Example:
303              
304             MyApp->config(
305             default_view =>'JSON',
306             'View::JSON' => {
307             handle_encode_error => \&Catalyst::View::JSON::PerRequest::HANDLE_ENCODE_ERROR,
308             },
309             );
310              
311             The example handler is defined like this:
312              
313             sub HANDLE_ENCODE_ERROR {
314             my ($view, $err) = @_;
315             $view->detach_internal_server_error({ error => "$err"});
316             }
317              
318             =head1 UTF-8 NOTES
319              
320             Generally a view should not do any encoding since the core L<Catalyst>
321             framework handles all this for you. However, historically the popular
322             Catalyst JSON views and related ecosystem (such as L<Catalyst::Action::REST>)
323             have done UTF8 encoding and as a result for compatibility core Catalyst code
324             will assume a response content type of 'application/json' is already UTF8
325             encoded. So even though this is a new module, we will continue to maintain this
326             historical situation for compatibility reasons. As a result the UTF8 encoding
327             flags will be enabled and expect the contents of $c->res->body to be encoded
328             as expected. If you set your own JSON class for encoding, or set your own
329             initialization arguments, please keep in mind this expectation.
330              
331             =head1 SEE ALSO
332              
333             L<Catalyst>, L<Catalyst::View>, L<Catalyst::View::JSON>,
334             L<CatalystX::InjectComponent>, L<Catalyst::Component::InstancePerContext>,
335             L<JSON::MaybeXS>
336              
337             =head1 AUTHOR
338            
339             John Napiorkowski L<email:jjnapiork@cpan.org>
340            
341             =head1 COPYRIGHT & LICENSE
342            
343             Copyright 2015, John Napiorkowski L<email:jjnapiork@cpan.org>
344            
345             This library is free software; you can redistribute it and/or modify it under
346             the same terms as Perl itself.
347              
348             =cut