NAME
HTML::FormFu::Manual::Models - Interaction with data stores via models
VERSION
version 0.001
INTRODUCTION
One of the most powerful features of HTML::FormFu is its ability to integrate with various data models and automatically perform operations with the form values, e.g. updating the corresponding database fields. In this chapter we will dissect how HTML::FormFu models work and how you can use them to dramatically reduce the development time for you applications.
AVAILABLE MODELS
Currently there are two models available for use with HTML::FormFu: HTML::FormFu::Model::HashRef, which ships with the core HTML::FormFu distribution, and HTML::FormFu::Model::DBIC, which is a package which you need to install separately. HTML::FormFu::Model::HashRef allows you to fill in forms from a hashref, or convert form values to a hashref. It can be useful if you are interacting with a datastore that is not supported by HTML::FormFu but can process data stored in hashrefs. HTML::FormFu::Model::DBIC is a much more complicated module which links HTML::FormFu with the powerful DBIx::Class library.
WORKING WITH MODELS - THE BASICS
Every HTML::FormFu object with a properly configured model has a model
method which returns the underlying model object. You can access the functionality that the model provides by invoking methods on that model object. Additionally, you can pass configuration options to the model object by invoking the model_config method of individual form elements.
Let us look first at the functionality of the model object. It has two very important methods:
- default_values
-
This method is similar to the default_values method of the base HTML::FormFu class, but while the
$form->default_values
accepts as its argument a simple hash of field names and their respective values,$form->model->default_values
allows us to fill in a form with default values from arguments that are specific to every model class. For example, HTML::FormFu::Model::HashRef fills in default values from a hashref, while HTML::FormFu::Model::DBIC fills in default values from aDBIx::Class::Row
object. Internally, each model class converts the values of its argument into a simple hash and passes its values to default_values in HTML::FormFu. - update
-
The update method updates a foreign object specific to the model class with the values from the submitted form. With HTML::FormFu::Model::HashRef it will create a hashref with the values from the form. With HTML::FormFu::Model::DBIC it will take as an argument to update a
DBIx::Class::Row
object, and its values in the database will be updated with the ones submitted from the form. Here is an example of how to use update in conjunction with default_values:# fetch a record from the database my $record = $dbic_resultset->find({ id => 5 }); # load the data from the record into the form $form->model->default_values($record); # later on in the script .... if ($form->submitted_and_valid) { # take the submitted values, update the record object # and save to database $form->model->update($record); }
USING HTML::FormFu::Model::DBIC
The rest of this chapter will focus exclusively on HTML::FormFu::Model::DBIC, since this is the more complex module and the one that you are most likely to need in your applications.
Loading and configuring the model class
Use the model method of the HTML::FormFu object to specify which model class you want to load:
# loads HTML::FormFu::Model::DBIC
$form->model('DBIC');
Normally your form will work with a particular DBIx::Class schema. You specify the name of that schema by putting a schema
entry in the form's stash:
my $schema = My::Schema::Class->connect( ... );
$form->stash( schema => $schema );
If you are working with Catalyst, you can just supply the name of a Catalyst model instead:
$form->stash( schema => 'Model::My::Schema::Class' );
Although a form is in practice associated with a particular DBIx::Class::ResultSource, you do not normally have to specify which class that is. Instead, you pass an object of that class as the argument to default_values or update. HTML::FormFu will work happily as long as you have given it an object which has columns and relationships corresponding to the ones you have specified in the form definition.
Most of the model configuration happens via the model_config method of individual form elements. We will describe some of the most important options below.
Populating select fields
Before we move on to displaying and updating data from the database, we will discuss a very important topic - loading options for select fields from the database.
We have a Books
schema for a database containing information about books. One of the tables in the database - genre
, contains a list of genres that can be associated with books. Here is the result class for the genre
table:
### Books/Result/Genre.pm ###
package Books::Result::Genre;
use DBIx::Class::Candy;
table 'genre';
column 'id' => {
data_type => 'int',
is_auto_increment => 1,
};
column 'name' => {
data_type => 'varchar',
size => 256,
};
primary_key 'id';
1;
And here is the list of predefined genres that the genre
table contains:
id name
-- -----------------
1 Children's
2 Fantasy
3 Horror
4 Mystery
5 Romance
6 Science Fiction
7 Short Fiction
8 Thriller/Suspense
When creating or editing a book record, we want to be able to choose from that list of genres. Normally we would do that by using either a select field or a group of radio buttons. We will create a script that builds that form and prints it out to STDOUT:
use strict;
use warnings;
use HTML::FormFu;
use Books;
my $form = HTML::FormFu->new;
# load configuration file
$form->load_config_file("books.conf");
# setup model and schema
$form->model('DBIC');
my $schema = Books->connect( ... );
$form->stash( schema => $schema );
$form->process;
print $form->render;
We will start with the following configuration file (named books.conf
, in Config::General format):
<element>
name name
type Text
</element>
<element>
name genre
type Select
</element>
<element>
type Submit
</element>
Nothing surprising here, here is the output that we get when we render the form:
<form action="" method="post">
<div class="text">
<input name="name" type="text" />
</div>
<div class="select">
<select name="genre">
</select>
</div>
<div class="button">
<input type="submit" />
</div>
</form>
But if we change the definition of the genre
field as follows:
<element>
name genre
type Select
<model_config>
resultset Genre
</model_config>
</element>
we get all the options for the select field populated from the database:
<form action="" method="post">
<div class="text">
<input name="name" type="text" />
</div>
<div class="select">
<select name="genre">
<option value="1">Children's</option>
<option value="2">Fantasy</option>
<option value="3">Horror</option>
<option value="4">Mystery</option>
<option value="5">Romance</option>
<option value="6">Science Fiction</option>
<option value="7">Short Fiction</option>
<option value="8">Thriller/Suspense</option>
</select>
</div>
<div class="button">
<input type="submit" />
</div>
</form>
What happened here? As we said earlier, all options specific to the particular model we are using are specified in a model_config
hashref. In order for HTML::FormFu::Model::DBIC to populate our select fields, several conditions need to be in place:
The form element must inherit from HTML::FormFu::Element::_Group, i.e. one of HTML::FormFu::Element::ComboBox, HTML::FormFu::Element::Radiogroup, HTML::FormFu::Element::Checkboxgroup or HTML::FormFu::Element::Select.
The
resultset
option must be passed to model_config, so that HTML::FormFu::Model::DBIC knows which result class to use to fetch the values. Ifresultset
does not contain any column (:
) characters, it will be expanded into a result class belonging to the loaded schema (i.e.Genre
will becomeBooks::Result::Genre
). Otherwise it will be used as is.If no resultset is specified, but model_config is present, HTML::FormFu::Model::DBIC will attempt to guess the result class from the name of the element. So, the following two element definitions are equivalent:
<element> name something type Select <model_config /> </element> # is the same as: <element> name something type Select <model_config> resultset Something </model_config> </element>
And last, we may need to specify which column to use as the value for each option, and which column to use as the label. By default HTML::FormFu::Model::DBIC uses the column holding the primary key as the value, and the first text column in the table as the label. You can override this behaviour with the
id_column
andlabel_column
options respectively. The following definition of the select element achieves the same result as above, but is explicit about which columns to use for option values and labels:<element> name genre type Select <model_config> resultset Genre id_column id label_column name </model_config> </element>
Let us say that at some point later a new requirement comes up that you must add non-fiction genres to the database, and you must be able to distinguish them from the fiction genres. We will add a boolean column to specify whether a genre is fiction or not. The new genre
table will look like that:
id name fiction
-- ----------------------- -------
1 Children's 1
2 Fantasy 1
3 Horror 1
4 Mystery 1
5 Romance 1
6 Science Fiction 1
7 Short Fiction 1
8 Thriller/Suspense 1
9 Essay 0
10 Journal 0
11 History 0
12 Scientific Paper 0
13 Biography 0
14 Textbook 0
15 Travel Book 0
16 Technical Documentation 0
And of course we add the new column in our Books::Result::Genre
class as well:
package Books::Result::Genre
...
column 'fiction' => {
data_type => 'int',
size => 1,
};
...
Now if we are required to create a form for editing non-fiction books, we may request that our select field be populated with only a subset of the rows in the genre
table. We change the definition of the select element as follows:
<element>
name genre
type Select
<model_config>
resultset Genre
<condition>
fiction 0
</condition>
</model_config>
</element>
This will produce the following form:
<form action="" method="post">
<div class="text">
<input name="name" type="text" />
</div>
<div class="select">
<select name="genre">
<option value="9">Essay</option>
<option value="10">Journal</option>
<option value="11">History</option>
<option value="12">Scientific Paper</option>
<option value="13">Biography</option>
<option value="14">Textbook</option>
<option value="15">Travel Book</option>
<option value="16">Technical Documentation</option>
</select>
</div>
<div class="button">
<input type="submit" />
</div>
</form>
HTML::FormFu::Model::DBIC has two complementary options that affect how rows are selected: condition
and attributes
. They are passed as the first and second argument to DBIx::Class::ResultSet::search respectively. That is, if you have the following model_config specification for an element:
<model_config>
resultset SomeResultSource
<condition>
...
</condition>
<attributes>
...
</attributes>
</model_config>
Assuming that the above definition is parsed into a $model_config
hashref, HTML::FormFu::Model::DBIC will internally do something like this to fetch the rows containing the requested options:
$schema->resultset( $model_config->resultset )->search(
$model_config->condition,
$model_config->attributes,
);
Displaying and updating table fields
Now let us look at the most important part of working with models - automatically retrieving, displaying and updating data from the data store. The key link between fields in the database and fields in the form is the name attribute. Let us start with a simple book table. Here is the definition of the ResultSource class:
### Books/Result/Book.pm ###
package Books::Result::Book;
use DBIx::Class::Candy;
table 'book';
column 'id' => {
data_type => 'int',
is_auto_increment => 1,
};
column 'title' => {
data_type => 'varchar',
size => 1000,
};
column 'author' => {
data_type => 'varchar',
size => 1000,
};
primary_key 'id';
1;
The configuration file for a form to edit a book record may look like this:
<element>
name title
type Text
label "Title"
</element>
<element>
name author
type Text
label "Author"
</element>
<element>
type Submit
</element>
Now if we display the form and the user enters some values, we would need to do something like that to enter a new record in the database:
...
if ($form->submitted_and_valid) {
my $result = $schema->resultset('Book')->new_result({});
$form->model->update($result);
}
This creates a new empty record, and passes it to update, which takes care of updating the record with the submitted values and saving it into the database. Here is how to go about editing an existing record:
my $result = $schema->resultset('Book')->find({ id => $id });
$form->model->default_values($result);
The form will be displayed with data from that record filled-in. After the form is submitted, updating the result object is as simple as:
if ($form->submitted_and_valid) {
$form->model->update($result);
}
Displaying and updating fields from related tables
So far so good, but things become more complicated when a result object involves relationships between different tables. Let us expand on the topic of loading options from the database, and add updating to the mix. We will update our Books::Result::Book
class as follows, and the Books::Result::Genre
class will be identical to the one we used above:
### Books/Result/Book.pm ###
package Books::Result::Book;
use DBIx::Class::Candy;
table 'book';
column 'id' => {
data_type => 'int',
is_auto_increment => 1,
};
column 'title' => {
data_type => 'varchar',
size => 1000,
};
column 'genre_id' => {
data_type => 'int',
is_foreign_key => 1,
};
primary_key 'id';
belongs_to 'genre' => ( 'Books::Result::Genre', 'genre_id' );
1;
We will add a select field to our form definition to select a genre.
...
<element>
name genre
type Select
label Genre
<model_config>
resultset Genre
</model_config>
</element>
...
You can use either genre
, or genre_id
as field name here. In the former case HTML::FormFu
will figure out that you want to update the genre
relationship and will know that it can do that by setting genre_id
in the book
table to the desired id. In the latter case it will just update genre_id
directly - in both cases the result will be the same. It is generally cleaner to use the name of the relationship as the name attribute. For this to work, of course, we will have to use a form element that is based on HTML::FormFu::Element::_Group and allows setting of a single option only, i.e. HTML::FormFu::Element::Radiogroup or HTML::FormFu::Element::Select without the multiple attribute.
This becomes even more clear if we consider many_to_many relationships. Let us say that some time after we have implemented our database, a new requirement comes up that more than one genre may be specified for each book. To achieve that, we will need to add an intermediary table to keep track of the links between books and genres. We will have the following three result source classes:
### Books/Result/Book.pm ###
package Books::Result::Book;
use DBIx::Class::Candy;
table 'book';
column 'id' => {
data_type => 'int',
is_auto_increment => 1,
};
column 'title' => {
data_type => 'varchar',
size => 1000,
};
primary_key 'id';
has_many 'book_genres' => ( 'Books::Result::BookGenre', 'book_id' );
many_to_many 'genres' => ( 'book_genres', 'genre' );
1;
### Books/Result/Genre.pm ###
package Books::Result::Genre;
use DBIx::Class::Candy;
table 'genre';
column 'id' => {
data_type => 'int',
is_auto_increment => 1,
};
column 'name' => {
data_type => 'varchar',
size => 256,
};
column 'fiction' => {
data_type => 'boolean',
};
primary_key 'id';
has_many 'book_genres' => ( 'Books::Result::BookGenre', 'genre_id' );
many_to_many 'books' => ( 'book_genres', 'book' );
1;
### Books/Result/BookGenre.pm ###
package Books::Result::BookGenre;
use DBIx::Class::Candy;
table 'book_genre';
column 'book_id' => {
data_type => 'int',
is_foreign_key => 1,
};
column 'genre_id' => {
data_type => 'int',
is_foreign_key => 1,
};
primary_key 'book_id', 'genre_id';
belongs_to 'book' => ( 'Books::Result::Book', 'book_id' );
belongs_to 'genre' => ( 'Books::Result::Genre', 'genre_id' );
1;
The only thing we need to do to make the new relationship work is to change the type of the form element to one that can accept multiple values (i.e. HTML::FormFu::Element::Checkboxgroup or HTML::FormFu::Element::Select with the multiple attribute set):
...
<element>
name genre
type Select
label Genre
<attributes>
multiple 1
</attributes>
<model_config>
resultset Genre
</model_config>
</element>
...
Now updating the result object will correctly set genre_id
and book_id
in table book_genre
.
Directly editing fields in related tables
In the above examples we used lists of predefined options to set data related to other tables. Sometimes, however, we need to be able to edit directly data that is in a different table. The way this works in HTML::FormFu::Model::DBIC is again very straightforward - to edit fields from a different table, you need to place those fields in a block which has a nested_name property that corresponds to the respective relationship name. HTML::FormFu::Model::DBIC will recognize that these fields belong to the related table and will update them accordingly.
Let us look at some examples now. Imagine that we have an author
table in our database with information about books, and we have a separate table address
that holds the address of each author. Our schema classes will look like this:
### Books/Result/Author.pm ###
package Books::Result::Author;
use DBIx::Class::Candy;
table 'author';
column 'id' => {
data_type => 'int',
is_auto_increment => 1,
};
column 'name' => {
data_type => 'varchar',
size => 256,
};
column 'address_id' => {
data_type => 'int',
is_foreign_key => 1,
is_nullable => 1,
};
primary_key 'id';
belongs_to 'address' => ( 'Books::Result::Address', 'address_id' );
1;
### Books/Result/Address.pm ###
package Books::Result::Address;
use DBIx::Class::Candy;
table 'address';
column 'id' => {
data_type => 'int',
is_auto_increment => 1,
};
column 'address' => {
data_type => 'text',
};
primary_key 'id';
has_one 'author' => ( 'Books::Result::Author', 'address_id' );
1;
When displaying the form to edit an author, we want to be able to edit the author's address too. Here is what our configuration file should look like:
<element>
name name
type Text
label Name
</element>
<element>
type Block
nested_name address
<element>
name address
type Textarea
label Address
</element>
</element>
<element>
type Submit
</element>
When you use nested_name on a block, the value of nested_name will be prepended to the name of each field within that block. This allows HTML::FormFu::Model::DBIC to identify the fields that belong to foreign tables and update them accordingly. The above form will be rendered as follows:
<form action="" method="post">
<div class="text label">
<label>Name</label>
<input name="name" type="text" />
</div>
<div>
<div class="textarea label">
<label>Address</label>
<textarea name="address.address" cols="40" rows="20"></textarea>
</div>
</div>
<div class="submit">
<input type="submit" />
</div>
</form>
The last and most complicated example that we will look into involves has_many relationships, where we need to work with multiple rows from the related table at once. If we elaborate on the above scenario, imagine that we must modify our database to allow multiple addresses to be specified for each author. The new classes will have the following structure:
### Books/Result/Author.pm ###
package Books::Result::Author;
use DBIx::Class::Candy;
table 'author';
column 'id' => {
data_type => 'int',
is_auto_increment => 1,
};
column 'name' => {
data_type => 'varchar',
size => 256,
};
primary_key 'id';
has_many 'addresses' => ( 'Books::Result::Address', 'author_id' );
1;
### Books/Result/Address.pm ###
package Books::Result::Address;
use DBIx::Class::Candy;
table 'address';
column 'id' => {
data_type => 'int',
is_auto_increment => 1,
};
column 'address' => {
data_type => 'text',
};
column 'author_id' => {
data_type => 'int',
is_foreign_key => 1,
};
primary_key 'id';
belongs_to 'author' => ( 'Books::Result::Author', 'author_id' );
1;
The following is required in order to get this to work:
All the fields from the related table must be within a HTML::FormFu::Element::Repeatable block.
As above, the nested_name option of the Repeatable block must be set to the name of the respective relationship (
addresses
in our case).When you list the fields from the related table in the Repeatable block you must include that table's primary key as a Hidden field. Since we are working with multiple rows this is necessary to identify rows by id when updating the form.
The Repeatable block's increment_field_names must be set to true (it is true by default). We will look into the effects of this option shortly.
And last but not least, you must include a Hidden field specifying the number of rows from the related table that the form will contain, i.e. how many times the Repeatable block will be repeated. This field must be placed outside the Repeatable block, and be referenced from within the Repeatable block via the counter_name option. For more information, check the documentation on HTML::FormFu::Element::Repeatable.
Here is an example configuration file for the new form:
<element>
name name
type Text
label Name
</element>
<element>
name address_counter
type Hidden
</element>
<element>
type Repeatable
nested_name addresses
counter_name address_counter
<element>
name id
type Hidden
</element>
<element>
name address
type Textarea
label Address
</element>
</element>
<element>
type Submit
</element>
This is how this form will render:
<form method="post" action="">
<div class="text label">
<label>Name:</label>
<input type="text" name="name">
</div>
<input type="hidden" name="address_counter">
<div>
<input type="hidden" name="addresses_1.id">
<div class="textarea label">
<label>Address:</label>
<textarea rows="20" cols="40" name="addresses_1.address"></textarea>
</div>
</div>
<div class="submit">
<input type="submit">
</div>
</form>
Notice how the use of the increment_field_names option makes each field from the related table numbered:
name="addresses_1.address"
This means that this is the address
field from the first row from the table corresponding to the addresses
relationship. This address
field belongs to the row whose primary key is the value of the addresses_1.id
field - currently none since this is an empty form to create a new author. If we have a form with more than one addresses the subsequent fields will be named addresses_2.address
, addresses_3.address
, etc.
If you want to add to the has_many
relationship (i.e. add a new address row for this author), you can use the empty_rows
option of the model_config
section of the Repeatable block to display the form with the specified number of additional empty rows at the end of the block. Alternatively you can use javascript to add dynamically a new element, e.g. by copying the last element in the Repeatable block and incrementing its counter. And in order to delete an existing element you can add a checkbox with a delete_if_true
option of its model_config
section set to 1
. Those addresses with this checkbox checked will be deleted upon form submission. Alternatively you can use javascript to remove this field from the form and decrement the counters of the other elements respectively.
EXAMPLE
This manual comes with a bundled Catalyst application that shows most of the above examples in action. You can find it in the the distribution's example
directory.
AUTHOR
Peter Shangov <pshangov@yahoo.com>
COPYRIGHT AND LICENSE
This software is copyright (c) 2010 by Peter Shangov.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.