NAME
CatalystX::Controller::ExtJS::REST - RESTful interface to dbic objects
VERSION
version 2.0.0
SYNOPSIS
package MyApp::Controller::User;
use base qw(CatalystX::Controller::ExtJS::REST);
__PACKAGE__->config({ ... });
1;
# set the Accept header to 'application/json' globally
Ext.Ajax.defaultHeaders = {
'Accept': 'application/json'
};
DESCRIPTION
This controller will make CRUD operations with ExtJS dead simple. Using REST you can update, create, remove, read and list objects which are retrieved via DBIx::Class.
USAGE
Set-up Form Configuration
To use this controller, you need to set up at least one configuration file per controller
If you create a controller MyApp::Controller::User
:
package MyApp::Controller::User;
use Moose;
extends 'CatalystX::Controller::ExtJS::REST';
1;
Forms can be defined either in files or directly in the controller. To see how to define forms directly in the controller see "forms".
If you are creating files, you need at least one file called root/forms/user.yml
. For a more fine grained control over object creation, deletion, update or listing, you have to create some more files.
root/
forms/
user.yml
user_get.yml
user_post.yml
user_put.yml
lists/
user.yml
Only root/forms/user.yml
is required. All other files are optional. If ExtJS issues a GET request, this controller will first try to find the file root/forms/user_get.yml
. If this file does not exist, it will fall back to the so called base file root/forms/user.yml
.
This controller tries to guess the correct model and resultset. The model defaults to DBIC
and the resultset is derived from the name of the controller. In this example the controller uses the resultset $c->model('DBIC::User')
.
You can override these values in the form config files:
---
model_config:
resultset: User
schema: DBIC
elements:
- name: username
- name: password
- name: name
- name: forename
# root/forms/user_put.yml
# make username and password required an object is created
---
load_config_file: root/forms/user.yml
constraints:
- type: Required
name: username
- type: Required
name: password
Now you can fire up your Catalyst app and you should see two new chained actions:
Loaded Chained actions:
...
| /users/... | /user/list
| /user/... | /user/object
Accessing objects
To access an object, simply request the controller's url with the desired method. A POST
request to /user
will create a new user object. The response will include the id of the new object. You can get the object by requesting /user/$id
via GET or remove it by using the DELETE
method.
To update an object, use PUT
. PUT is special since it also allows for partial submits. This means, that the object is loaded into the form before the request parameters are applied to it. You only need to send changed columns to the server.
Accessing a list of objects
You can access http://localhost:3000/users or http://localhost:3000/user to get a list of users, which can be used to populate an ExtJS store. If you access this URL with your browser you'll get a HTML representation of all users. If you access using a XMLHttpRequest using ExtJS the returned value will be a valid JSON string. Listing objects is very flexible and can easily be extended. There is also a built-in validation for query parameters. By default the following parameters are checked for sane defaults:
dir (either
asc
,ASC
,desc
orDESC
)limit (integer, range between 0 and 100)
start (positive integer)
You can extend the validation of parameters by providing an additional file. Place it in root/lists/
and add the suffix _options
(e. g. root/lists/user_options.yml
). You can overwrite or extend the validation configuration there.
Any more attributes you add to the url will result in a call to the corresponding resultset.
# http://localhost:3000/users/active/
$c->model('DBIC::Users')->active($c)->all;
As you can see, the Catalyst context object is passed as first parameter. You can even supply arguments to that method using a comma separated list:
# http://localhost:3000/users/active,arg1,arg2/
$c->model('DBIC::Users')->active($c, 'arg1', 'arg2')->all;
You can chain those method calls to any length. Though, you cannot access resultset method which are inherited from DBIx::Class::ResultSet. This is a security restriction because an attacker could call http://localhost:3000/users/delete
which will lead to $c->model('DBIC::Users')->delete
. This would remove all rows from DBIC::Users
!
To define a default resultset method which gets called every time the controller hits the result table, set:
__PACKAGE__->config({default_rs_method => 'restrict'});
This will lead to the following chain:
# http://localhost:3000/users/active,arg1,arg2/
$c->model('DBIC::Users')->restrict($c)->active($c, 'arg1', 'arg2')->all;
# same for GET, POST and PUT
# http://localhost:3000/user/1234
$c->model('DBIC::Users')->restrict($c)->find(1234);
The default_rs_method
defaults to the value of "default_rs_method". If it is not set by the configuration, this controller tries to call extjs_rest_$class
(i.e. extjs_rest_user
).
Handling Uploads
This module handles your uploads. If there is an upload and the name of that field exists in you form config, the column is set to an IO::File object. You need to handle this on the model side because storing a filehandle will most likely fail.
Fortunately, there are modules out there which can help you with that. Have a look at DBIx::Class::InflateColumn::FS. Don't use DBIx::Class:InflateColumn::File because it is deprecated and broken. If you need a more advanced processing of uploaded files, don't hesitate and overwrite "handle_uploads".
CONFIGURATION
Local configuration:
__PACKAGE__->config({ ... });
Global configuration for all controllers which use CatalystX::Controller::ExtJS::REST:
MyApp->config( {
CatalystX::Controller::ExtJS::REST =>
{ key => value }
} );
find_method
The method to call on the resultset to get an existing row object. This can be set to the name of a custom function function which is defined with the (custom) resultset class. It needs to take the primary key as first parameter.
Defaults to find
.
default_rs_method
This resultset method is called on every request. This is useful if you want to restrict the resultset, e. g. only find objects which are associated to the current user. The first parameter is the Catalyst context object and the second parameter is either list
(if a list of objects has been requested) or object
(if only one object is manipulated).
Nothing is called if the specified method does not exist.
This defaults to extjs_rest_[controller namespace]
.
A controller MyApp::Controller::User
expects a resultset method extjs_rest_user
.
root_property
Set the root property used by "list", update and create which will contain the data. Defaults to data
.
context_stash
To allow your form validation packages, etc, access to the catalyst context, a weakened reference of the context is copied into the form's stash.
$form->stash->{context};
This setting allows you to change the key name used in the form stash.
Default value: context
form_base_path
Defaults to root/forms
forms
If you define forms in the controller, files will not be loaded and are not required. You need to have at least the default
form defined. It is equivalent to the file without the request method appended.
Example:
forms => {
default => [
{ name => 'id' },
{ name => 'title' },
],
get => ...
list => ...
options => ...
}
See t/lib/MyApp/Controller/InlineUser.pm
for a working example.
limit
The maximum number of rows to return. Defaults to 100.
list_base_path
Defaults to root/lists
no_list_metadata
If set to a true value there will be no meta data send with lists.
Defaults to undef. That means the metaData hash will be send by default.
model_config
schema
Defaults to DBIC
resultset
Defaults to "default_resultset"
namespace
Defaults to "namespace" in Catalyst::Controller
order_by
Specify the default sort order.
Examples: order_by => 'productid' order_by => { -desc => 'updated_on' }
list_namespace
Defaults to the plural form of "namespace". If this is the same as "namespace" list_
is prepended.
LIMITATIONS
This module is limited to HTML::FormFu as form processing engine, DBIx::Class as ORM and Catalyst as web application framework.
PUBLIC ATTRIBUTES
To change the default value of an attribute, either set it as default value
package MyApp::Controller::MyController;
use Moose;
extends 'CatalystX::Controller::ExtJS::REST';
has '+default_result' => ( default => 'MyUser' );
use the config
__PACKAGE__->config( default_result => 'MyUser' );
or overwrite the builder
sub _build_default_result { return 'MyUser' };
default_resultset
Determines the default name of the resultset class from the Model / View or Controller class if the forms contains no <model_config/resultset> config value. Defaults to the class name of the controller.
list_base_path
Returns the path in which form config files for grids will be searched.
list_base_file
Returns the path to the specific form config file for grids or the default form config file if the specfic one can not be found.
path_to_forms
Returns the path to the specific form config file or the default form config file if the specfic one can not be found.
form_base_path
base_path
Returns the path in which form config files will be searched.
form_base_file
base_file
Returns the path to the default form config file.
PUBLIC METHODS
get_form
Returns a new HTML::FormFu::ExtJS class, sets the model config options and the request type to Catalyst
. The first parameter is the Catalyst context object $c
and optionally a Path::Class::File object to load a config file.
list
List Action which returns the data for a ExtJS grid.
handle_uploads
Handles uploaded files by assigning the filehandle to the column accessor of the DBIC row object.
As an upload field is a regular field it gets set twice. First the filename is set and $row->update
is called. This is entirely handled by HTML::FormFu::Model::DBIC. After that "handle_uploads" is called which sets the value of a upload field to the corresponding IO::File object. Make sure you test for that, if you plan to inflate such a column.
If you want to handle uploads yourself, overwrite "handle_uploads"
sub handle_uploads {
my ($self, $c, $row) = @_;
if(my $file = c->req->uploads->{upload}) {
$file->copy_to('yourdestination/'.$filename);
$row->upload($file->filename);
}
}
However, this should to be part of the model.
Since you cannot upload files with an XMLHttpRequest
, ExtJS creates an iframe and issues a POST
request in there. If you need to make a PUT
request you have to tunnel the desired method using a hidden field, by using the params
config option of Ext.form.Action.Submit
or extraParams
in Ext.Ajax.request
. The name of that parameter has to be x-tunneled-method
.
Make sure you do not include a file field in your GET
form definition. It will cause a security error in your browser because it is not allowed set the value of a file field.
object
REST Action which returns works with single model entites.
object_PUT
REST Action to update a single model entity with a PUT request.
object_POST
REST Action to create a single model entity with a POST request.
object_PUT_or_POST
Internal method for REST Actions to handle the update of single model entity with PUT or POST requests.
This method is called before the form is being processed. To add or remove form elements dynamically, this would be the right place.
object_GET
REST Action to get the data of a single model entity with a GET request.
object_DELETE
REST Action to delete a single model entity with a DELETE request.
PRIVATE ATTRIBUTES
_extjs_config
This attribute holds the configuration for the controller. It is created by merging by __PACKAGE__->config
with the default values.
PRIVATE METHODS
These methods are private. Please don't overwrite those unless you know what you are doing.
begin
Run this code before any action in this controller. It sets the ActionClass
to CatalystX::Action::ExtJS::Deserialize. This ActionClass
makes sure that no deserialization happens if the body's content is a file upload.
end
If the request contains a file upload field, extjs expects the json response to be serialized and returned in a document with the Content-type
set to text/html
.
_parse_NSPathPart_attr
_parse_NSListPathPart_attr
CONTRIBUTORS
Mario Minati
AUTHOR
Moritz Onken <onken@netcubed.de>
COPYRIGHT AND LICENSE
This software is Copyright (c) 2011 by Moritz Onken.
This is free software, licensed under:
The (three-clause) BSD License