| Rose::DB::Object::Loader(3pm) | User Contributed Perl Documentation | Rose::DB::Object::Loader(3pm) |
Rose::DB::Object::Loader - Automatically create Rose::DB::Object subclasses based on database table definitions.
Sample database schema:
CREATE TABLE vendors
(
id SERIAL NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
UNIQUE(name)
);
CREATE TABLE products
(
id SERIAL NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
vendor_id INT REFERENCES vendors (id),
status VARCHAR(128) NOT NULL DEFAULT 'inactive'
CHECK(status IN ('inactive', 'active', 'defunct')),
date_created TIMESTAMP NOT NULL DEFAULT NOW(),
release_date TIMESTAMP,
UNIQUE(name)
);
CREATE TABLE prices
(
id SERIAL NOT NULL PRIMARY KEY,
product_id INT NOT NULL REFERENCES products (id),
region CHAR(2) NOT NULL DEFAULT 'US',
price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
UNIQUE(product_id, region)
);
CREATE TABLE colors
(
id SERIAL NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
UNIQUE(name)
);
CREATE TABLE product_color_map
(
product_id INT NOT NULL REFERENCES products (id),
color_id INT NOT NULL REFERENCES colors (id),
PRIMARY KEY(product_id, color_id)
);
To start, make a Rose::DB::Object::Loader object, specifying the database connection information and an optional class name prefix.
$loader =
Rose::DB::Object::Loader->new(
db_dsn => 'dbi:Pg:dbname=mydb;host=localhost',
db_username => 'someuser',
db_password => 'mysecret',
db_options => { AutoCommit => 1, ChopBlanks => 1 },
class_prefix => 'My::Corp');
It's even easier to specify the database information if you've set up Rose::DB (say, by following the instructions in Rose::DB::Tutorial). Just pass a Rose::DB-derived object pointing to the database you're interested in.
$loader =
Rose::DB::Object::Loader->new(
db => My::Corp::DB->new('main'),
class_prefix => 'My::Corp');
Finally, automatically create Rose::DB::Object subclasses for all the tables in the database. All it takes is one method call.
$loader->make_classes;
Here's what you get for your effort.
$p = My::Corp::Product->new(name => 'Sled');
$p->vendor(name => 'Acme');
$p->prices({ price => 1.23, region => 'US' },
{ price => 4.56, region => 'UK' });
$p->colors({ name => 'red' },
{ name => 'green' });
$p->save;
$products =
My::Corp::Product::Manager->get_products_iterator(
query => [ name => { like => '%le%' } ],
with_objects => [ 'prices' ],
require_objects => [ 'vendor' ],
sort_by => 'vendor.name');
$p = $products->next;
print $p->vendor->name; # Acme
# US: 1.23, UK: 4.56
print join(', ', map { $_->region . ': ' . $_->price } $p->prices);
See the Rose::DB::Object and Rose::DB::Object::Manager documentation for learn more about the features these classes provide.
The contents of the database now look like this.
mydb=# select * from products;
id | name | price | vendor_id | status | date_created
----+--------+-------+-----------+----------+-------------------------
1 | Sled 3 | 0.00 | 1 | inactive | 2005-11-19 22:09:20.7988
mydb=# select * from vendors;
id | name
----+--------
1 | Acme 3
mydb=# select * from prices;
id | product_id | region | price
----+------------+--------+-------
1 | 1 | US | 1.23
2 | 1 | UK | 4.56
mydb=# select * from colors;
id | name
----+-------
1 | red
2 | green
mydb=# select * from product_color_map;
product_id | color_id
------------+----------
1 | 1
1 | 2
Rose::DB::Object::Loader will automatically create Rose::DB::Object subclasses for all the tables in a database. It will configure column data types, default values, primary keys, unique keys, and foreign keys. It can also discover and set up inter-table relationships. It uses Rose::DB::Object's auto-initialization capabilities to do all of this.
To do its work, the loader needs to know how to connect to the database. This information can be provided in several ways. The recommended practice is to set up Rose::DB according to the instructions in the Rose::DB::Tutorial, and then pass a Rose::DB-derived object or class name to the loader. The loader will also accept traditional DBI-style connection information: DSN, username, password, etc.
Once the loader object is configured, the make_classes method does all the work. It takes a few options specifying which tables to make classes for, whether or not to make manager classes for each table, and a few other options. The convention manager is used to convert table names to class names, generate foreign key and relationship method names, and so on. The result of this process is a suite of Rose::DB::Object (and Rose::DB::Object::Manager) subclasses ready for use.
Rose::DB::Object::Loader inherits from, and follows the conventions of, Rose::Object. See the Rose::Object documentation for more information.
Database schema information is extracted using DBI's schema interrogation methods, which dutifully report exactly how the database describes itself. In some cases, what the database reports about a particular table may not exactly match what you specified in your table definition.
The most egregious offender is (surprise!) MySQL, which, to give just one example, tends to offer up empty string default values for non-null character columns. That is, if you write a table definition like this:
CREATE TABLE widgets
(
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(64) NOT NULL
);
and then interrogate it using DBI, you will find that the "name" column has a default value (as reflected in the "COLUMN_DEF" column returned by DBI's column_info() method) of '' (i.e., an empty string). In other words, it's as if your table definition was this instead:
CREATE TABLE widgets
(
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(64) NOT NULL DEFAULT ''
);
MySQL is full of such surprises, and it's not the only database to do such things. Consult the documentation for your database (or do a Google search for "<mydbname> gotchas") for the gory details.
To work around these kinds of problems, try the pre_init_hook feature. For example, in your pre_init_hook subroutine you could walk over the list of columns for each class, eliminating all the empty string default values (i.e., changing them to undef instead).
Returns a list (in list context) or reference to an array (in scalar context) of base class names. Defaults to a dynamically-generated Rose::DB::Object subclass name.
Unless this attribute is explicitly set or fetched before the call to the make_classes method, the convention manager object used by make_classes will be produced by calling the convention_manager method of the metadata object of the first (left-most) base class.
Setting this attribute also sets the db_class attributes, overwriting any previously existing value, and sets the db_dsn value to undef.
Setting this attribute sets the db attribute to undef unless its class is the same as CLASS.
Setting this attribute immediately sets the dsn of the db attribute, if it is defined.
Table names are compared to REGEX and the names in ARRAYREF in a case-insensitive manner. To override this in the case of the REGEX, add "(?-i)" to the front of the REGEX. Otherwise, use the filter_tables method instead.
This attribute should not be combined with the exclude_tables or include_tables attributes.
Table names are compared to REGEX and the names in ARRAYREF in a case-insensitive manner. To override this in the case of the REGEX, add "(?-i)" to the front of the REGEX. Otherwise, use the filter_tables method instead.
Table names are compared to REGEX and the names in ARRAYREF in a case-insensitive manner. To override this in the case of the REGEX, add "(?-i)" to the front of the REGEX. Otherwise, use the "filter_tables" parameter instead.
Table names are compared to REGEX and the names in ARRAYREF in a case-insensitive manner. To override this in the case of the REGEX, add "(?-i)" to the front of the REGEX. Otherwise, use the "filter_tables" parameter instead.
Defaults to the value of the loader object's filter_tables attribute, provided that both the "exclude_tables" and "include_tables" values are undefined. Tables without primary keys are automatically skipped.
If this parameter is omitted and if the loader object's force_lowercase attribute is not defined, then the value is chosen based on the database currently being examined. If the database is Oracle, then it defaults to true. Otherwise, it defaults to false.
The final value is propagated to the convention manager attribute of the same name.
If "require_primary_key" is false and the loader object's warn_on_missing_primary_key attribute is undefined, or if the "warn_on_missing_primary_key" parameter is set to an undefined value or is not passed to the make_classes call at all, then "warn_on_missing_primary_key" is set to false. Otherwise, it defaults to the value of the loader object's warn_on_missing_primary_key attribute. Note that a Rose::DB::Object-derived class based on a table with no primary key will not function correctly in all circumstances.
These complicated defaults are intended to honor the intentions of the "require_primary_key" attribute/parameter. If not requiring primary keys and no explicit decision has been made about whether to warn about missing primary keys, either in the parameters to the make_classes call or in the loader object itself, then we don't warn about missing primary keys. The idea is that not requiring primary keys is a strong indication that their absence is not worth a warning.
The manager class name is determined by passing the Rose::DB::Object-derived class name to the generate_manager_class_name method.
The Rose::DB::Object subclass's metadata object's make_manager_class method will be used to create the manager class. It will be passed the return value of the convention manager's auto_manager_base_name method as an argument.
Any remaining name/value parameters will be passed on to the call to auto_initialize used to set up each class. For example, to ask the loader not to create any relationships, pass the "with_relationships" parameter with a false value.
$loader->make_classes(with_relationships => 0);
This parameter will be passed on to the auto_initialize method, which, in turn, will pass the parameter on to its own call to the auto_init_relationships method. See the Rose::DB::Object::Metadata documentation for more information on these methods.
Each Rose::DB::Object subclass will be created according to the "best practices" described in the Rose::DB::Object::Tutorial. If a base class is not provided, one (with a dynamically generated name) will be created automatically. The same goes for the db object. If one is not set, then a new (again, dynamically named) subclass of Rose::DB, with its own private data source registry, will be created automatically.
This method returns a list (in list context) or a reference to an array (in scalar context) of the names of all the classes that were created. (This list will include manager class names as well, if any were created.)
This method calls make_classes to make the actual classes.
Note: If you are trying to regenerate a set of module files that already exist in the target "module_dir", please make sure that this "module_dir" is not in your @INC path. (That is, make sure it is not in the set of paths that perl will search when looking for module files in response to a "use" or "require" statement.) More generally, you must make sure that existing versions of the modules you are attempting to generate are not in your @INC path.
(If you do not do this, when make_classes makes a class and looks for a related class, it will find and load the previously generated ".pm" file, which will then cause make_classes to skip that class later when it sees that it already exists in memory. And if make_classes skips it, make_modules will never see it and therefore will never regenerate the ".pm" file.)
This method takes all of the same parameters as make_classes, with several additions:
Defaults to the value of the loader object's module_dir attribute. If the module_dir attribute is also undefined, then the current working directory (as determined by a call to cwd()) is used instead.
Defaults to the value of the loader object's module_preamble attribute.
Defaults to the value of the loader object's module_postamble attribute.
Note that a Rose::DB::Object-derived class based on a table with no primary key will not function correctly in all circumstances. Use this feature at your own risk.
The manager class name is determined by passing the Rose::DB::Object-derived class name to the generate_manager_class_name method.
The Rose::DB::Object subclass's metadata object's make_manager_class method will be used to create the manager class. It will be passed the return value of the convention manager's auto_manager_base_name method as an argument.
Returns a list (in list context) or reference to an array (in scalar context) of base class names. Defaults to Rose::DB::Object::Manager.
John C. Siracusa (siracusa@gmail.com)
Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| 2022-10-14 | perl v5.36.0 |