#ABSTRACT: Provides a DBIx::Class web service automagically
use Moose;
-BEGIN { extends 'Catalyst::Controller'; }
+BEGIN { extends 'Catalyst::Controller::ActionRole'; }
use CGI::Expand ();
use DBIx::Class::ResultClass::HashRefInflator;
This action is the chain root of the controller. It must either be overridden or configured to provide a base pathpart to the action and also a parent action. For example, for class MyAppDB::Track you might have
package MyApp::Controller::API::RPC::Track;
- use Moose;
+ use Moose;
BEGIN { extends 'Catalyst::Controller::DBIC::API::RPC'; }
__PACKAGE__->config
- ( action => { setup => { PathPart => 'track', Chained => '/api/rpc/rpc_base' } },
+ ( action => { setup => { PathPart => 'track', Chained => '/api/rpc/rpc_base' } },
...
);
{
$req_params = $c->req->data;
}
- else
+ else
{
$req_params = CGI::Expand->expand_hash($c->req->params);
$req_params->{$param}->{$key} = $deserialized;
}
catch
- {
+ {
$c->log->debug("Param '$param.$key' did not deserialize appropriately: $_")
if $c->debug;
}
$req_params->{$param} = $deserialized;
}
catch
- {
+ {
$c->log->debug("Param '$param' did not deserialize appropriately: $_")
if $c->debug;
}
}
}
}
-
+
$self->inflate_request($c, $req_params);
}
}
=method_protected inflate_request
-
+
inflate_request is called at the end of deserialize to populate key portions of the request with the useful bits
=cut
try
{
# set static arguments
- $c->req->_set_controller($self);
+ $c->req->_set_controller($self);
# set request arguments
$c->req->_set_request_data($params);
# set the current resultset
$c->req->_set_current_result_set($self->generate_rs($c));
-
+
}
catch
{
sub objects_no_id :Chained('deserialize') :CaptureArgs(0) :PathPart('')
{
my ($self, $c) = @_;
-
+
if($c->req->has_request_data)
{
my $data = $c->req->request_data;
my $vals;
-
+
if(exists($data->{$self->data_root}) && defined($data->{$self->data_root}))
{
my $root = $data->{$self->data_root};
?page=2&count=20
Would result in this search:
-
+
$rs->search({}, { page => 2, rows => 20 })
=cut
$self->list_format_output($c);
# make sure there are no objects lingering
- $c->req->clear_objects();
+ $c->req->clear_objects();
}
=method_protected list_munge_parameters
list_munge_parameters is a noop by default. All arguments will be passed through without any manipulation. In order to successfully manipulate the parameters before the search is performed, simply access $c->req->search_parameters|search_attributes (ArrayRef and HashRef respectively), which correspond directly to ->search($parameters, $attributes). Parameter keys will be in already-aliased form.
+To store the munged parameters call $c->req->_set_search_parameters($newparams) and $c->req->_set_search_attributes($newattrs).
=cut
sub list_perform_search
{
my ($self, $c) = @_;
-
- try
+
+ try
{
my $req = $c->req;
-
+
my $rs = $req->current_result_set->search
(
- $req->search_parameters,
+ $req->search_parameters,
$req->search_attributes
);
my $rs = $c->req->current_result_set->search;
$rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
-
+
try
{
my $output = {};
my $formatted = [];
-
+
foreach my $row ($rs->all)
{
push(@$formatted, $self->row_format_output($c, $row));
}
-
+
$output->{$self->data_root} = $formatted;
if ($c->req->has_search_total_entries)
sub update_or_create
{
my ($self, $c) = @_;
-
+
if($c->req->has_objects)
{
$self->validate_objects($c);
sub transact_objects
{
my ($self, $c, $coderef) = @_;
-
+
try
{
$self->stored_result_source->schema->txn_do
{
my $err = $_;
$c->log->error($err);
- $err =~ s/\s+at\s+\/.+\n$//g;
+ $err =~ s/\s+at\s+.+\n$//g;
$self->push_error($c, { message => $err });
$c->detach();
}
my %requires_map = map
{
$_ => 1
- }
+ }
@{
- ($object->in_storage)
- ? []
+ ($object->in_storage)
+ ? []
: $c->stash->{create_requires} || $self->create_requires
};
-
+
my %allows_map = map
{
(ref $_) ? %{$_} : ($_ => 1)
- }
+ }
(
- keys %requires_map,
+ keys %requires_map,
@{
- ($object->in_storage)
- ? ($c->stash->{update_allows} || $self->update_allows)
+ ($object->in_storage)
+ ? ($c->stash->{update_allows} || $self->update_allows)
: ($c->stash->{create_allows} || $self->create_allows)
}
);
{
# check value defined if key required
my $allowed_fields = $allows_map{$key};
-
+
if (ref $allowed_fields)
{
my $related_source = $object->result_source->related_source($key);
my $related_params = $params->{$key};
my %allowed_related_map = map { $_ => 1 } @$allowed_fields;
my $allowed_related_cols = ($allowed_related_map{'*'}) ? [$related_source->columns] : $allowed_fields;
-
+
foreach my $related_col (@{$allowed_related_cols})
{
- if (my $related_col_value = $related_params->{$related_col}) {
+ if (defined(my $related_col_value = $related_params->{$related_col})) {
$values{$key}{$related_col} = $related_col_value;
}
}
}
- else
+ else
{
my $value = $params->{$key};
}
}
}
-
+
# check for multiple values
if (ref($value) && !($value == JSON::Any::true || $value == JSON::Any::false))
{
}
}
- unless (keys %values || !$object->in_storage)
+ unless (keys %values || !$object->in_storage)
{
die 'No valid keys passed';
}
- return \%values;
+ return \%values;
}
=method_protected delete
sub delete
{
my ($self, $c) = @_;
-
+
if($c->req->has_objects)
{
$self->transact_objects($c, sub { $self->delete_objects($c, @_) });
{
$self->update_object_from_params($c, $object, $params);
}
- else
+ else
{
$self->insert_object_from_params($c, $object, $params);
}
$self->update_object_relation($c, $object, delete $params->{$key}, $key);
}
}
-
+
$object->update($params);
}
{
my ($self, $c, $object, $related_params, $relation) = @_;
my $row = $object->find_related($relation, {} , {});
- $row->update($related_params);
+
+ if ($row) {
+ $row->update($related_params);
+ }
+ else {
+ $object->create_related($relation, $related_params);
+ }
}
=method_protected insert_object_from_params
sub insert_object_from_params
{
my ($self, $c, $object, $params) = @_;
- $object->set_columns($params);
+
+ my %rels;
+ while (my ($k, $v) = each %{ $params }) {
+ if (ref $v && !($v == JSON::Any::true || $v == JSON::Any::false)) {
+ $rels{$k} = $v;
+ }
+ else {
+ $object->set_column($k => $v);
+ }
+ }
+
$object->insert;
+
+ while (my ($k, $v) = each %rels) {
+ $object->create_related($k, $v);
+ }
}
=method_protected delete_objects
$c->stash->{response}->{success} = $self->use_json_boolean ? JSON::Any::true : 'true';
$default_status = 200;
}
-
+
unless ($default_status == 200)
{
delete $c->stash->{response}->{$self->data_root};
You can also use this to allow custom columns should you wish to allow them through in order to be caught by a custom resultset. For example:
package RestTest::Controller::API::RPC::TrackExposed;
-
+
...
-
+
__PACKAGE__->config
( ...,
search_exposes => [qw/position title custom_column/],
and then in your custom resultset:
package RestTest::Schema::ResultSet::Track;
-
+
use base 'RestTest::Schema::ResultSet';
-
+
sub search {
my $self = shift;
my ($clause, $params) = @_;
# $c->req->all_objects will contain all of the created
$self->next::method($c);
- if ($c->req->has_objects) {
+ if ($c->req->has_objects) {
# $c->stash->{response} will be serialized in the end action
$c->stash->{response}->{$self->data_root} = [ map { { $_->get_inflated_columns } } ($c->req->all_objects) ] ;
}
Similarly you might want create, update and delete to all forward to the list action once they are done so you can refresh your view. This should also be simple enough.
-If more extensive customization is required, it is recommened to peer into the roles that comprise the system and make use
+If more extensive customization is required, it is recommened to peer into the roles that comprise the system and make use
=head1 NOTES