File Coverage

blib/lib/Jifty/Param/Schema.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Jifty::Param::Schema;
2              
3             =head1 NAME
4              
5             Jifty::Param::Schema - Declare parameters of a Jifty action with ease.
6              
7             =head1 SYNOPSIS
8              
9             package MyApp::Action::Login;
10             use Jifty::Param::Schema;
11             use Jifty::Action schema {
12              
13             param email =>
14             label is 'Email address',
15             is mandatory,
16             ajax validates;
17              
18             param password =>
19             type is 'password',
20             label is 'Password',
21             is mandatory;
22              
23             param remember =>
24             type is 'checkbox',
25             label is 'Remember me?',
26             hints is 'If you want, your browser can remember your login for you',
27             default is 0;
28              
29             };
30              
31             =head1 DESCRIPTION
32              
33             This module provides a simple syntax to declare action parameters.
34              
35             It re-exports C and C from L, for setting
36             parameter fields that must be recomputed at request-time:
37              
38             param name =>
39             default is defer { Jifty->web->current_user->name };
40              
41             See L for more information about C.
42              
43             =head2 schema
44              
45             The C block from a L subclass describes an action
46             for a Jifty application.
47              
48             Within the C block, the localization function C<_> is redefined
49             with C, so that it resolves into a dynamic value that will be
50             recalculated upon each request, according to the user's current language
51             preference.
52              
53             =head2 param
54              
55             Each C statement inside the C block sets out the name
56             and attributes used to describe one named parameter, which is then used
57             to build a L object. That class defines possible field names
58             to use in the declarative syntax here.
59              
60             The C function is not available outside the C block.
61              
62             =head1 ALIASES
63              
64             In addition to the labels provided by L and
65             L, this module offers the following aliases:
66              
67             ajax validates, # ajax_validates is 1
68             ajax canonicalizes, # ajax_canonicalizes is 1
69             order is -1, # sort_order is -1
70             default is 0, # default_value is 0
71             valid are qw( 1 2 3 ), # valid_values are qw( 1 2 3 )
72             available are qw( 1 2 3 ), # available_values are qw( 1 2 3 )
73             render as 'select', # render_as is 'select'
74              
75             =head1 SEE ALSO
76              
77             L, L
78              
79             =cut
80              
81 1     1   21396 use strict;
  1         2  
  1         30  
82 1     1   6 use warnings;
  1         1  
  1         22  
83 1     1   480 use Jifty::I18N;
  0            
  0            
84             use Jifty::Param;
85             use Scalar::Defer;
86             use SUPER;
87              
88             use Object::Declare (
89             mapping => {
90             param => 'Jifty::Param',
91             },
92             aliases => {
93             default => 'default_value',
94             available => 'available_values',
95             valid => 'valid_values',
96             render => 'render_as',
97             order => 'sort_order',
98             },
99             copula => {
100             is => '',
101             are => '',
102             as => '',
103             ajax => 'ajax_',
104             }
105             );
106             use Exporter::Lite;
107             use Class::Data::Inheritable;
108              
109             our @EXPORT = qw( defer lazy param schema );
110              
111             sub schema (&) {
112             my $code = shift;
113             my $from = caller;
114              
115             no warnings 'redefine';
116            
117             # See the perldoc for an explanation of why we're redefining
118             # the localization method _().
119             local *_ = sub { my $args = \@_; defer { _(@$args) } };
120              
121             Class::Data::Inheritable::mk_classdata($from => qw/PARAMS/);
122             my @params = &declare($code);
123              
124             # The .99 here is a flag for Jifty::Action::Record to mark autogenerated orders
125             my $count = 10000.99;
126             foreach my $param (@params) {
127             next if !ref($param) or defined($param->sort_order);
128             $param->sort_order($count);
129             $count += 10;
130             }
131              
132             if ( my $super_params = $from->super('PARAMS') ) {
133             my $super = $super_params->();
134             # XXX: skip the merge_params if the parent class' PARAMS is
135             # empty to avoid the currently kludgy merge_params
136             # implementation to pollute scalar::defer with undetermined
137             # behaviour
138             $from->PARAMS(
139             ($super && keys %$super)
140             ? merge_params( $super, {@params} )
141             : {@params} );
142             }
143             else {
144             $from->PARAMS( {@params} );
145             }
146              
147             no strict 'refs';
148             push @{$from . '::ISA'}, 'Jifty::Action'
149             unless $from->isa('Jifty::Action');
150             return;
151             }
152              
153             use Hash::Merge ();
154             no warnings 'uninitialized';
155             use constant MERGE_PARAM_BEHAVIOUR => {
156             SCALAR => {
157             SCALAR => sub { length($_[1]) ? $_[1] : $_[0] },
158             ARRAY => sub { [ @{$_[1]} ] },
159             HASH => sub { $_[1] } },
160             ARRAY => {
161             SCALAR => sub { length($_[1]) ? $_[1] : $_[0] },
162             ARRAY => sub { [ @{$_[1]} ] },
163             HASH => sub { $_[1] } },
164             HASH => {
165             SCALAR => sub { length($_[1]) ? $_[1] : $_[0] },
166             ARRAY => sub { [ @{$_[1]} ] },
167             HASH => sub { Hash::Merge::_merge_hashes( $Hash::Merge::context, $_[0], $_[1] ) } }
168             };
169              
170             my $prev_behaviour = Hash::Merge::get_behavior();
171             # the behavior name must be upper-case
172             Hash::Merge::specify_behavior( MERGE_PARAM_BEHAVIOUR, 'MERGE_PARAMS' );
173             Hash::Merge::set_behavior( $prev_behaviour );
174             my $merge = Hash::Merge->new('MERGE_PARAMS');
175              
176             =head2 merge_params HASHREF HASHREF
177              
178             Takes two hashrefs. Merges them together and returns the merged hashref.
179              
180             - Empty fields in subclasses don't override nonempty fields in superclass anymore.
181             - Arrays don't merge; e.g. if parent class's valid_values is [1,2,3,4], and
182             subclass's valid_values() is [1,2], they don't somehow become [1,2,3,4,1,2].
183              
184             BUG: This should either be a private routine or factored out into Jifty::Util
185              
186              
187              
188             =cut
189              
190             sub merge_params {
191             # We pull this deref and re-ref trick to un-bless any
192             # Jifty::Params which might exist; Hash::Merge pre-0.10 merged
193             # objects and hahrefs with no complaint, but 0.10 doesn't. This
194             # is a horrible, horrible hack, and will hopeflly be able to be
195             # backed out if and when Hash::Merge reverts to the old behavior.
196             my $field_type = {};
197             my @types;
198             for my $m (@_) {
199             my @t;
200             for (keys %{$m}) {
201             push @t, ref $m->{$_};
202             $field_type->{$_} = ref $m->{$_};
203             bless $m->{$_}, "HASH";
204             }
205             push @types, \@t;
206             }
207             my $rv = $merge->merge(@_);
208             for my $m (@_) {
209             my @t = @{shift @types};
210             for (keys %{$m}) {
211             bless $m->{$_}, shift @t;
212             }
213             }
214              
215             for ( keys %$rv ) {
216             bless $rv->{$_}, $field_type->{$_}
217             if $field_type->{$_};
218             }
219             return $rv;
220             }
221              
222             1;