File Coverage

blib/lib/Catalyst/Plugin/Snippets.pm
Criterion Covered Total %
statement 57 59 96.6
branch 8 12 66.6
condition 1 3 33.3
subroutine 16 17 94.1
pod 4 4 100.0
total 86 95 90.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Catalyst::Plugin::Snippets;
4              
5 1     1   1169 use strict;
  1         3  
  1         46  
6 1     1   7 use warnings;
  1         3  
  1         34  
7 1     1   1094 use MRO::Compat;
  1         4815  
  1         43  
8              
9 1     1   2 BEGIN { eval { require JSON::Syck } }
  1         1042  
10              
11             our $VERSION = "0.03";
12              
13             sub setup {
14 1     1 1 1284 my $app = shift;
15 1         8 my $ret = $app->maybe::next::method(@_);
16              
17 1 50       99 %{ $app->config->{snippets} } = (
  1         4  
18             format => "plain",
19             allow_refs => 1,
20             use_session_id => 0,
21             json_content_type => "application/javascript+json",
22             content_type => "text/plain",
23 1         27 %{ $app->config->{snippets} || {} },
24             );
25              
26 1         149 $ret;
27             }
28              
29             sub snippet {
30 13     13 1 5100 my ( $c, $namespace, $key, @args ) = @_;
31              
32 13 100       30 my $meth = @args ? "set" : "get";
33              
34 13         28 my $o = $c->_snippet_opts($namespace);
35              
36 13         34 my $cache_key = $c->_snippet_key( $namespace, $key, $o );
37              
38 13         36 $c->cache->$meth( $cache_key, @args );
39             }
40              
41             sub _snippet_key {
42 13     13   102 my ( $c, $namespace, $key, $options ) = @_;
43              
44 13         28 my @long_key = ( "snippet", $namespace, $key );
45              
46 13 100       51 push @long_key, $c->sessionid if $options->{use_session_id};
47              
48 13         698 return join ":", @long_key;
49             }
50              
51             sub _snippet_opts {
52 17     17   26 my ( $c, $namespace, @opts ) = @_;
53              
54 17 50       43 my $override = @opts == 1 ? shift @opts : { @opts };
55              
56 17 50       42 my %options = (
57             %$override,
58 17         1223 %{ $c->config->{"snippets:$namespace"} || {} },
59 17         34 %{ $c->config->{"snippets"} },
60             );
61              
62 17         1095 return \%options;
63             }
64              
65             sub serve_snippet {
66 4     4 1 1281 my ( $c, $namespace, @_opts ) = @_;
67              
68 4   33     21 $namespace ||= $c->action->name;
69              
70 4         429 my $options = $c->_snippet_opts($namespace, @_opts);
71              
72 4         6 my $key = join( "/", @{ $c->request->arguments } ); # deparse ;-)
  4         12  
73              
74 4         493 my $value = $c->snippet( $namespace, $key );
75              
76 4         429 $c->send_snippet( $value, $options );
77             }
78              
79             sub send_snippet {
80 4     4 1 6 my ( $c, $value, $options ) = @_;
81              
82 4         14 $c->_snippet_sender($options)->( $options, $value );
83             }
84              
85             sub _snippet_sender {
86 4     4   8 my ( $c, $options ) = @_;
87              
88 4         80 my $formatter = $options->{format};
89              
90 4 50       10 if ( ref $formatter ) {
91 0     0   0 return sub { $c->_send_snippet( $formatter->( @_ ) ) };
  0         0  
92             } else {
93 4         8 my $name = "_send_snippet_$formatter";
94 4     4   19 return sub { $c->$name( @_ ) }
95 4         175 }
96             }
97              
98             sub _send_snippet {
99 4     4   8 my ( $c, $content_type, $body ) = @_;
100 4         13 $c->response->content_type($content_type);
101 4         580 $c->response->body($body);
102             }
103              
104             sub _send_snippet_json {
105 2     2   5 my ( $c, $options, $value ) = @_;
106 2         164 $c->_send_snippet(
107             $options->{json_content_type},
108             JSON::Syck::Dump($value),
109             );
110             }
111              
112             sub _send_snippet_plain {
113 2     2   5 my ( $c, $options, $value ) = @_;
114 1     1   5404 no warnings 'uninitialized';
  1         2  
  1         87  
115 2         11 $c->_send_snippet( $options->{content_type}, "$value" );
116             }
117              
118             __PACKAGE__;
119              
120             __END__
121              
122             =pod
123              
124             =head1 NAME
125              
126             Catalyst::Plugin::Snippets - Make sharing data with clients easy
127              
128             =head1 SYNOPSIS
129              
130             package MyApp;
131              
132             # use this plugin, and any Cache plugin
133             use Catalyst qw/
134             Cache::FastMmap
135             Snippets
136             /;
137              
138             package MyApp::Controller::Foo;
139              
140             sub action : Local {
141             my ( $self, $c ) = @_;
142             # ...
143             $c->snippet( $namespace, $key, $value );
144             }
145              
146             sub foo : Local {
147             my ( $self, $c ) = @_;
148             $c->serve_snippet( $namespace, \%options ); # namespace defaults to $c->action->name;
149             }
150              
151             sub other_action : Private {
152             my ( $self, $c ) = @_;
153             my $value = $c->snippet( $namespace, $key );
154             }
155              
156             =head1 DESCRIPTION
157              
158             This plugin provides a means of setting data that can then be queried by a
159             client in a different request.
160              
161             This is useful for making things such as progress meters and statistics amongst
162             other things.
163              
164             This plugin provides an API for storing data, and a way to conveniently fetch
165             it too.
166              
167             =head1 METHODS
168              
169             =over 4
170              
171             =item snippet $namespace, $key, [ $value ]
172              
173             This is an accessor for the client exposed data.
174              
175             If given a value it will set the value, and otherwise it will retrieve it.
176              
177             =item serve_snippet [ $namespace, ] [ %options ]
178              
179             This method will serve data bits to the client based on a key. The namespace
180             defaults to the action name.
181              
182             The optional options array reference will take this values. This array will
183             take it's default first from C<< $c->config->{"snippets:$namespace"} >> and
184             then it will revert to C<< $c->config->{snippets} >>.
185              
186             See the L</CONFIGURATION> section for detailed options.
187              
188             =item serialize_snippet $value, \%options
189              
190             This method is automatically called by C<serve_snippet> to serialize the
191             value in question.
192              
193             =item send_snippet $value, \%options
194              
195             This method is automatically called by C<serve_snippet> to set the response
196             body.
197              
198             =back
199              
200             =head1 INTERNAL METHODS
201              
202             =over 4
203              
204             =item setup
205              
206             Set up configuration defaults, etc.
207              
208             =back
209              
210             =head1 CONFIGURATION
211              
212             =over 4
213              
214             =item format
215              
216             This takes either C<json>, C<plain> (the default) or a code reference.
217              
218             The C<json> format specifies that all values values will be serialized as a
219             JSON expression suitable for consumption by javascript. This is reccomended for
220             deep structures.
221              
222             You can also use a code reference to implement your own serializer. This code reference should return two values: the content type, and a a value to set C<< $c->response->body >> to
223              
224             =item allow_refs
225              
226             If this is disabled reference values will raise an
227             error instead of being returned to the client.
228              
229             This is true by default.
230              
231             =item use_session_id
232              
233             This fields allows you to automatically create a different "namespace" for each
234             user, when used in conjunction with L<Catalyst::Plugin::Session>.
235              
236             This is false by default.
237              
238             =item content_type
239              
240             When the formatter type is C<plain> you may use this field to specify the
241             content-type header to use.
242              
243             This option defaults to C<text/plain>.
244              
245             =item json_content_type
246              
247             Since no one seems to agree on what the "right" content type for JSON data is,
248             we have this option too ;-).
249              
250             This option defaults to C<application/javascript+json>
251              
252             =back
253              
254             =head1 PRIVACY CONCERNS
255              
256             Like session keys, if the values are private the key used by your code should
257             be sufficiently hard to guess to protect the privacy of your users.
258              
259             Please use the C<use_session_id> option for the appropriate namespace unless
260             you have a good reason not to.
261              
262             =head1 RECIPES
263              
264             =head2 Ajax Progress Meter
265              
266             Suppuse your app runs a long running process in the server.
267              
268             sub do_it {
269             my ( $self, $c ) = @_;
270              
271             IPC::Run::run(\@cmd);
272              
273             # done
274             }
275              
276             The user might be upset that this takes a long while. If you can track
277             progress, along these lines:
278              
279             my $progress = 0;
280              
281             IPC::Run::run(\@cmd, ">", sub {
282             my $output = shift;
283             $progress++ if ( $output =~ /made_progress/ );
284             });
285              
286             then you can make use of this data to report progress to the user:
287              
288             $c->snippet( progress => $task_id => ++$progress )
289             if ( $output =~ /made_progress/ );
290              
291             Meanwhile, javascript code with timers could periodically poll the server using
292             an ajax request to update the progress level. To expose this data to the client
293             create an action somewhere:
294              
295             sub progress : Local {
296             my ( $self, $c ) = @_;
297             $c->serve_snippet;
298             }
299              
300             and have the client query for C<"/controller/progress/$task_id">.
301              
302             =cut
303              
304