NAME

Devel::Ladybug::ExtID - Define inter-object relationships

SYNOPSIS

use Devel::Ladybug qw| :all |;

create "YourApp::ChildObject" => {
  parentObjectId => Devel::Ladybug::ExtID->assert(
    "YourApp::ParentObject"
  )
};

DESCRIPTION

Ladybug's ExtID assertions are a simple and powerful way to define cross-object, cross-table relationships. An ExtID is a column/instance variable which points at the ID of another object.

Extends Devel::Ladybug::Str.

RELATIONSHIP DIRECTION

This is important.

Always use parent id in a child object, rather than child id in a parent object-- or else cascading operations will probably eat your data in unexpected and unwanted ways.

PUBLIC CLASS METHODS

  • $class->assert([$query], @rules)

    ExtID assertions are like pointers defining relationships with other classes, or within a class to define parent/child hierarchy.

    Attributes asserted as ExtID must match an id in the specified class's database table.

    If using a backing store which supports it, ExtID assertions also enforce database-level foreign key constraints.

EXAMPLES

Self-Referencing Table

Create a class of object which refers to itself by parent ID:

#
# File: YourApp/ParentObject.pm
#
use strict;
use warnings;

use Devel::Ladybug qw| :all |;

create "YourApp::ParentObject" => {
  #
  # Folders can go in other folders:
  #
  parentId => Devel::Ladybug::ExtID->assert(
    "YourApp::ParentObject",
    subtype(
      optional => true
    )
  ),

  # ...
};

Meanwhile, in caller, create a top-level object and a child object which refers to it:

#
# File: test-selfref.pl
#
use strict;
use warnings;

use YourApp::ParentObject;

my $parent = YourApp::ParentObject->new(
  name => "Hello Parent"
);

$parent->save;

my $child = YourApp::ChildObject"->new(
  name => "Hello Child",
  parentId => $parent->id,
);

$child->save;

Externally Referencing Table

A document class, building on the above example. Documents refer to their parent by ID:

#
# File: YourApp/ChildObject.pm
#
use strict;
use warnings;

use Devel::Ladybug qw| :all |;

use YourApp::ParentObject; # You must "use" any external classes

create "YourApp::ChildObject" => {
  parentId => Devel::Ladybug::ExtID->assert(
    "YourApp::ParentObject"
  )

};

Meanwhile, in caller, create a node which refers to its foreign class parent:

#
# File: test-extref.pl
#
use strict;
use warnings;

use YourApp::ChildObject;

my $parent = YourApp::ParentObject->loadByName("Hello Parent");

my $child = YourApp::ChildObject->new(
  name => "Hello Again",
  parentId => $parent->id
);

$child->save;

One to Many

Wrap ExtID assertions inside a Devel::Ladybug::Array assertion to create a one-to-many relationship.

#
# File: YourApp/OneToManyExample.pm
#

# ...

create "YourApp::OneToManyExample" => {
  parentIds => Devel::Ladybug::Array->assert
    Devel::Ladybug::ExtID->assert(
      "YourApp::ParentObject"
    )
  ),

  # ...
};

Many to One / One to One

ExtID's default behavior is to permit many-to-one relationships (that is, multiple children may refer to the same parent by ID). To restrict this to a one-to-one relationship, include a unique subtype argument.

#
# File: YourApp/OneToOneExample.pm
#

# ...

create "YourApp::OneToOneExample" => {
  parentId => Devel::Ladybug::ExtID->assert(
    "YourApp::ParentObject",
    subtype(
      unique => true
    )
  ),
  
  # ...
};

Dynamic Allowed Values

If a string is specified as a second argument to ExtID, it will be used as a SQL query, which selects a subset of Ids used as allowed values at runtime.

This is entirely an application-level constraint, and is not enforced by the database when manually inserting or updating rows. Careful!

create "YourApp::PickyExample" => {
  userId => Devel::Ladybug::ExtID->assert(
    "YourApp::Example::::User",
    "select id from example_user where foo = 1"
  ),

  # ...
};

BUGS AND LIMITATIONS

Same-table non-GUID keys

Self-referential tables whose ID is not of type Devel::Ladybug::ID should assert an appropriate column type. This is needed because at class creation time, Ladybug does not yet know anything about the ID of the class being created. This workaround is only needed for self-referential tables which have overridden their id column.

You do not need to do this for externally referencing tables, since Ladybug will already know which column type to use. You do not need to do this unless the id assertion was overridden.

create "YourApp::FunkySelfRef" => {
  id => Devel::Ladybug::Serial->assert(),

  parentId => Devel::Ladybug::ExtID->assert(
    "YourApp::FunkySelfRef",
    subtype(
      columnType => "INTEGER"     # <-- eg
    )
  ),

};

SEE ALSO

This file is part of Devel::Ladybug.