Chapter 2.1: Genelet::Model

NAME

Genelet::Model – Genelet Model Class

 

SYNOPSIS
my $dbh = DBI->connect(...);
my $ARGS = {key=>value, key=>value};
my $model = Genelet::Model->new(dbh=>$dbh, ARGS=>$ARGS);
my $extra = {key=>value};
 
my $error = $model->insert($extra);
die $error if $error;
my $lists = $model->lists();
warn Dumper $lists;

$error = $model->topics();
die $error if $error;
$lists = $model->lists();
warn Dumper $lists;

 

DESCRIPTION

This class provides the following functionalities:

  1. Unified data structures for use in Controller
  2. Built-in RESTful verbs
  3. Multiple table searches using JOINS
  4. Pagination
  5. Trigger: retrieving data from, or passing data to other models

 

CONSTRUCTOR

If you run model in a standalone program, the constructor should be like:

my $model = Genelet::Model->new(dbh=>$dbh, args=>$args);

In Genelet web project, the model is already initialized and the inputs are collected in early phases and ready to use:

  • args: input data collected from query and POST body, as a hash reference.
  • dbh: a database handler.
  • logger: optional, a Genelet::Logger object for logging purpose.
  • storage: optional, a read-only caching object in FCGI.

 

OUTPUT

The following attributes are output data. Your program should generate them. Internally, Genelet will deliver them back to Controller for displaying or API:

  • lists: the main output data, as an array reference
  • other: other data, as a hash reference

 

 

CRUD

Here is the list of attributes for database table information.

  • current_table: the table name.
  • current_key: the primary key (PK). If it consists of several fields, put them as an array reference.
  • current_id_auto: if there exists an auto increment field, put it here.

Here is the list of attributes for CRUD.

  • insert_pars: array reference, defining which fields will be inserted. Fields without input value will be ignored.
  • topics_pars: an array reference, defining which fields will be searched for multiple items.
  • edit_pars: an array reference, defining which fields will be selected for one specific item id.
  • update_pars: an array reference, defining which fields will be updated.
  • insupd_pars: an array reference, defining unique-key names. When run Genelet’s special verb insupd, you add a new row if the unique keys do not exist, you update the existing record if the unique keys exist.
  • empties: an array reference. In UPDATE, fields without input values will be ignored. The fields in this list will be forced to be set NULL in the table.
  • key_in: a hash reference like {table=>fk, table=>fk}, simulating foreign key (FK) behavior. If to delete an item using the PK and if this PK still exists in other tables as FK, Genelet will stop the deletion. This hash defines all FKs in other tables for the current PK of this table.

 

PAGINATION

Here is the list of attributes for pagination.

  • sortby: when SELECT, you may use a field to sort the output. This is the variable name you use to pass the sorting field in query. The default is sortby.
  • sortreverse: used together with sortby, to specify if the data should be reversely sorted. For example, if you have defined it myreverse, then you should pass myreverse=1 for reverse sorting. The default is sortreverse.
  • fields: You can specify a subset of topics_pars or edit_pars on-the-fly. Use this variable to pass it in query. The default is fields.
  • rowcount: when SELECT, passing rowcount will trigger pagination. This is the variable to pass rows per page. For example, you may define it eachpage and pass eachpage=50 in query. The default is rowcount.
  • pageno: in pagination, use it to pass the page number. The default is pageno.
  • totalno: variable name for the total rows. For example, if you define it how_many, then you can pass how_many=1234 in query. In some API, it will be calculated and returned as {“how_many”: 1234}. The default is totalno.
  • maxpageno: Same as totalno, but for the total pages.
  • total_force: default to 0. If it is 1 and pageno=1, totaln0 and maxpageno will be calculated and returned in API.

 

CRUD METHODS
insert
my $err = $self->insert(extra);

Use this method to insert a new row into table, following the definition of $self->{INSERT_PARS} and the input $self->{ARGS}.  The optional argument extra, if exists, will override the key-values pairs in $self->{ARGS}.

If successful, no error will be returned and the inserted row will be put as the first element in $self->{LISTS}. If failed, an error will be returned and the error message will be display in HTML or API.

 

topics
my $err = $self->insert(extra);

Use this method to search all records in the table. The row is saved as the first element in $self->{LISTS}. An error will be returned if something goes wrong.

Optional extra lets you to place extra search conditions. See JOIN and pagination for more sophisticated search conditions.

 

