File Coverage

blib/lib/CatalystX/QueryModel/DoesQueryModel.pm
Criterion Covered Total %
statement 73 99 73.7
branch 26 56 46.4
condition 4 15 26.6
subroutine 16 18 88.8
pod 2 10 20.0
total 121 198 61.1


line stmt bran cond sub pod time code
1             package CatalystX::QueryModel::DoesQueryModel;
2              
3 6     6   3109 use Moo::Role;
  6         24  
  6         93  
4 6     6   17507 use Scalar::Util;
  6         25  
  6         251  
5 6     6   63 use CatalystX::RequestModel::Utils::BadRequest;
  6         19  
  6         161  
6 6     6   2850 use CatalystX::QueryModel::QueryParser;
  6         26  
  6         8013  
7              
8             has ctx => (is=>'ro');
9             has current_namespace => (is=>'ro', predicate=>'has_current_namespace');
10             has current_parser => (is=>'ro', predicate=>'has_current_parser');
11             has catalyst_component_name => (is=>'ro');
12              
13             sub namespace {
14 15     15   59 my ($class_or_self, @data) = @_;
15 15 100       61 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
16 15 100       56 if(@data) {
17 6         19 @data = map { split /\./, $_ } @data;
  6         30  
18 6         55 CatalystX::QueryModel::_add_metadata($class, 'namespace', @data);
19             }
20              
21 15 100       137 return $class_or_self->namespace_metadata if $class_or_self->can('namespace_metadata');
22             }
23              
24             sub has_content_type {
25 9     9 0 25 my ($self) = @_;
26 9 50       42 return $self->content_type ? 1:0;
27             }
28              
29             sub content_type {
30 9     9   39 my ($class_or_self, $ct) = @_;
31 9 50       32 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
32 9 50       153 CatalystX::QueryModel::_add_metadata($class, 'content_type', $ct) if $ct;
33              
34 9 50       109 if($class_or_self->can('content_type_metadata')) {
35 0         0 my ($ct) = $class_or_self->content_type_metadata; # needed because this returns an array but we only want the first one
36 0         0 return $ct;
37             }
38             }
39              
40             sub property {
41 60     60   160 my ($class_or_self, $attr, $data_proto, $options) = @_;
42 60 50       142 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
43 60 50       152 if(defined $data_proto) {
44 60 50 50     210 my $data = (ref($data_proto)||'') eq 'HASH' ? $data_proto : +{ name => $attr };
45 60 50       139 $data->{name} = $attr unless exists($data->{name});
46 60         256 CatalystX::QueryModel::_add_metadata($class, 'property_data', +{$attr => $data});
47             }
48             }
49              
50             sub properties {
51 18     18   42 my ($class_or_self, @data) = @_;
52 18 50       45 my $class = ref($class_or_self) ? ref($class_or_self) : $class_or_self;
53 18         63 while(@data) {
54 0         0 my $attr = shift(@data);
55 0 0 0     0 my $data = (ref($data[0])||'') eq 'HASH' ? shift(@data) : +{ name => $attr };
56 0 0       0 $data->{name} = $attr unless exists($data->{name});
57 0         0 CatalystX::QueryModel::_add_metadata($class, 'property_data', +{$attr => $data});
58             }
59              
60 18 50       141 return $class_or_self->property_data_metadata if $class_or_self->can('property_data_metadata');
61             }
62              
63             sub COMPONENT {
64 24     24 0 6938 my ($class, $app, $args) = @_;
65 24         217 $args = $class->merge_config_hashes($class->config, $args);
66 24         4551 return bless $args, $class;
67             }
68              
69             ## TODO handle if we are wrapping a model that already does ACCEPT_CONTEXT
70             sub ACCEPT_CONTEXT {
71 9     9 0 596 my $self = shift;
72 9         15 my $c = shift;
73              
74 9         63 my %args = (%$self, @_);
75 9         47 my %query_args = $self->parse_query($c, %args);
76 9         44 my %init_args = (%args, %query_args, ctx=>$c);
77 9         26 my $class = ref($self);
78              
79 9         45 return my $request_model = $self->build_query_model($c, $class, %init_args);
80             }
81              
82             sub build_query_model {
83 9     9 0 33 my ($self, $c, $class, %init_args) = @_;
84              
85             my $request_model = eval {
86             $class->new(%init_args)
87 9   33     19 } || do {
88             CatalystX::RequestModel::Utils::BadRequest->throw(class=>$class, error_trace=>$@);
89             };
90 9         2717 return $request_model;
91             }
92              
93             sub parse_query {
94 9     9 0 30 my ($self, $c, %args) = @_;
95              
96 9         49 my @rules = $self->properties;
97 9         39 my @ns = $self->get_namespace(%args);
98 9         25 my $parser_class = 'CatalystX::QueryModel::QueryParser';
99             my $parser = exists($args{current_parser}) ?
100             $args{current_parser} :
101 9 50       85 $parser_class->new(ctx=>$c, request_model=>$self );
102              
103 9 50       27 $parser->{context} = $args{context} if exists $args{context}; ## TODO ulgy
104 9         63 my %request_args = $parser->parse(\@ns, \@rules);
105 9         68 return %request_args;
106             }
107              
108             sub get_content_type {
109 0     0 0 0 my ($self, $c) = @_;
110 0         0 my $ct = $c->req->content_type;
111 0 0 0     0 return 'application/x-www-form-urlencoded' if !$ct && $c->req->method eq 'GET';
112 0 0       0 return 'application/x-www-form-urlencoded' if $self->get_content_in eq 'query';
113 0         0 return $ct;
114             }
115              
116             sub get_namespace {
117 9     9 0 26 my ($self, %args) = @_;
118 9 50       27 return @{$args{current_namespace}} if exists($args{current_namespace});
  0         0  
119 9         38 return grep { defined $_ } $self->namespace;
  9         74  
120             }
121              
122             sub get_attribute_value_for {
123 17     17 0 43 my ($self, $attr) = @_;
124 17         539 return $self->$attr;
125             }
126              
127             sub nested_params {
128 9     9 1 733 my $self = shift;
129 9         17 my %return;
130 9         34 foreach my $p ($self->properties) {
131 23         79 my ($attr, $meta) = %$p;
132 23 100       71 if(my $predicate = $meta->{attr_predicate}) {
133 19 50       49 if($meta->{omit_empty}) {
134 19 100       738 next unless $self->$predicate; # skip empties when omit_empty=>1
135             }
136             }
137              
138 17         50 my $value = $self->get_attribute_value_for($attr);
139 17 50 50     106 if( (ref($value)||'') eq 'ARRAY') {
    50 33        
140 0         0 my @gathered = ();
141 0         0 foreach my $v (@$value) {
142 0 0       0 if(Scalar::Util::blessed($v)) {
143 0         0 my $params = $v->nested_params;
144 0 0       0 push @gathered, $params if keys(%$params);
145             } else {
146 0         0 push @gathered, $v;
147             }
148              
149             }
150 0         0 $return{$attr} = \@gathered;
151             } elsif(Scalar::Util::blessed($value) && $value->can('nested_params')) {
152 0         0 my $params = $value->nested_params;
153 0 0       0 next unless keys(%$params);
154 0         0 $return{$attr} = $params;
155             } else {
156 17         148 $return{$attr} = $value;
157             }
158             }
159 9         72 return \%return;
160             }
161              
162             sub get {
163 0     0 1   my ($self, @fields) = @_;
164 0           my $p = $self->nested_params;
165 0           my @got = @$p{@fields};
166 0           return @got;
167             }
168              
169             1;
170              
171             =head1 NAME
172              
173             CatalystX::QueryModel::DoesQueryModel - Role to provide query model API
174              
175             =head1 SYNOPSIS
176              
177             Generally you will apply this role via L<CatalystX::QueryModel>
178              
179             See L<CatalystX::QueryModel> for a more general overview.
180              
181             =head1 DESCRIPTION
182              
183             A role that gives a L<Catalyst::Model> the ability to indicate which of its attributes should be
184             consider query model data, as well as additional need meta data so that we can process it
185             properly.
186              
187             Since we need to wrap C<has> you should never apply this role manually but rather instead use
188             L<CatalystX::QueryModel> to apply it for you. If you need to customize this role you will
189             also need to subclass L<CatalystX::QueryModel> and have that new subclass apply you custom
190             role. Please ping me if you really need this since I guess we could change L<CatalystX::QueryModel>
191             to make it easier to supply a custom role, just let me know your use case.
192              
193             =head1 METHODS
194              
195             This class defines the following public API
196              
197             =head2 nested_params
198              
199             Returns all the attributes marked as request properties in the form of a hashref. If any of the
200             properties refer to an array or indexed value, or an object, we automatically follow that to
201             return all the property data below.
202              
203             Attributes that are empty will be left out of the return data structure.
204              
205             Easiest way to get all your data but then again you get a structure that is very tightly tied to
206             your request model.
207              
208             =head2 get
209              
210             Accepts a list of attributes that refer to request properties and returns their values. In the case
211             when the attribute listed has no value, you will instead get an C<undef>.
212              
213             =head1 EXCEPTIONS
214              
215             This class can throw the following exceptions:
216              
217             =head2 Invalid Request Content Body
218              
219             If we can't create an instance of the request model we throw a L<CatalystX::RequestModel::Utils::BadRequest>.
220             This will get interpretated as an HTTP 400 status client error if you are using L<CatalystX::Errors>.
221              
222             =head1 AUTHOR
223              
224             See L<CatalystX::QueryModel>.
225            
226             =head1 COPYRIGHT
227            
228             See L<CatalystX::QueryModel>.
229              
230             =head1 LICENSE
231            
232             See L<CatalystX::QueryModel>.
233            
234             =cut
235