The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

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

SYNOPSIS

  use Devel::Ladybug qw| :all |;

  use YourApp::Parent;

  create "YourApp::Child" => {
    # refer to Parent by ID:
    parentId => YourApp::Parent->assert,

    # the above is shorthand for:
    # parentId => Devel::Ladybug::ExtID->assert(
    #   "YourApp::Parent"
    # )
  };

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/Parent.pm
  #
  use strict;
  use warnings;

  use Devel::Ladybug qw| :all |;

  create "YourApp::Parent" => {
    #
    # Folders can go in other folders:
    #
    parentId => Devel::Ladybug::ExtID->assert(
      "YourApp::Parent",
      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::Parent;

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

  $parent->save;

  my $child = YourApp::Child"->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/Child.pm
  #
  use strict;
  use warnings;

  use Devel::Ladybug qw| :all |;

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

  create "YourApp::Child" => {
    parentId => YourApp::Parent->assert,

  };

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

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

  use YourApp::Child;

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

  my $child = YourApp::Child->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(
      YourApp::Parent->assert
    ),

    # ...
  };

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 => YourApp::Parent->assert(
      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 => YourApp::Example::User->assert(
      "select id from example_user where foo = 1"
    ),

    # ...
  };

BUGS AND LIMITATIONS

Same-table non-GUID keys

Self-referential tables (tables which refer back to themselves by parent ID), with an ID assertion which is not of type Devel::Ladybug::ID, should assert an appropriate column type in the ExtID assertion's subtype args. This workaround is only needed for self-referential tables which have overridden their id column.

You do not need to do this for externally referential 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(), # Overriding ID type

    #
    # Use ExtID->assert directly for self-ref tables:
    #
    parentId => Devel::Ladybug::ExtID->assert(
      "YourApp::FunkySelfRef",
      subtype(
        columnType => "INTEGER" # <-- Must match ID column type
      )
    ),

  };

SEE ALSO

This file is part of Devel::Ladybug.