NAME
Model - simple ORM based on a mix of iBATIS and ActiveRecord
SYNOPSIS
package Models::Service;
use Model 'services';
our @ISA = qw(Pinwheel::Model::Base);
BEGIN {
belongs_to 'parent', package => 'Models::Service';
has_many 'broadcasts';
query 'find_by_directory';
}
our %sql = (
find_by_directory => q{
SELECT * FROM services WHERE directory=?
},
);
DESCRIPTION
Model uses simple schema conventions (adopted from ActiveRecord) to provide lightweight object wrappers around database tables. It deliberately avoids trying to generate SQL statements (with the exception of "find by id").
Each table is represented by a class under Models:: and inherits from Pinwheel::Model::Base. The table name is supplied by the use statement, and relations and query functions/methods are declared with one of belongs_to, has_one, has_many, and query.
All database access is performed via the Database module (which uses DBI). Only mysql data sources are supported.
CONVENTIONS
This module works best with a database schema that uses these ActiveRecord-derived naming conventions:
- Table names
-
Use plural nouns, eg people and contracts, and separate words with underscores, eg line_items.
- Keys
-
Each table with a model class should have a primary key called id.
Foreign keys should use a clean, descriptive name followed by _id. For example, a singular version of the foreign table name such as contract_id or line_item_id, or a description of the relationship, such as parent_pip_id or child_pip_id.
- Column names
-
Avoid putting the table name or a data type in column names, eg customers.name rather than customers.customer_name, and created_at rather than created_date.
RELATIONSHIPS
- belongs_to
-
Declare a one-to-one or many-to-one relationship where the foreign key is in the table containing the
belongs_to. For example:package Models::Broadcast; ... belongs_to 'service';This states that the broadcasts table contains a service_id column referencing the services table. Each instance of
Models::Broadcastwill have aservicemethod which returns the linkedModels::Serviceobject. - has_one
-
Declare a one-to-one or many-to-one relationship where the foreign key is in a different table. For example:
package Models::Episode; ... has_one 'brand';With the above, each instance of
Models::Episodewill have abrandmethod which returns the linkedModels::Brandobject. - has_many
-
Declare a many-to-one relationship. For example:
package Models::Brand; ... has_many 'episodes';With the above, each instance of
Models::Brandwill have anepisodesmethod which returns a list of linkedModels::Episodeobjects.
Each of the relation functions takes three named arguments, package, finder and key:
package-
The package name of the class at the other end of the relation. When omitted, the relation name is changed to the singular (by removing 's' from the end except when it ends with 'ies'), converted to a MixedCaseName, and prefixed with Models::. For example,
belongs_to 'service'generates apackagevalue ofModels::Service.In the following, the
packagevalue is the same as the default:belongs_to 'service', package => 'Models::Service'; has_one 'brand', package => 'Models::Brand'; has_many 'series', package => 'Models::Series'; finder-
The name of the query function to call in
packageto retrieve the object. For abelongs_tothis defaults tofind. For ahas_manythis defaults tofind_all_by_followed by the singular version of the table name, egfind_all_by_service. And for ahas_onethis defaults tofind_by_followed by the singular version of the table name, egfind_by_broadcast.In the following, the
findervalue is the same as the default:belongs_to 'service', finder => 'find'; has_one 'brand', finder => 'find_by_episode'; has_many 'series', finder => 'find_all_by_series'; key-
The attribute to pass to the
finderfunction. Forhas_oneandhas_manyrelations this isid. Forbelongs_toit is the relation name followed by_id.In the following, the
keyvalue is the same as the default:belongs_to 'service', key => 'service_id'; has_one 'brand', key => 'id'; has_many 'series', key => 'id';
QUERIES
The query function makes SQL from the package's %sql hash callable as a class or instance method, with parameters passed on as bind variables (model objects parameters are converted to keys via their id method). For example:
package Models::Service;
...
query 'find_by_directory';
our %sql = (
find_by_directory => q{
SELECT * FROM services WHERE directory=?
},
);
...
$service = Models::Service->find_by_directory('radio1');
This would execute the following SQL and return an instance of the Models::Service class.
SELECT * FROM services WHERE directory='radio1'
Query Options
query also allows additional options to be passed:
query 'name_of_query', %opts;
The following options are recognised:
- type
-
The type of value returned by the query can be varied with the
typeoption, which must have one of the following values:--
Fetch a single row and return it wrapped as an instance of this model class.
[-]-
Fetch all the available rows and return a list of model objects.
1-
Fetch a single row and return just the first column as a scalar.
[1]-
Fetch all the rows and return a list containing just the first column from each as a scalar.
x-
No return value.
The default is
1if the query name begins with "count",-if it begins with "find" (but not "find_all"),xif it begins with any of: set, add, remove, create, replace, update, delete; or[-]otherwise.Some examples:
# Return the number of rows query 'count', type => '1'; # Return a list of the first column from each result row query 'scheduled_days', type => '[1]'; # Return a single row, wrapped as a model object query 'find_by_directory', type => '-'; # Return a list of model objects query 'find_all_by_series', type => '[-]'; - fn
-
The
fnparameter provides a function to convert the provided arguments into a list of bind variables, and optionally also to declare which (if any) of the model relations will be in the result set. The function is called in list context with the provided arguments, including the leading class or object. The function should return a list of bind variables, optionally preceded by an array reference indicating the list of relations to be filled in from the result set. - postfn
-
TODO, document me.
- include
-
TODO, document me.
METHODS
Columns are automatically exposed as methods on a model object, eg:
$brand = Models::Brand->find(1);
print $brand->name . "\n";
Model classes also gain the following methods (which also happen to work as object methods):
- $foo = Models::Foo->find($id)
-
Return the row identified by the supplied primary key.
- @foos = @{ Models::Foo->find_all }
-
Return all the rows in the table.
- $foo = @foos = @{ Models::Foo->find_all_by_COLUMN($value) }
-
Return all rows where the given COLUMN matches the value.
- $foo = Models::Foo->find_by_COLUMN($value)
-
Return the row where the given COLUMN matches the value.
See Pinwheel::Model::Base for additional methods gained by model objects.
BUGS
TODO, document the following: sql_param, hash refs as query parameters, the ?$...$ syntax, prefetch, inheritance key/value, how 'describe' is used at import time, wrapping of dates and times, caching. Plus anything marked as "TODO" above.
import should make use of Exporter, so the caller can avoid importing query etc. if they so wish.
The query type values ("-", "[-]", etc) should probably be made available as constants (e.g. QUERY_RETURN_ONE_MODEL, QUERY_RETURN_MANY_MODELS, etc).
AUTHOR
A&M Network Publishing <DLAMNetPub@bbc.co.uk>