Rose-DB-Object

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

    * 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)

Changes  view on Meta::CPAN

    * 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)

Changes  view on Meta::CPAN

    * 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

Changes  view on Meta::CPAN

      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)

Changes  view on Meta::CPAN

    * 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.

Changes  view on Meta::CPAN

    * 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.

Changes  view on Meta::CPAN

    * 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

MANIFEST  view on Meta::CPAN

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;



( run in 0.843 second using v1.01-cache-2.11-cpan-49f99fa48dc )