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::Persistence - Serialization mix-in

DESCRIPTION

Configurable class mix-in for storable Devel::Ladybug objects.

This package will typically be used indirectly. Subclasses created with Devel::Ladybug's create function will respond to these methods by default.

Connection settings are controlled via .ladybugrc (See Devel::Ladybug::Constants), or may be overridden on a per-class basis (See __dbHost or __dbUser, in this document).

Database and table names are automatically derived, but may be overridden on a per-class basis (See databaseName or tableName, in this document).

See __use[Feature], in this document, for directions on how to disable or augment specific backing store options when subclassing.

PUBLIC CLASS METHODS

  • $class->load($id)

    Retrieve an object by ID from the backing store.

    This method will delegate to the appropriate private backend method. It returns the requested object, or throws an exception if the object was not found.

      my $object;
    
      try {
        $object = $class->load($id);
      } catch Error with {
        # ...
    
      };
  • $class->query($query)

    Runs the received query, and returns a statement handle which may be used to iterate through the query's result set.

    Reconnects to database, if necessary.

      sub($) {
        my $class = shift;
    
        my $query = sprintf(
          q| SELECT id FROM %s where foo like "%bar%" |,
          $class->tableName()
        );
    
        my $sth = $class->query($query);
    
        while ( my ( $id ) = $sth->fetchrow_array() ) {
          my $obj = $class->load($id);
    
          # ...
        }
    
        $sth->finish();
      }
  • $class->write($query)

    Runs the received query against the database, using the DBI do() method. Returns number of rows updated.

    Reconnects to database, if necessary.

      sub($$) {
        my $class = shift;
        my $value = shift;
    
        my $query = sprintf('update %s set foo = %s',
          $class->tableName(), $class->quote($value)
        )
    
        return $class->write($query);
      }
  • $class->search($hash)

    Wrapper to DBIx::TextIndex's full-text search method.

    Returns a Devel::Ladybug::Hash of hit IDs, or undef if the class contains no indexed fields.

    Attributes which are asserted as indexed will be automatically added to the class's fulltext index at save time. This is not recommended for frequently changing data.

      #
      # In class YourApp/SearchExample.pm:
      #
      use Devel::Ladybug qw| :all |;
    
      create "YourApp::SearchExample" => {
        field1 => Devel::Ladybug::Str->assert(
          subtype( indexed => true )
        ),
        field2 => Devel::Ladybug::Str->assert(
          subtype( indexed => true )
        ),
      };

    Meanwhile...

      #
      # In caller, mysearch.pl:
      #
      use YourApp::SearchExample;
    
      my $class = "YourApp::SearchExample";
    
      #
      # See DBIx::TextIndex
      #
      my $ids = $class->search({
        field1 => '+andword -notword orword "phrase words"',
        field2 => 'more words',
      });
    
      #
      # Or, just provide a string:
      #
      # my $ids = $class->search("hello world");
    
      $ids->each( sub {
        my $id = shift;
    
        my $obj = $class->load($id);
      } );
  • $class->selectScalar($query)

    Returns the results of the received query, as a singular scalar value.

  • $class->selectBool($query)

    Returns the results of the received query, as a binary true or false value.

  • $class->selectSingle($query)

    Returns the first row of results from the received query, as a one-dimensional Devel::Ladybug::Array.

      sub($$) {
        my $class = shift;
        my $name = shift;
    
        my $query = sprintf( q|
            SELECT mtime, ctime FROM %s WHERE name = %s
          |,
          $class->tableName(), $class->quote($name)
        );
    
        return $class->selectSingle($query);
      }
    
      #
      # Flat array of selected values, ie:
      #
      #   [ *userId, *ctime ]
      #
  • $class->selectMulti($query)

    Returns each row of results from the received query, as a one-dimensional Devel::Ladybug::Array.

      my $query = "SELECT userId FROM session";
    
      #
      # Flat array of User IDs, ie:
      #
      #   [
      #     *userId,
      #     *userId,
      #     ...
      #   ]
      #
      my $userIds = $class->selectMulti($query);

    Returns a two-dimensional Devel::Ladybug::Array of Devel::Ladybug::Arrays, if * or multiple columns are specified in the query.

      my $query = "SELECT userId, mtime FROM session";
    
      #
      # Array of arrays, ie:
      #
      #   [
      #     [ *userId, *mtime ],
      #     [ *userId, *mtime ],
      #     ...
      #   ]
      #
      my $idsWithTime = $class->selectMulti($query);
  • $class->allIds()

    Returns a Devel::Ladybug::Array of all IDs in the receiving class.

      my $ids = $class->allIds();
    
      #
      # for loop way
      #
      for my $id ( @{ $ids } ) {
        my $object = $class->load($id);
    
        # Stuff ...
      }
    
      #
      # collector way
      #
      $ids->each( sub {
        my $object = $class->load($_);
    
        # Stuff ...
      } );
  • $class->count;

    Returns the number of rows in this class's backing store.

  • $class->stream

    Returns a Devel::Ladybug::Stream of all IDs and Names in this table.

      my $stream = $class->stream;
    
      $stream->eachTuple( sub {
        my $id = shift;
        my $name = shift;
    
        print "Have ID $id, Name $name\n";
      } );
  • $class->each

    Iterator for each ID in the current class.

    See collector usage in Devel::Ladybug::Array docs.

      $class->each( sub {
        my $id = shift;
    
        my $obj = $class->load($id);
      } );
  • $class->tuples

    Returns a Devel::Ladybug::Array of all IDs and Names in this table.

      my $tuples = $class->tuples;
    
      $tuples->eachTuple( sub {
        my $id = shift;
        my $name = shift;
    
        print "Have ID $id, Name $name\n";
      } );
  • $class->memberClass($attribute)

    Only usable for foreign keys.

    Returns the class of object referenced by the named attribute.

      #
      # In a class prototype, there was an ExtID assertion:
      #
    
      # ...
      use YourApp::OtherClass;
      use YourApp::AnotherClass;
    
      create "YourApp::Example" => {
        #
        # OtherClass asserts ExtID by default:
        #
        userId => YourApp::OtherClass->assert,
    
        #
        # This is a one-to-many ExtID assertion:
        #
        categoryId => Devel::Ladybug::Array->assert(
          YourApp::AnotherClass->assert
        ),
    
        # ...
      };

    Meanwhile, in caller...

      # ...
      use YourApp::Example;
    
      my $class = "YourApp::Example";
    
      my $exa = $class->load("Foo");
    
      do {
        # Ask for the foreign class, eg "YourApp::OtherClass":
        my $memberClass = $class->memberClass("userId");
    
        # Use the persistence methods in the foreign class:
        my $user = $memberClass->load($exa->userId());
    
        $user->print;
      };

    One-to-many example:

      do {
        # Ask for the foreign class, eg "YourApp::AnotherClass":
        my $memberClass = $class->memberClass("categoryId");
    
        # Use the persistence methods in the foreign class:
        $exa->categoryId->each( sub {
          my $memberId = shift;
    
          my $category = $memberClass->load($memberId);
    
          $category->print;
        } );
      };
  • $class->doesIdExist($id)

    Returns true if the received ID exists in the receiving class's table.

  • $class->doesNameExist($name)

    Returns true if the received ID exists in the receiving class's table.

      my $name = "Bob";
    
      my $object = $class->doesNameExist($name)
        ? $class->loadByName($name)
        : $class->new( name => $name, ... );
  • $class->loadByName($name)

    Loader for named objects. Works just as load().

      my $object = $class->loadByName($name);
  • $class->spawn($name)

    Loader for named objects. Works just as load(). If the object does not exist on backing store, a new object with the received name is returned.

      my $object = $class->spawn($name);
  • $class->idForName($name)

    Return the corresponding row id for the received object name.

      my $id = $class->idForName($name);
  • $class->nameForId($id)

    Return the corresponding name for the received object id.

      my $name = $class->nameForId($id);
  • $class->allNames()

    Returns a list of all object ids in the receiving class. Requires DBI.

      my $names = $class->allNames();
    
      $names->each( sub {
        my $object = $class->loadByName($_);
    
        # Stuff ...
      } );
  • $class->quote($value)

    Returns a DBI-quoted (escaped) version of the received value. Requires DBI.

      my $quoted = $class->quote($hairy);
  • $class->databaseName()

    Returns the name of the receiving class's database. Corresponds to the lower-cased first-level Perl namespace, unless overridden in subclass.

      do {
        #
        # Database name is "yourapp"
        #
        my $dbname = YourApp::Example->databaseName();
      };
  • $class->tableName()

    Returns the name of the receiving class's database table. Corresponds to the second-and-higher level Perl namespaces, using an underscore delimiter.

    Will probably want to override this, if subclass lives in a deeply nested namespace.

      do {
        #
        # Table name is "example"
        #
        my $table = YourApp::Example->tableName();
      };
    
      do {
        #
        # Table name is "example_job"
        #
        my $table = YourApp::Example::Job->tableName();
      };
  • $class->loadYaml($string)

    Load the received string containing YAML into an instance of the receiving class.

    Warns and returns undef if the load fails.

      my $string = q|---
      foo: alpha
      bar: bravo
      |;
    
      my $object = $class->loadYaml($string);
  • $class->loadJson($string)

    Load the received string containing JSON into an instance of the receiving class.

    Warns and returns undef if the load fails.

  • $class->restore($id, [$version]);

    Returns the object with the received ID from the RCS backing store. Does not call save, caller must do so explicitly.

    Uses the latest RCS revision if no version number is provided.

      do {
        my $self = $class->restore("LmBkxTee3hGGZgM418LRwQ==", "1.1");
    
        $self->save("Restoring from RCS archive");
      };