search conditions

Normally, each key-value pair in extra represents a field and its value. In case the value is an array reference, it will be interpreted as an IN condition in WHERE. For example, {firstname => [“John”, “Mary”]} will be interpreted as WHERE firstname IN [“John”, “Mary”].

In case the value is a hash reference, it will be interpreted as operator-value pairs with AND logic in WHERE . For example, {a => { “>” => 0, “<=” => 5}} will be interpreted as WHERE (a>0 AND a<=5).

The special key _gsql will be interpreted as a raw SQL statement. For example, {_gsql => ‘firstname = “John” AND lastname != “Smith”‘} will be interpreted as WHERE firstname=”John” AND lastname != “Smith”.

 

edit
my $err = $self->edit(extra);

Use this method to search one record by current_key. extra lets you to place extra search conditions. For any login role of none admin type, visitor’s login ID is always enforced as a condition. The result is saved as the first element in $self->{LISTS}.

 

update
my $err = $self->update(extra);

Use this method to update a row. extra lets you to place extra update conditions. The updated row is saved as the first element in $self->{LISTS}.

 

insupd
my $err = $self->insupd(extra);

Use this method to insert a row. If the unique key or key combination $self->{CURRENT_INSUPD} is already existing, the old row will be updated instead of inserted. The new or updated row is saved as the first element in $self->{LISTS}. The optional argument, extra will override the values in $self->{ARGS}.

 

delete
my $err = $self->delete(extra);

Use this method to delete a row by the PK. extra lets you place deletion’s conditions. If the PK exists in other tables indicated in $self->{KEY_IN}, the operation will stop and nothing would be changed.

 

METHODS
existing
my $err = $self->existing(field, value, table);

It checks if field of value exists in table. Return nothing if the statement is true, or 1075 if it is wrong.

 

randomid
my $err = $self->randomid(max, trials, field, table);

It generates a random id for field in table, not exceeding max. If after trials times it still could not get one (because all random ids in trials are already existing in the table), it will give up and return 1076.

To restrict the generated random id between a and b, pass [a,b] as the max.

 

METHODS ON OTHER MODEL
call_once
my $err = $self->call_once(page, extra);

This method runs an action on another model, defined by

page = { model=>STRING, action=>STRING }

The other model will be constructed using the same input, logger and database handler as those of the current model. There is an optional extra whose meaning is defined as above.

If successful, the output data from the other model will be saved as the value to key model_action in this model’s $self->{OTHER}. If failed, the error will be returned.

 

call_nextpage
my $err = $self->call_nextpage(page, extra);

This method runs action on the other model for many items in $self->{LISTS}. The other model will be constructed using the same input, logger and database handler as those of the current model. There is an optional extra whose meaning is defined as above.

To run action on the other modelpage is defined as

page = {
  model       => STRING,
  action      => STRING,  
  relate_item => {this_field=>other_field, this_field=>other_field, ...},
  manual      => {field=>value, field=>value, ...}
}

Here relate_item links this_field in $self->{LISTS} to the corresponding other_field in the other model. The value of this_field will be used as a search or insert condition.

The manual map allows you to assign manually key-value pairs as search condition.

For example, you have derived customers from topics. You’d like to see each customer’s purchasing history that is handled by another model. So you run call_nextpage in this model, using the other model name and action topics, which will search each customer’s history.

Another example: you just had a new registration to your customer table with insert. You’d like to add the new customer to a sponsorship table, which tracks who has referred the customer.

If successful, the result will be saved as an new key-value pair with key model_action in $self->{LISTS}. For example, if you have a list of customers:

$self->{LISTS} = [
  {customer_id=>1111, name=>"John Kerr", ...},
  {customer_id=>2222, name=>"Peter Pan", ...},
  ...
]

You want to get each customer’s purchase history. You may call:

my $err = $self->call_nextpage({model=>"Project::Purchase::Model", action=>"topics", relate_item=>{customer_id=>"customer_id"}}, {status_id=>1});

will update $self->{LISTS} to have new key Project_Purchase_Model_topics.

$self->{LISTS} = [
  {customer_id=>1111, Project_Purchase_Model_topics=>[...], name=>"John Kerr", ...},
  {customer_id=>2222, Project_Purchase_Model_topics=>[...], name=>"Peter Pan", ...},
  ...
]

Genelet uses call_once and call_nextpage in Triggers.