view release on metacpan or search on metacpan
* Non-persistent columns feature added.
* Made "where" an alias for the Manager's "query" parameter.
(Requested by Ask Bjørn Hansen)
* Documented restrictions on the Manager's "select" parameter.
* Fixed bug that prevented CURRENT_TIMESTAMP from being properly
inlined in queries sent to SQLite. (RT 37224)
* Fixed a memory leak. (Reported by Christopher Laco)
* The "cluck" error mode now correctly calls cluck() rather than
croak(). (Reported by Kevin McGrath)
* Added support for Oracle date/time column keywords.
* Cascaded delete now properly cascades to one-to-one related objects.
(Reported by kittens)
0.770 (05.28.2008) - John Siracusa <siracusa@gmail.com>
* Added "iterator" method type, similar to "find", to OneToMany
and ManyToMany. (Patch by Peter Karman - peknet@gmail.com)
* Updated the Loader documentation to describe an important
consideration when regenerating modules with make_modules().
* Improved error propagation in relationship methods. (Suggested
by Wiggins d'Anconia)
* Added the with_column_triggers attribute to foreign keys and singular
relationships in order to keep columns and related objects in sync.
* Fixed a bug that caused some optional related objects to be improperly
transformed into required objects. (Reported by Ethan Rowe)
* Improved detection of errors when auto-loading related classes.
* Duplicate auto-created map record method names are now detected and
reported as a fatal error.
* Added and documented a return value for add_columns().
* Added module_preamble and module_postamble features to the Loader's
make_modules() method. (Patch by David Christensen)
* Made changes_only, cascade, and prepare_cached arguments to save()
also apply to *_on_save collections.
* Added test and prerequisite version for a Rose::DB bug that
prevented certain reserved words from being detected as primary
key columns in PostgreSQL. (Reported by Fred Cox)
* Baseline Oracle support added to the Loader. (Patch by Teodor Zlatanov)
* The clone() and clone_and_reset() methods now handle missing or
differently named accessor/mutator methods.
* QueryBuilder now supports eq/ne undef for is/is not null comparisons.
* Foreign key columns that are also primary key columns are no longer set
to undef when a foreign object is set to undef. (Reported by Ovid)
* Added a "find" method type to one-to-many relationships for
ad-hoc queries.
* Added support for Informix's "datetime year to month" column type.
* Updated the dbh() method to be a more conventional proxy for ->db->dbh().
* The get_objects() and delete_objects() Manager methods now accept a lone
arrayref or hashref argument as a short way to specify the value of the
"query" parameter.
* Eliminated warning in the BigNum column type when the GMP math library
is not installed.
* Added a double precision column type and class for PostgreSQL.
* Fixed a bug that caused cascaded save() to fail to cascade beyond
a set-on-save related object.
* Improved reporting of errors in auto-loaded related modules.
* Fixed a bug that caused numeric columns to have invalid length
restrictions. (Reported by Fred Cox)
* Fixed many incorrect skip counts in the test suite when running against
PostgreSQL without CHKPASS support.
0.760 (01.16.2007) - John Siracusa <siracusa@gmail.com>
* Fixed a mistake in the test suite that caused spurious failures
foreign_key_name_generator() method and the convention manager's
auto_foreign_key_name() method to avoid some name conflict bugs
and create a more sensible flow for foreign key naming.
(Suggested by Graham Barr)
* Added has_modified_children() has_loaded_related() methods to
Rose::DB::Object::Util.
* Added an init_with_column_value_pairs() helper method. (Requested
by Jonathan Vanasco)
* Modified child objects are now correctly detected and handled by
cascading save(). (Reported by Lucian Dragus)
* Fixed a bug that caused save(changes_only => 1, cascade => 1) to
fail in cases where a child object set a key column in the parent
object. (Reported by Lucian Dragus)
* Fixed a bug in the Manager that caused the with_objects parameter
to be ignored when the count_only parameter was set. (Reported
by Uwe Voelker)
* The column_values_as_*() helper methods no longer require the
column_value_pairs() helper to also be imported. (Reported
by Jonathan Vanasco)
* Fixed a bug caused by blank lines in JSON and YAML output.
(Patch by Jonathan Vanasco)
* The Loader's include_tables and exclude_tables attributes now accept
references to arrays of table names as well as regular expressions.
* The Loader's include_tables and exclude_tables attributes are now
case-insensitive by default.
* Fixed a bug that caused save() with sub-objects to fail in some cases.
(Reported by Wiggins d'Anconia)
* Added examples of the add_on_save relationship methods to the tutorial.
0.75 (08.10.2006) - John Siracusa <siracusa@gmail.com>
* Added a cascade option to save().
* Added auto-detection of one-to-one relationships to the Loader.
* The object_class parameter to Manager methods now defaults to the
return value of the object_class() class method.
* The soft() and referential_integrity() methods of the ManyToOne and
OneToOne relationship classes have been renamed to optional() and
required(), respectively. The old method names still work, but may
be removed at some later date. Also, the default values are now
determined by a new set of rules, rather than a constant.
* Passing invalid query parameters to Manager methods will now
cause a fatal error.
* Fixed a bug that caused false boolean values to be ignored when an
object was loaded, modified, and then saved. (Patch by Cees Hek)
0.726 (05.17.2006) - John Siracusa <siracusa@gmail.com>
* Fixed incorrect skip count in t/spot-check-07.t
0.725 (05.17.2006) - John Siracusa <siracusa@gmail.com>
* Fixed a bug that caused self-referential many-to-many relationships to
fail during cascaded save() operations. (Reported by Michael Drozdov)
* The test suite now requires DBD::SQLite version 1.11 or later.
* Modified auto-init system to account for custom FetchHashKeyName
DBI settings.
0.724 (05.11.2006) - John Siracusa <siracusa@gmail.com>
* Added the replace_column() Metadata method.
* The add_now and add_on_save relationship methods now return
the number of items added when called in scalar context and
the list of items added when called in list context.
* Fixed a bug in QueryBuilder that prevented the ability to check
for null columns.
* Added the ability to query columns that are not SELECTed.
0.076 (10.05.2005) - John Siracusa <siracusa@gmail.com>
* Fixed a bug that caused incorrect counts in get_objects_count()
when using the require_object parameter with "... to many"
relationships.
* Added bulk update and delete methods to Manager.
* Added cascaded delete, plus a plea in the documentation for users
to do this in the database instead.
* Added "many to one" relationship and made it the new default
relationship type for foreign keys.
* Added *_now and *_on_save method types for foreign keys and
"... to one" relationships.
* Made get_set_on_save and delete_on_save the default auto method
types for foreign keys and "... to one" relationships.
* load() now returns the object itself on success, which allows
for the convenient $obj = MyObject->new(id => 123)->load;
* save() now returns the object itself on success, which allows
t/make-modules.t
t/map-record-name-conflict.pl
t/multi-many-the-hard-way.t
t/multi-pk-sequences.t
t/nested-joins.t
t/one-to-many-reset.t
t/pk-fk-columns.t
t/pod.t
t/query-builder.t
t/rt-cpan-45836.t
t/save-cascade.t
t/sandbox/code-gen/generated-perl-test.pl
t/sandbox/code-gen/lib/.placeholder
t/sandbox/code-gen/make-modules.pl
t/sandbox/convention/convention-test-auto.pl
t/sandbox/convention/convention-test-loader.pl
t/sandbox/convention/convention-test.pl
t/sandbox/convention/lib/My/Auto/Color.pm
t/sandbox/convention/lib/My/Auto/Price.pm
t/sandbox/convention/lib/My/Auto/Product.pm
t/sandbox/convention/lib/My/Auto/ProductColors.pm
lib/Rose/DB/Object.pm view on Meta::CPAN
$self->{MODIFIED_COLUMNS()} = {};
return $self || 1;
}
sub save
{
my($self, %args) = @_;
my $meta = $self->meta;
my $cascade =
exists $args{'cascade'} ? $args{'cascade'} :
$meta->default_cascade_save;
# Keep trigger-encumbered and cascade code in separate code path
if($self->{ON_SAVE_ATTR_NAME()} || $cascade)
{
my $db = $args{'db'} || $self->db || return 0;
my $ret = $db->begin_work;
$args{'db'} ||= $db;
unless($ret)
{
my $error = $db->error;
$self->error(ref $error ? $error : "Could not begin transaction before saving - $error");
lib/Rose/DB/Object.pm view on Meta::CPAN
TRY:
{
local $@;
eval
{
my %did_set;
my %code_args =
map { ($_ => $args{$_}) } grep { exists $args{$_} }
qw(changes_only prepare_cached cascade);
#
# Do pre-save stuff
#
my $todo = $self->{ON_SAVE_ATTR_NAME()}{'pre'};
foreach my $fk_name (keys %{$todo->{'fk'}})
{
my $code = $todo->{'fk'}{$fk_name}{'set'} or next;
lib/Rose/DB/Object.pm view on Meta::CPAN
my $code = $item->{'code'};
my $object = $item->{'object'};
# Don't run the code to delete this object if we just set it above
next if($did_set{'fk'}{$fk_name}{Rose::DB::Object::Util::row_id($object)});
$code->($self, \%code_args) or die $self->error;
}
}
if($cascade)
{
foreach my $fk ($meta->foreign_keys)
{
# If this object was just set above, just save changes (there
# should be none) as a way to continue the cascade
local $args{'changes_only'} = 1 if($todo->{'fk'}{$fk->name}{'set'});
my $foreign_object = $fk->object_has_foreign_object($self) || next;
if(Rose::DB::Object::Util::has_modified_columns($foreign_object) ||
Rose::DB::Object::Util::has_modified_children($foreign_object))
{
$Debug && warn "$self - save foreign ", $fk->name, " - $foreign_object\n";
$foreign_object->save(%args);
}
lib/Rose/DB/Object.pm view on Meta::CPAN
$code->($self, \%code_args) or die $self->error;
}
# Add value(s)
if($code = $todo->{'rel'}{$rel_name}{'add'}{'code'})
{
$code->($self, \%code_args) or die $self->error;
}
}
if($cascade)
{
foreach my $rel ($meta->relationships)
{
# If this object was just set above, just save changes (there
# should be none) as a way to continue the cascade
local $args{'changes_only'} = 1 if($todo->{'rel'}{$rel->name}{'set'});
my $related_objects = $rel->object_has_related_objects($self) || next;
foreach my $related_object (@$related_objects)
{
if(Rose::DB::Object::Util::has_modified_columns($related_object) ||
Rose::DB::Object::Util::has_modified_children($related_object))
{
$Debug && warn "$self - save related ", $rel->name, " - $related_object\n";
lib/Rose/DB/Object.pm view on Meta::CPAN
my @pk_values = grep { defined } map { $self->$_() } @pk_methods;
unless(@pk_values == @pk_methods)
{
$self->error("Cannot delete " . ref($self) . " without a primary key (" .
join(', ', @pk_methods) . ')');
$self->meta->handle_error($self);
return 0;
}
# Totally separate code path for cascaded delete
if(my $cascade = $args{'cascade'})
{
unless(exists $CASCADE_VALUES{$cascade})
{
Carp::croak "Illegal value for 'cascade' parameter: '$cascade'. ",
"Valid values are 'delete', 'null', and '1'";
}
$cascade = $CASCADE_VALUES{$cascade};
my $mgr_error_mode = Rose::DB::Object::Manager->error_mode;
my($db, $started_new_tx, $error);
TRY:
{
local $@;
eval
{
$db = $self->db;
my $meta = $self->meta;
my $ret = $db->begin_work;
unless(defined $ret)
{
die 'Could not begin transaction before deleting with cascade - ',
$db->error;
}
$started_new_tx = ($ret == IN_TRANSACTION) ? 0 : 1;
unless($self->{STATE_IN_DB()})
{
$self->load
or die "Could not load in preparation for cascading delete: ",
$self->error;
lib/Rose/DB/Object.pm view on Meta::CPAN
my $method = $meta->column_accessor_method_name($local_column);
my $value = $self->$method();
# XXX: Comment this out to allow null keys
next REL unless(defined $value);
push(@query, $foreign_column => $value);
}
if($cascade eq 'delete')
{
Rose::DB::Object::Manager->delete_objects(
db => $db,
object_class => $relationship->class,
where => \@query);
}
elsif($cascade eq 'null')
{
my %set = map { $_ => undef } values(%$column_map);
Rose::DB::Object::Manager->update_objects(
db => $db,
object_class => $relationship->class,
set => \%set,
where => \@query);
}
else { Carp::confess "Illegal cascade value '$cascade' snuck through" }
}
elsif($rel_type eq 'many to many')
{
my $map_class = $relationship->map_class;
my $map_from = $relationship->map_from;
my $map_from_relationship =
$map_class->meta->foreign_key($map_from) ||
$map_class->meta->relationship($map_from) ||
Carp::confess "No foreign key or 'many to one' relationship ",
lib/Rose/DB/Object.pm view on Meta::CPAN
my $method = $meta->column_accessor_method_name($foreign_column);
my $value = $self->$method();
# XXX: Comment this out to allow null keys
next REL unless(defined $value);
push(@query, $local_column => $value);
}
if($cascade eq 'delete')
{
Rose::DB::Object::Manager->delete_objects(
db => $db,
object_class => $map_class,
where => \@query);
}
elsif($cascade eq 'null')
{
my %set = map { $_ => undef } keys(%$key_columns);
Rose::DB::Object::Manager->update_objects(
db => $db,
object_class => $map_class,
set => \%set,
where => \@query);
}
else { Carp::confess "Illegal cascade value '$cascade' snuck through" }
}
elsif($rel_type eq 'one to one')
{
push(@one_to_one_rels, $relationship);
}
}
# Delete the object itself
my $dbh = $db->dbh or die "Could not get dbh: ", $self->error;
#local $self->{STATE_SAVING()} = 1;
lib/Rose/DB/Object.pm view on Meta::CPAN
my $method = $meta->column_accessor_method_name($local_column);
my $value = $self->$method();
# XXX: Comment this out to allow null keys
next FK unless(defined $value);
push(@query, $foreign_column => $value);
}
if($cascade eq 'delete')
{
Rose::DB::Object::Manager->delete_objects(
db => $db,
object_class => $fk->class,
where => \@query);
}
elsif($cascade eq 'null')
{
my %set = map { $_ => undef } values(%$key_columns);
Rose::DB::Object::Manager->update_objects(
db => $db,
object_class => $fk->class,
set => \%set,
where => \@query);
}
else { Carp::confess "Illegal cascade value '$cascade' snuck through" }
}
# Process all the rows for each "one to one" relationship
REL: foreach my $relationship (@one_to_one_rels)
{
my $column_map = $relationship->column_map;
my @query;
foreach my $local_column (keys %$column_map)
{
lib/Rose/DB/Object.pm view on Meta::CPAN
my $method = $meta->column_accessor_method_name($local_column);
my $value = $self->$method();
# XXX: Comment this out to allow null keys
next REL unless(defined $value);
push(@query, $foreign_column => $value);
}
if($cascade eq 'delete')
{
Rose::DB::Object::Manager->delete_objects(
db => $db,
object_class => $relationship->class,
where => \@query);
}
elsif($cascade eq 'null')
{
my %set = map { $_ => undef } values(%$column_map);
Rose::DB::Object::Manager->update_objects(
db => $db,
object_class => $relationship->class,
set => \%set,
where => \@query);
}
else { Carp::confess "Illegal cascade value '$cascade' snuck through" }
}
if($started_new_tx)
{
$db->commit or die $db->error;
}
};
$error = $@;
}
if($error)
{
Rose::DB::Object::Manager->error_mode($mgr_error_mode);
$self->error(ref $error ? $error : "delete() with cascade - $error");
$db->rollback if($db && $started_new_tx);
$self->meta->handle_error($self);
return 0;
}
Rose::DB::Object::Manager->error_mode($mgr_error_mode);
$self->{STATE_IN_DB()} = 0;
return 1;
}
else
lib/Rose/DB/Object.pm view on Meta::CPAN
Get or set the L<DBI> database handle contained in L<db|/db>.
=item B<delete [PARAMS]>
Delete the row represented by the current object. The object must have been previously loaded from the database (or must otherwise have a defined primary key value) in order to be deleted. Returns true if the row was deleted or did not exist, false...
PARAMS are optional name/value pairs. Valid PARAMS are:
=over 4
=item B<cascade TYPE>
Also process related rows. TYPE must be "delete", "null", or "1". The value "1" is an alias for "delete". Passing an illegal TYPE value will cause a fatal error.
For each "one to many" relationship, all of the rows in the foreign ("many") table that reference the current object ("one") will be deleted in "delete" mode, or will have the column(s) that reference the current object set to NULL in "null" mode.
For each "many to many" relationship, all of the rows in the "mapping table" that reference the current object will deleted in "delete" mode, or will have the columns that reference the two tables that the mapping table maps between set to NULL in "n...
For each "one to one" relationship or foreign key with a "one to one" L<relationship type|Rose::DB::Object::Metadata::ForeignKey/relationship_type>, all of the rows in the foreign table that reference the current object will deleted in "delete" mode,...
In all modes, if the L<db|/db> is not currently in a transaction, a new transaction is started. If any part of the cascaded delete fails, the transaction is rolled back.
=item B<prepare_cached BOOL>
If true, then L<DBI>'s L<prepare_cached|DBI/prepare_cached> method will be used (instead of the L<prepare|DBI/prepare> method) when preparing the SQL statement that will delete the object. If omitted, the default value is determined by the L<metadat...
=back
The cascaded delete feature described above plays it safe by only deleting rows that are not referenced by any other rows (according to the metadata provided by each L<Rose::DB::Object>-derived class). I B<strongly recommend> that you implement "cas...
=item B<error>
Returns the text message associated with the last error that occurred.
=item B<insert [PARAMS]>
Insert the current object to the database table. This method should only be used when you're absolutely sure that you want to B<force> the current object to be inserted, rather than updated. It is recommended that you use the L<save|/save> method i...
PARAMS are optional name/value pairs. Valid PARAMS are:
lib/Rose/DB/Object.pm view on Meta::CPAN
# Product and vendor records created and linked together,
# all within a single transaction.
$product->save;
See the "making methods" sections of the L<Rose::DB::Object::Metadata::Relationship|Rose::DB::Object::Metadata::Relationship/"MAKING METHODS"> and L<Rose::DB::Object::Metadata::ForeignKey|Rose::DB::Object::Metadata::ForeignKey/"MAKING METHODS"> docum...
Valid parameters to L<save()|/save> are:
=over 4
=item B<cascade BOOL>
If true, then sub-objects related to this object through a foreign key or relationship that have been previously loaded using methods called on this object and that contain unsaved changes will be L<saved|/save> after the parent object is saved. Thi...
All database operations are done within a single transaction. If the L<db|/db> is not currently in a transaction, a new transaction is started. If any part of the cascaded save fails, the transaction is rolled back.
If omitted, the default value of this parameter is determined by the L<metadata object|/meta>'s L<default_cascade_save|Rose::DB::Object::Metadata/default_cascade_save> class method, which returns false by default.
Example:
$p = Product->new(id => 123)->load;
print join(', ', $p->colors); # related Color objects loaded
$p->colors->[0]->code('zzz'); # one Color object is modified
# The Product object and the modified Color object are saved
$p->save(cascade => 1);
=item B<changes_only BOOL>
If true, then only the columns whose values have been modified will be included in the insert or update query. Otherwise, all eligible columns will be included. Note that any column that has a L<default|Rose::DB::Object::Metadata::Column/default> v...
If omitted, the default value of this parameter is determined by the L<metadata object|/meta>'s L<default_update_changes_only|Rose::DB::Object::Metadata/default_update_changes_only> class method on update, and the L<default_insert_changes_only|Rose::...
=item B<insert BOOL>
If set to a true value, then an L<insert|/insert> is attempted, regardless of whether or not the object was previously L<load|/load>ed from the database.
lib/Rose/DB/Object/Loader.pm view on Meta::CPAN
{
croak "Illegal class prefix: $class_prefix";
}
$class_prefix .= '::' unless($class_prefix =~ /::$/);
}
# Evil masking of object attribute
local $self->{'class_prefix'} = $class_prefix;
# When setting explicit values for attributes that cascade to
# affect other attributes, save off the old values are restore
# them at the end.
my %save;
if(exists $args{'db_class'})
{
my $db_class = delete $args{'db_class'};
if($db && $db_class && $db_class ne $db->class)
{
lib/Rose/DB/Object/MakeMethods/Generic.pm view on Meta::CPAN
$self->$local_method(undef);
}
}
# Forget about any value we were going to set on save
$to_save_pre = delete $self->{ON_SAVE_ATTR_NAME()}{'pre'}{'fk'}{$fk_name}{'set'};
$to_save_post = delete $self->{ON_SAVE_ATTR_NAME()}{'post'}{'rel'}{$fk_name}{'set'};
$self->save or die $self->error;
# Propogate cascade arg, if any
$deleted = $object->delete(@_) or die $object->error;
if($started_new_tx)
{
$db->commit or die $db->error;
}
$self->{$key} = undef;
# Not sharing? Aw.
lib/Rose/DB/Object/Metadata.pm view on Meta::CPAN
allow_inline_column_values => { default => 0 },
is_initialized => { default => 0 },
is_auto_initializating => { default => 0 },
allow_auto_initialization => { default => 0 },
was_auto_initialized => { default => 0 },
initialized_foreign_keys => { default => 0 },
default_load_speculative => { default => 0 },
auto_load_related_classes => { default => 1 },
default_update_changes_only => { default => 0 },
default_insert_changes_only => { default => 0 },
default_cascade_save => { default => 0 },
default_smart_modification => { default => 0 },
include_predicated_unique_indexes => { default => 0 },
],
'array --get_set_inited' =>
[
'columns_ordered',
'nonpersistent_columns_ordered',
]
);
lib/Rose/DB/Object/Metadata.pm view on Meta::CPAN
If a L<Rose::DB::Object::ConventionManager>-derived class name is passed, a new object of that class is created with its L<meta|Rose::DB::Object::ConventionManager/meta> attribute set to this metadata object. Then it is used as the convention manage...
If a convention manager name is passed, then the corresponding class is looked up in the L<convention manager class map|convention_manager_classes>, a new object of that class is constructed, its L<meta|Rose::DB::Object::ConventionManager/meta> attri...
See the L<Rose::DB::Object::ConventionManager> documentation for more information on convention managers.
=item B<db>
Returns the L<Rose::DB>-derived object associated with this metadata object's L<class|/class>. A fatal error will occur if L<class|/class> is undefined or if the L<Rose::DB> object could not be created.
=item B<default_cascade_save [BOOL]>
Get or set a boolean value that indicates whether or not the L<class|/class> associated with this metadata object will L<save|Rose::DB::Object/save> related objects when the parent object is L<saved|Rose::DB::Object/save>. See the documentation for ...
=item B<default_load_speculative [BOOL]>
Get or set a boolean value that indicates whether or not the L<class|/class> associated with this metadata object will L<load|Rose::DB::Object/load> speculatively by default. See the documentation for L<Rose::DB::Object>'s L<load()|Rose::DB::Object/...
=item B<default_update_changes_only [BOOL]>
Get or set a boolean value that indicates whether or not the L<class|/class> associated with this metadata object will L<update|Rose::DB::Object/update> only an object's modified columns by default (instead of updating all columns). See the document...
lib/Rose/DB/Object/Tutorial.pod view on Meta::CPAN
$p->delete; # Now it's safe to delete the product
The list of prices for the product can also be set to an empty list, which will have the effect of deleting all associated prices when the product is saved.
$p->prices([]);
$p->save; # All associated prices deleted here
$p->delete; # Now it's safe to delete the product
Finally, the L<delete()|Rose::DB::Object/delete> method can actually automate this process, and do it all inside a transaction as well.
$p->delete(cascade => 1); # Delete all associated rows too
Again, the recommended approach is to use triggers inside the database itself. But if necessary, these other approaches will work too.
=head3 Many-to-many relationships
The final relationship type is the most complex. In a "many to many" relationship, a single row in table A may be related to multiple rows in table B, while a single row in table B may also be related to multiple rows in table A. (Confused? A conc...
This kind of relationship involves three tables instead of just two. The "local" and "foreign" tables, familiar from the other relationship types described above, still exist, but now there's a third table that connects rows from those two tables. ...
Let's add such a relationship to our growing family of classes. Imagine that each product may come in several colors. Right away, both the "one to one" and "many to one" relationship types are eliminated since they can only provide a single color...
lib/Rose/DB/Object/Tutorial.pod view on Meta::CPAN
Passing a reference to an empty array will remove all colors associated with a particular product by deleting all the mapping table entries.
$p->colors([]);
$p->save; # all mapping table entries for this product deleted here
Finally, the same caveats L<described earlier|/"Cascading delete"> about deleting products that have associated prices apply to colors as well. Again, I recommend using a trigger in the database to handle this, but L<Rose::DB::Object>'s cascading de...
# Delete all associated rows in the prices table, plus any
# rows in the product_color_map table, before deleting the
# row in the products table.
$p->delete(cascade => 1);
=head3 Relationship code summary
To summarize this exploration of inter-table relationships, here's a terse summary of the current state of our Perl classes, and the associated database tables.
For the sake of brevity, I've chosen to use the shorter versions of the foreign key and relationship definitions in the Perl classes shown below. Just remember that this only works when your tables, columns, and classes are named according to the ex...
First, the database schema.
CREATE TABLE vendors
t/benchmarks/lib/MyTest/CDBI/Complex/Product.pm view on Meta::CPAN
__PACKAGE__->has_a(last_modified => 'DateTime',
inflate => sub { $MyTest::CDBI::Base::DB->parse_datetime(shift) },
deflate => sub { $MyTest::CDBI::Base::DB->format_datetime(shift) });
__PACKAGE__->has_a(published => 'DateTime',
inflate => sub { $MyTest::CDBI::Base::DB->parse_datetime(shift) },
deflate => sub { $MyTest::CDBI::Base::DB->format_datetime(shift) });
__PACKAGE__->has_a(category_id => 'MyTest::CDBI::Complex::Category');
__PACKAGE__->has_many(code_names => 'MyTest::CDBI::Complex::CodeName', { cascade => 'None' });
1;
t/benchmarks/lib/MyTest/CDBI/Simple/Product.pm view on Meta::CPAN
use MyTest::CDBI::Simple::Category;
use base 'MyTest::CDBI::Base';
__PACKAGE__->table('rose_db_object_test_products');
__PACKAGE__->columns(Primary => 'id');
__PACKAGE__->columns(Essential => qw(category_id date_created fk1 fk2 fk3 id last_modified name published status));
__PACKAGE__->has_a(category_id => 'MyTest::CDBI::Simple::Category');
__PACKAGE__->has_many(code_names => 'MyTest::CDBI::Simple::CodeName', { cascade => 'None' });
1;
t/benchmarks/lib/MyTest/CDBI/Sweet/Complex/Product.pm view on Meta::CPAN
__PACKAGE__->has_a(last_modified => 'DateTime',
inflate => sub { $MyTest::CDBI::Base::DB->parse_datetime(shift) },
deflate => sub { $MyTest::CDBI::Base::DB->format_datetime(shift) });
__PACKAGE__->has_a(published => 'DateTime',
inflate => sub { $MyTest::CDBI::Base::DB->parse_datetime(shift) },
deflate => sub { $MyTest::CDBI::Base::DB->format_datetime(shift) });
__PACKAGE__->has_a(category_id => 'MyTest::CDBI::Sweet::Complex::Category');
__PACKAGE__->has_many(code_names => 'MyTest::CDBI::Sweet::Complex::CodeName', { cascade => 'None' });
# Dunno why I have to do this, but it doesn't work without it...
my $meta = __PACKAGE__->meta_info(has_many => 'code_names');
$meta->args->{'foreign_key'} = 'product_id';
1;
t/benchmarks/lib/MyTest/CDBI/Sweet/Simple/Product.pm view on Meta::CPAN
use MyTest::CDBI::Sweet::Simple::Code;
use MyTest::CDBI::Sweet::Simple::CodeName;
use MyTest::CDBI::Sweet::Simple::Category;
__PACKAGE__->table('rose_db_object_test_products');
__PACKAGE__->columns(Primary => 'id');
__PACKAGE__->columns(Essential => qw(category_id date_created fk1 fk2 fk3 id last_modified name published status));
__PACKAGE__->has_a(category_id => 'MyTest::CDBI::Sweet::Simple::Category');
__PACKAGE__->has_many(code_names => 'MyTest::CDBI::Sweet::Simple::CodeName', { cascade => 'None' });
# Dunno why I have to do this, but it doesn't work without it...
my $meta = __PACKAGE__->meta_info(has_many => 'code_names');
$meta->args->{'foreign_key'} = 'product_id';
1;
t/db-object-relationship.t view on Meta::CPAN
$o->fkone(1);
$o->fk2(2);
$o->fk3(3);
$o->save;
#local $Rose::DB::Object::Manager::Debug = 1;
eval
{
local $o->dbh->{'PrintError'} = 0;
$o->delete(cascade => 'null');
};
ok($@, "delete cascade null 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyPgOtherObject');
is($count, 2, "delete cascade rollback confirm 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyPgOtherObject2');
is($count, 3, "delete cascade rollback confirm 2 - $db_type");
ok($o->delete(cascade => 'delete'), "delete cascade delete 1 - $db_type");
$o = MyPgObject->new(id => 99)->load;
$o->fkone(11);
$o->fk2(12);
$o->fk3(13);
$o->save;
eval
{
local $o->dbh->{'PrintError'} = 0;
$o->delete(cascade => 'null');
};
ok($@, "delete cascade null 2 - $db_type");
ok($o->delete(cascade => 'delete'), "delete cascade delete 2 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyPgColorMap');
is($count, 0, "delete cascade confirm 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyPgOtherObject2');
is($count, 0, "delete cascade confirm 2 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyPgOtherObject');
is($count, 0, "delete cascade confirm 3 - $db_type");
eval { $o->meta->alias_column(nonesuch => 'foo') };
ok($@, "alias_column() nonesuch - $db_type");
# Start foreign key method tests
#
# Foreign key get_set_now
#
t/db-object-relationship.t view on Meta::CPAN
$o = MyPgObject->new(id => 200);
$o->load;
ok(!defined $o->other_obj_on_save, "set foreign key object on save 10 - $db_type");
$co = MyPgOtherObject->new(k1 => 51, k2 => 52, k3 => 53);
ok(!$co->load(speculative => 1), "set foreign key object on save 11 - $db_type");
$o->delete(cascade => 1);
# TEST: Set, delete, save
$o = MyPgObject->new(id => 200,
name => 'Rose',
flag => 1);
ok($o->other_obj_on_save(k1 => 51, k2 => 52, k3 => 53), "set foreign key object on save 12 - $db_type");
$co = MyPgObject->new(id => 200);
ok(!$co->load(speculative => 1), "set foreign key object on save 13 - $db_type");
t/db-object-relationship.t view on Meta::CPAN
$o = MyPgObject->new(id => 200);
$o->load;
ok(!defined $o->other_obj_on_save, "set foreign key object on save 18 - $db_type");
$co = MyPgOtherObject->new(k1 => 51, k2 => 52, k3 => 53);
ok(!$co->load(speculative => 1), "set foreign key object on save 19 - $db_type");
$o->delete(cascade => 1);
#
# Foreign key delete_on_save
#
$o = MyPgObject->new(id => 500,
name => 'Kip',
flag => 1);
$o->other_obj_on_save(k1 => 7, k2 => 8, k3 => 9);
t/db-object-relationship.t view on Meta::CPAN
$o->fk3(3);
$o->save;
#local $Rose::DB::Object::Manager::Debug = 1;
my $ret;
eval
{
local $o->dbh->{'PrintError'} = 0;
$ret = $o->delete(cascade => 'null');
};
# Allow for exceptions in case some fancy new version of MySQL actually
# tries preserve referential integrity. Hey, you never know...
ok($ret || $@, "delete cascade null 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyMySQLOtherObject2');
is($count, 3, "delete cascade rollback confirm 2 - $db_type");
$o = MyMySQLObject->new(id => 99)->load;
$o->fk1(11);
$o->fk2(12);
$o->fk3(13);
$o->save;
eval
{
local $o->dbh->{'PrintError'} = 0;
$ret = $o->delete(cascade => 'null');
};
ok($ret || $@, "delete cascade null 2 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyMySQLColorMap');
is($count, 3, "delete cascade confirm 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyMySQLOtherObject2');
is($count, 3, "delete cascade confirm 2 - $db_type");
eval { $o->meta->alias_column(nonesuch => 'foo') };
ok($@, "alias_column() nonesuch - $db_type");
# Start foreign key method tests
#
# Foreign key get_set_now
#
t/db-object-relationship.t view on Meta::CPAN
$o = MyMySQLObject->new(id => 200);
$o->load;
ok(!defined $o->other_obj_on_save, "set foreign key object on save 10 - $db_type");
$co = MyMySQLOtherObject->new(k1 => 51, k2 => 52, k3 => 53);
ok(!$co->load(speculative => 1), "set foreign key object on save 11 - $db_type");
$o->delete(cascade => 1);
# TEST: Set, delete, save
$o = MyMySQLObject->new(id => 200,
name => 'Rose',
flag => 1);
ok($o->other_obj_on_save(k1 => 51, k2 => 52, k3 => 53), "set foreign key object on save 12 - $db_type");
$co = MyMySQLObject->new(id => 200);
ok(!$co->load(speculative => 1), "set foreign key object on save 13 - $db_type");
t/db-object-relationship.t view on Meta::CPAN
$o = MyMySQLObject->new(id => 200);
$o->load;
ok(!defined $o->other_obj_on_save, "set foreign key object on save 18 - $db_type");
$co = MyMySQLOtherObject->new(k1 => 51, k2 => 52, k3 => 53);
ok(!$co->load(speculative => 1), "set foreign key object on save 19 - $db_type");
$o->delete(cascade => 1);
#
# Foreign key delete_on_save
#
$o = MyMySQLObject->new(id => 500,
name => 'Kip',
flag => 1);
$o->other_obj_on_save(k1 => 7, k2 => 8, k3 => 9);
t/db-object-relationship.t view on Meta::CPAN
ok(MyMySQLColorMap->new(obj_id => 60, color_id => 8)->load(speculative => 1),
"add 2 many to many on save 33 - $db_type");
$sth = $color->db->dbh->prepare('SELECT COUNT(*) FROM rose_db_object_colors_map WHERE obj_id = 60');
$sth->execute;
$count = $sth->fetchrow_array;
is($count, 5, "add 2 many to many on save 34 - $db_type");
# End "many to many" tests
# Start "one to one" cascaded delete tests
#local $Rose::DB::Object::Debug = 1;
#local $Rose::DB::Object::Manager::Debug = 1;
$o = MyMySQLObject->new(name => '1to1bug',
fk1 => 10,
fk2 => 20,
fk3 => 30,
other_obj_otoo =>
{
name => '1to1bugfo',
k1 => 10,
k2 => 20,
k3 => 30,
});
$o->save;
$o = MyMySQLObject->new(id => $o->id)->load;
ok(defined $o->other_obj_otoo, "delete(cascade => 1) one to one prep - $db_type");
$o = MyMySQLObject->new(id => $o->id);
$o->delete(cascade => 1);
ok(!MyMySQLOtherObject->new(k1 => 10, k2 => 20, k3 => 30)->load(speculative => 1),
"delete(cascade => 1) one to one delete - $db_type");
# XXX: This relies on MySQL's creepy behavior of setting not-null
# XXX: columns to 0 when they are set to NULL by a query.
#
# $o = MyMySQLObject->new(name => '1to1bug2',
# fk1 => 10,
# fk2 => 20,
# fk3 => 30,
# other_obj_otoo =>
# {
# name => '1to1bugfo2',
# k1 => 10,
# k2 => 20,
# k3 => 30,
# });
#
# $o->save;
#
# $o = MyMySQLObject->new(id => $o->id)->load;
#
# ok(defined $o->other_obj_otoo, "delete(cascade => 1) one to one prep - $db_type");
#
# $o = MyMySQLObject->new(id => $o->id);
# $o->delete(cascade => 'null');
#
# ok(MyMySQLOtherObject->new(k1 => 0, k2 => 0, k3 => 0)->load(speculative => 1),
# "delete(cascade => 1) one to one null - $db_type");
# End "one to one" cascaded delete tests
# Start fk hook-up tests
$o2 = MyMySQLOtherObject2->new(name => 'B', pid => 11);
$o2->save;
$o = MyMySQLObject->new(name => 'John', id => 12);
$o->add_other2_objs2($o2);
$o2->name('John2');
t/db-object-relationship.t view on Meta::CPAN
$o->fkone(1);
$o->fk2(2);
$o->fk3(3);
$o->save;
#local $Rose::DB::Object::Manager::Debug = 1;
eval
{
local $o->dbh->{'PrintError'} = 0;
$o->delete(cascade => 'null');
};
ok($@, "delete cascade null 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyInformixOtherObject');
is($count, 2, "delete cascade rollback confirm 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyInformixOtherObject2');
is($count, 3, "delete cascade rollback confirm 2 - $db_type");
ok($o->delete(cascade => 'delete'), "delete cascade delete 1 - $db_type");
$o = MyInformixObject->new(id => 99)->load;
$o->fkone(11);
$o->fk2(12);
$o->fk3(13);
$o->save;
eval
{
local $o->dbh->{'PrintError'} = 0;
$o->delete(cascade => 'null');
};
ok($@, "delete cascade null 2 - $db_type");
ok($o->delete(cascade => 'delete'), "delete cascade delete 2 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyInformixColorMap');
is($count, 0, "delete cascade confirm 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyInformixOtherObject2');
is($count, 0, "delete cascade confirm 2 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MyInformixOtherObject');
is($count, 0, "delete cascade confirm 3 - $db_type");
eval { $o->meta->alias_column(nonesuch => 'foo') };
ok($@, "alias_column() nonesuch - $db_type");
# Start foreign key method tests
#
# Foreign key get_set_now
#
t/db-object-relationship.t view on Meta::CPAN
$o = MyInformixObject->new(id => 200);
$o->load;
ok(!defined $o->other_obj_on_save, "set foreign key object on save 10 - $db_type");
$co = MyInformixOtherObject->new(k1 => 51, k2 => 52, k3 => 53);
ok(!$co->load(speculative => 1), "set foreign key object on save 11 - $db_type");
$o->delete(cascade => 1);
# TEST: Set, delete, save
$o = MyInformixObject->new(id => 200,
name => 'Rose',
flag => 1);
ok($o->other_obj_on_save(k1 => 51, k2 => 52, k3 => 53), "set foreign key object on save 12 - $db_type");
$co = MyInformixObject->new(id => 200);
ok(!$co->load(speculative => 1), "set foreign key object on save 13 - $db_type");
t/db-object-relationship.t view on Meta::CPAN
$o = MyInformixObject->new(id => 200);
$o->load;
ok(!defined $o->other_obj_on_save, "set foreign key object on save 18 - $db_type");
$co = MyInformixOtherObject->new(k1 => 51, k2 => 52, k3 => 53);
ok(!$co->load(speculative => 1), "set foreign key object on save 19 - $db_type");
$o->delete(cascade => 1);
#
# Foreign key delete_on_save
#
$o = MyInformixObject->new(id => 500,
name => 'Kip',
flag => 1);
$o->other_obj_on_save(k1 => 7, k2 => 8, k3 => 9);
t/db-object-relationship.t view on Meta::CPAN
$o->fkone(1);
$o->fk2(2);
$o->fk3(3);
$o->save;
#local $Rose::DB::Object::Manager::Debug = 1;
eval
{
local $o->dbh->{'PrintError'} = 0;
$o->delete(cascade => 'null');
};
ok($@, "delete cascade null 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MySQLiteOtherObject');
is($count, 2, "delete cascade rollback confirm 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MySQLiteOtherObject2');
is($count, 3, "delete cascade rollback confirm 2 - $db_type");
ok($o->delete(cascade => 'delete'), "delete cascade delete 1 - $db_type");
$o = MySQLiteObject->new(id => 99)->load;
$o->fkone(11);
$o->fk2(12);
$o->fk3(13);
$o->save;
eval
{
local $o->dbh->{'PrintError'} = 0;
$o->delete(cascade => 'null');
};
ok($@, "delete cascade null 2 - $db_type");
ok($o->delete(cascade => 'delete'), "delete cascade delete 2 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MySQLiteColorMap');
is($count, 0, "delete cascade confirm 1 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MySQLiteOtherObject2');
is($count, 0, "delete cascade confirm 2 - $db_type");
$count =
Rose::DB::Object::Manager->get_objects_count(
db => $o->db,
object_class => 'MySQLiteOtherObject');
is($count, 0, "delete cascade confirm 3 - $db_type");
eval { $o->meta->alias_column(nonesuch => 'foo') };
ok($@, "alias_column() nonesuch - $db_type");
# Start foreign key method tests
#
# Foreign key get_set_now
#
t/db-object-relationship.t view on Meta::CPAN
$o = MySQLiteObject->new(id => 200);
$o->load;
ok(!defined $o->other_obj_on_save, "set foreign key object on save 10 - $db_type");
$co = MySQLiteOtherObject->new(k1 => 51, k2 => 52, k3 => 53);
ok(!$co->load(speculative => 1), "set foreign key object on save 11 - $db_type");
$o->delete(cascade => 1);
# TEST: Set, delete, save
$o = MySQLiteObject->new(id => 200,
name => 'Rose',
flag => 1);
ok($o->other_obj_on_save(k1 => 51, k2 => 52, k3 => 53), "set foreign key object on save 12 - $db_type");
$co = MySQLiteObject->new(id => 200);
ok(!$co->load(speculative => 1), "set foreign key object on save 13 - $db_type");
t/db-object-relationship.t view on Meta::CPAN
$o = MySQLiteObject->new(id => 200);
$o->load;
ok(!defined $o->other_obj_on_save, "set foreign key object on save 18 - $db_type");
$co = MySQLiteOtherObject->new(k1 => 51, k2 => 52, k3 => 53);
ok(!$co->load(speculative => 1), "set foreign key object on save 19 - $db_type");
$o->delete(cascade => 1);
#
# Foreign key delete_on_save
#
$o = MySQLiteObject->new(id => 500,
name => 'Kip',
flag => 1);
$o->other_obj_on_save(k1 => 7, k2 => 8, k3 => 9);
t/make-modules.ext view on Meta::CPAN
sort { $a->price <=> $b->price } $p->prices) . '; ' .
join(', ', map { $_->name } sort { $a->name cmp $b->name } $p->colors) . '; ';
my $c = $color_class->new(name => 'red')->load;
$ret .= $c->name . ': ' . $c->code, '; ';
print $ret, "\n";
my $v = $p->vendor;
$p->delete(cascade => 1);
$v->delete;
$product_manager_class->delete_products(all => 1);
$color_manager_class->delete_colors(all => 1);
#My::Product->meta->auto_load_related_classes(0);
#print My::Product->meta->perl_class_definition(braces => 'bsd', indent => 2);
__END__
t/one-to-many-reset.t view on Meta::CPAN
#}
my $artist_class = $class_prefix . '::RoseDbObjectArtist';
my $album_class = $class_prefix . '::RoseDbObjectAlbum';
# DBD::Informix chokes badly when prepare_cached() is used.
Rose::DB::Object::Metadata->dbi_prepare_cached($db_type eq 'informix' ? 0 : 1);
my $albums_method = 'rose_db_object_albums';
foreach my $cascade (0, 1)
{
my @cascade = $cascade ? (cascade => 1) : ();
my $album = $album_class->new(id => 1, title => 'album1');
$album->save();
my $artist = $artist_class->new(id => 1, name => 'Rage');
$artist->$albums_method($album->id);
$artist->save(@cascade);
ok($artist, "$cascade saved artist with albums - $db_type");
$artist->$albums_method($album->id);
$artist->save(@cascade);
ok($artist, "$cascade re-saved artist albums = $db_type");
$artist = $artist_class->new(id => $artist->id)->load;
is(scalar @{$artist->$albums_method() ||[]}, 1, "$cascade Check artist albums count - $db_type");
is($artist->$albums_method()->[0]->id, $album->id, "$cascade Check artist album ids - $db_type");
my @albums = $artist->$albums_method();
$artist->$albums_method(@albums);
$artist->save;
$artist->$albums_method(@albums);
$artist->save;
$artist = $artist_class->new(id => $artist->id)->load;
is(scalar @{$artist->$albums_method() ||[]}, 1, "$cascade Check artist albums count 2 - $db_type");
is($artist->$albums_method()->[0]->id, $album->id, "$cascade Check artist album ids 2 - $db_type");
$artist->delete(cascade => 1);
}
}
BEGIN
{
our %Have;
#
# PostgreSQL
#
t/save-cascade.t view on Meta::CPAN
# Foreign key
my $p = $product_class->new(name => 'p1', vendor => { name => 'v1' });
$p->save;
$p = $product_class->new(id => $p->id)->load;
my $v = $p->vendor;
$v->name('v1.1');
$p->save(cascade => 1);
$v = $vendor_class->new(id => $v->id)->load;
is($v->name, 'v1.1', "cascade fk 1.$i - $db_type");
# One-to-many
$p->prices([ { price => 1.25 } ]);
$p->save;
$p = $product_class->new(id => $p->id)->load;
my $price = $p->prices->[0];
is($price->price, 1.25, "cascade one-to-many 1.$i - $db_type");
is($price->region, 'US', "cascade one-to-many 2.$i - $db_type");
$price->region('UK');
$p->add_prices({ price => 4.25 });
$p->save(cascade => 1);
$price = $price_class->new(price_id => $price->price_id)->load;
is($price->region, 'UK', "cascade one-to-many 3.$i - $db_type");
$price = (sort { $a->price <=> $b->price } @{$p->prices})[-1];
is($price->price, 4.25, "cascade one-to-many 4.$i - $db_type");
is($price->region, 'US', "cascade one-to-many 5.$i - $db_type");
# Many-to-many
$p->colors([ { code => 'f00', name => 'red' } ]);
$p->save;
$p = $product_class->new(id => $p->id)->load;
my $color = $p->colors->[0];
is($color->code, 'f00', "cascade many-to-many 1.$i - $db_type");
is($color->name, 'red', "cascade many-to-many 2.$i - $db_type");
$color->name('r3d');
$p->add_colors({ code => '0f0', name => 'green' });
$p->save(cascade => 1);
$color = $color_class->new(code => $color->code)->load;
is($color->name, 'r3d', "cascade many-to-many 3.$i - $db_type");
$color = (sort { $a->name cmp $b->name } @{$p->colors})[0];
is($color->code, '0f0', "cascade many-to-many 4.$i - $db_type");
is($color->name, 'green', "cascade many-to-many 5.$i - $db_type");
$p->dbh->do('DELETE FROM product_colors');
$p->dbh->do('DELETE FROM colors');
$p->dbh->do('DELETE FROM prices');
$p->dbh->do('DELETE FROM products');
$p->dbh->do('DELETE FROM vendors');
}
}
BEGIN
t/spot-check-12.t view on Meta::CPAN
my $c = Clients->new( name => 'c1' );
$c->load;
my $a = Addresses->new( client_id => $c->id, street => 's1' );
$c->address($a);
#$Rose::DB::Object::Debug = 1;
#$Rose::DB::Object::Manager::Debug = 1;
ok($c->save( cascade => 1, changes_only => 1 ), 'save cascade changes only');
$c = Rose::DB::Object::Manager->get_objects(
object_class => 'Clients',
with_objects => 'address')->[0];
ok(!keys %{ $c->{MODIFIED_COLUMNS()} || {} }, 'check modified columns');
ok(has_loaded_related($c, 'address'), 'has_loaded_related() 1');
ok(has_loaded_related(object => $c, relationship => 'address'), 'has_loaded_related() 2');
$c->address->street('s2');
ok($c->save(cascade => 1, changes_only => 1), 'save cascade changes only - loaded with Manager');
$a = Addresses->new(id => $a->id)->load;
is($a->street, 's2', 'save cascade changes only - check');
}
END
{
if(have_db('sqlite_admin'))
{
my $dbh = get_dbh('sqlite_admin');
local $dbh->{'RaiseError'} = 0;
local $dbh->{'PrintError'} = 0;