PRIVATE CLASS METHODS

The __use<Feature> methods listed below can be overridden simply by setting a class variable with the same name, as the examples illustrate.

  • $class->__useFlatfile()

    Return a true value or constant value from Devel::Ladybug::StorageType, to maintain a flatfile backend for all saved objects.

    Default inherited value is auto-detected for the local system. Set class variable to override.

    Flatfile and DBI backing stores are not mutually exclusive. Classes may use either, both, or neither, depending on use case.

      #
      # Flatfile backing store only-- no DBI:
      #
      create "YourApp::Example::NoDbi" => {
        __useFlatfile => true,
        __useDbi  => false,
      };
    
      #
      # Maintain version history, but otherwise use DBI for everything:
      #
      create "YourApp::Example::DbiPlusRcs" => {
        __useFlatfile => true,
        __useRcs  => true,
        __useDbi  => true,
      };

    To use JSON format, use the constant value from Devel::Ladybug::StorageType.

      #
      # Javascript could handle these objects as input:
      #
      create "YourApp::Example::JSON" => {
        __useFlatfile => Devel::Ladybug::StorageType::JSON
      };

    Use __yamlHost to enforce a master flatfile host.

  • $class->__useRcs()

    Returns a true value if the current class keeps revision history with its YAML backend, otherwise false.

    Default inherited value is false. Set class variable to override.

    __useRcs does nothing for classes which do not also use YAML. The RCS archive is derived from the physical files in the YAML backing store.

      create "YourApp::Example" => {
        __useFlatfile => true, # Must be YAML
        __useRcs => true
    
      };
  • $class->__yamlHost()

    Optional; Returns the name of the RCS/YAML master host.

    If defined, the value for __yamlHost must *exactly* match the value returned by the local system's hostname command, including fully-qualified-ness, or any attempt to save will throw an exception. This feature should be used if you don't want files to accidentally be created on the wrong host/filesystem.

    Defaults to the yamlHost Devel::Ladybug::Constants value (ships as undef), but may be overridden on a per-class basis.

      create "YourApp::Example" => {
        __useFlatfile  => true,
        __useRcs       => true,
        __yamlHost     => "host023.example.com".
    
      };
  • $class->__useMemcached()

    Returns a TTL in seconds, if the current class should attempt to use memcached to minimize database load.

    Returns a false value if this class should bypass memcached.

    If the memcached server can't be reached at package load time, callers will load from the physical backing store.

    Default inherited value is 300 seconds (5 minutes). Set class variable to override.

      create "YourApp::CachedExample" => {
        #
        # Ten minute TTL on cached objects:
        #
        __useMemcached => 600
      };

    Set to false or 0 to disable caching.

      create "YourApp::NeverCachedExample" => {
        #
        # No caching in play:
        #
        __useMemcached => false
      };
  • $class->__useDbi

    Returns a constant from the Devel::Ladybug::StorageType enumeration, which represents the DBI type to be used.

    Default inherited value is auto-detected for the local system. Set class variable to override.

      create "YourApp::Example" => {
        __useDbi => Devel::Ladybug::StorageType::SQLite
      };
  • $class->__basePath()

    Return the base filesystem path used to store objects of the current class.

    By default, this method returns a directory named after the current class, under the directory specified by yamlRoot in the local .ladybugrc file.

      my $base = $class->__basePath();
    
      for my $path ( <$base/*> ) {
        print $path;
        print "\n";
      }

    To override the base path in subclass if needed:

      __basePath => sub($) {
        my $class = shift;
    
        return join( '/', customPath, $class );
      }
  • $class->__baseRcsPath()

    Returns the base filesystem path used to store revision history files.

    By default, this just tucks "RCS" onto the end of __basePath().

  • $class->__primaryKey()

    Returns the name of the attribute representing this class's primary ID. Unless overridden, this method returns the string "id".

  • $class->__primaryKeyClass()

    Returns the object class used to represent this class's primary keys.

  • $class->__localLoad($id)

    Load the object with the received ID from the backing store.

      my $object = $class->__localLoad($id);
  • $class->__loadFromMemcached($id)

    Retrieves an object from memcached by ID. Returns nothing if the object wasn't there.

  • $class->__loadFromDatabase($id)

    Instantiates an object by id, from the database rather than the YAML backing store.

      my $object = $class->__loadFromDatabase($id);
  • $class->__marshal($hash)

    Loads any complex datatypes which were dumped to the database when saved, and blesses the received hash as an instance of the receiving class.

    Returns true on success, otherwise throws an exception.

      while ( my $object = $sth->fetchrow_hashref() ) {
        $class->__marshal($object);
      }
  • $class->__allIdsSth()

    Returns a statement handle to iterate over all ids. Requires DBI.

      my $sth = $class->__allIdsSth();
    
      while ( my ( $id ) = $sth->fetchrow_array() ) {
        my $object = $class->load($id);
    
        # Stuff ...
      }
    
      $sth->finish();
  • $class->__allNamesSth()

    Returns a statement handle to iterate over all names in class. Requires DBI.

      my $sth = $class->__allNamesSth();
    
      while ( my ( $name ) = $sth->fetchrow_array() ) {
        my $object = $class->loadByName($name);
    
        # Stuff ...
      }
    
      $sth->finish();
  • $class->__cacheKey($id)

    Returns the key for storing and retrieving this record in Memcached.

      #
      # Remove a record from the cache:
      #
      my $key = $class->__cacheKey($object->get($class->__primaryKey()));
    
      $memd->delete($key);
  • $class->__loadFromQuery($query)

    Returns the first row of results from the received query, as an instance of the current class. Good for simple queries where you don't want to have to deal with while().

      sub($$) {
        my $class = shift;
        my $id = shift;
    
        my $query = $class->__selectRowStatement($id);
    
        my $object = $class->__loadFromQuery($query) || die $@;
    
        # Stuff ...
      }
  • $class->__dbh()

    Creates a new DB connection, or returns the one which is currently active.

      sub($$) {
        my $class = shift;
        my $query = shift;
    
        my $dbh = $class->__dbh();
    
        my $sth = $dbh->prepare($query);
    
        while ( my $hash = $sth->fetchrow_hashref() ) {
          # Stuff ...
        }
    
        $sth->finish();
      }
  • $class->__loadYamlFromId($id)

    Return the instance with the received id (returns new instance if the object doesn't exist on disk yet)

      my $object = $class->__loadYamlFromId($id);
  • $class->__loadYamlFromPath($path)

    Return an instance from the YAML at the received filesystem path

      my $object = $class->__loadYamlFromPath('/tmp/foobar.123');
  • $class->__getSourceByPath($path)

    Quickly return the file contents for the received path. Basically cat a file into memory.

      my $example = $class->__getSourceByPath("/etc/passwd");
  • $class->__fsIds()

    Return the id of all instances of this class on disk.

      my $ids = $class->__fsIds();
    
      $ids->each( sub {
        my $object = $class->load($_);
    
        # Stuff ...
      } );
  • $class->__checkYamlHost

    Throws an exception if the current host is not the correct place for YAML/RCS filesystem ops.

  • $class->__init()

    Override Devel::Ladybug::Class->__init() to automatically create any missing tables in the database.

    Callers should invoke this at some point, if overriding in superclass.

  • __dbUser, __dbPass, __dbHost, __dbPort

    These items may be set on a per-class basis.

    Unless overridden, credentials from the global .ladybugrc will be used.

      #
      # Set as class variables in the prototype:
      #
      create "YourApp::YourClass" => {
        __dbUser => "user",
        __dbPass => "pass",
        __dbHost => "example.com",
        __dbPort => 12345,
    
      };
    
      #
      # Or, set at runtime (such as from apache startup):
      #
      my $class = "YourApp::YourClass";
    
      YourApp::YourClass->__dbUser("user");
      YourApp::YourClass->__dbPass("pass");
      YourApp::YourClass->__dbHost("example.com");
      YourApp::YourClass->__dbPort(12345);
  • $class->__elementClass($key);

    Returns the dynamic subclass used for an Array or Hash attribute.

    Instances of the element class represent rows in a linked table.

      #
      # File: Example.pm
      #
      create "YourApp::Example" => {
        testArray => Devel::Ladybug::Array->assert( ... )
    
      };
    
      #
      # File: testcaller.pl
      #
      my $elementClass = YourApp::Example->__elementClass("testArray");
    
      print "$elementClass\n"; # YourApp::Example::testArray

    In the above example, YourApp::Example::testArray is the name of the dynamic subclass which was allocated as a container for YourApp::Example's array elements.

PUBLIC INSTANCE METHODS

  • $self->save($comment)

    Saves self to all appropriate backing stores.

    Comment is ignored for classes not using RCS.

      $object->save("This is a checkin comment");
  • $self->presave();

    Abstract callback method invoked just prior to saving an object.

    Implement this method in subclass, if additional object sanity tests are needed.

  • $self->remove()

    Remove self's DB record, and unlink the YAML backing store if present.

    Does not delete RCS history, if present.

      $object->remove();
  • $self->revert([$version])

    Reverts self to the received version, which must be in the object's RCS file. This method is not usable unless __useRcs in the object's class is true.

    Uses the latest RCS revision if no version number is provided.

    Does not call save, caller must do this explicitly.

      do {
        $self->revert("1.20"); # Load previous version from RCS
    
        $self->save("Restoring from RCS archive");
      };
  • $self->exists()

    Returns true if this object has ever been saved.

      my $object = YourApp::Example->new();
    
      my $false = $object->exists();
    
      $object->save();
    
      my $true = $object->exists();
  • $self->key()

    Returns the value for this object's primary database key, since the primary key may not always be "id".

    Equivalent to:

      $self->get( $class->__primaryKey() );

    Which, in most cases, yields the same value as:

      $self->id();
  • $self->setIdsFromNames($attr, @names)

    Convenience setter for attributes which contain either an ExtID or an Array of ExtIDs.

    Sets the value for the received attribute name in self to the IDs of the received names. If a name is provided which does not correspond to a named object in the foreign table, a new object is created and saved, and the new ID is used.

    If the named objects do not yet exist, and have required attributes other than "name", then this method will raise an exception. The referenced object will need to be explicitly saved before the referent.

      #
      # Set "parentId" in self to the ID of the object named "Mom":
      #
      $obj->setIdsFromNames("parentId", "Mom");
    
      #
      # Set "categoryIds" in self to the ID of the objects
      # named "Blue" and "Red":
      #
      $obj->setIdsFromNames("categoryIds", "Blue", "Red");
  • $self->revisions()

    Return an array of all of this object's revision numbers

  • $self->revisionInfo()

    Return a hash of info for this file's checkins

  • $self->head()

    Return the head revision number for self's backing store.

PRIVATE INSTANCE METHODS

  • $self->_newId()

    Generates a new ID for the current object. Default is GUID-style.

      _newId => sub {
        my $self = shift;
    
        return Devel::Ladybug::Utility::randstr();
      }
  • $self->_localSave($comment)

    Saves self to all applicable backing stores.

  • $self->_localSaveInsideTransaction($comment);

    Private backend method called by _localSave() when inside of a database transaction.

  • $self->_saveToMemcached

    Saves a copy of self to the memcached cluster

  • $self->_saveToTextIndex

    Adds indexed values to this class's DBIx::TextIndex collection

  • $self->_removeFromTextIndex

    Removes indexed values from this class's DBIx::TextIndex collection

  • $self->_removeFromMemcached

    Removes self's cached entry in memcached

  • $self->_updateRecord()

    Executes an INSERT or UPDATE for this object's record. Callback method invoked from _localSave().

    Returns number of rows on UPDATE, or ID of object created on INSERT.

  • $self->_quotedValues()

    Callback method used to construct the values portion of an UPDATE or INSERT query.

  • $self->_fsDelete()

    Unlink self's YAML datafile from the filesystem. Does not dereference self from memory.

  • $self->_fsSave()

    Save $self as YAML to a local filesystem. Path is $class->__basePath + $self->id();

  • $self->_path()

    Return the filesystem path to self's YAML data store.

  • $self->_saveToPath($path)

    Save $self as YAML to a the received filesystem path

  • $self->_rcsPath()

    Return the filesystem path to self's RCS history file.

  • $self->_checkout($rcs, $path)

    Performs the RCS co command on the received path, using the received Rcs object.

  • $self->_checkin($rcs, $path, [$comment])

    Performs the RCS ci command on the received path, using the received Rcs object.

  • $self->_rcs()

    Return an instance of the Rcs class corresponding to self's backing store.

SEE ALSO

Cache::Memcached::Fast, Rcs, YAML::Syck, DBI

This file is part of Devel::Ladybug.