plainblack.com
Username Password
search
Bookmark and Share

Lineage

Every asset in WebGUI is part of the asset tree. Like a directory tree, the asset tree starts with a Root asset. Every asset descends from the Root asset. And, like a directory tree, every asset (except the Root asset) has a parent. In WebGUI, many assets can contain other assets. Some assets can only contain specific assets, like Calendars can only contain Events. The relationship between a parent asset and its child is stored in the asset table as "parentId".

 

Using the parentId, you can calculate the ancestry of any asset in the tree by walking up and down the tree, using parentIds to determine how to navigate, but this is unbearably slow. You'd need to query the database for every level. To solve this problem, WebGUI uses lineage.

 

 

The lineage of an asset is like a cache of the asset's position in the tree. Lineage is stored as a sequence of digits in the lineage column of the asset table, with 6 digits per level. Say you have a Page Layout, inside a Folder, with two Articles (Article1 and Article2), that each have two Images (Image1a, Image1b, Image2a, and Image2b). Using lineage, you can quickly look at the assets that are related to each other.

 

assetID

Lineage

Root

000001

Page

000001000001

Article1

000001000001000001

Image1a

000001000001000001000001

Image1b

000001000001000001000002

Article2

000001000001000002

Image2a

000001000001000002000001

Image2b

000001000001000002000002


 

Since every asset is descended from the root asset, every asset's lineage starts with the same six digits "000001". The Page Layout asset is a child of the Root asset, so it has an additional six digits. Every new child that is added will get a unique 6-digit number added to its parent's lineage, so the Article1 asset, a child of the Page asset, has another six digits in its lineage. The same with the Image1a asset.

 

Looking closer at the Image1a asset's lineage, you can determine a few things:

 

Ancestors

000001 000001

Page

Parent

000001 000001 000001

Article1

*Self

000001 000001 000001 000001

Image1a

Siblings

000001 000001 000001 ------

Siblings have the same parent

Children

000001 000001 000001 000001 ------

Children have Image1a as a parent.

 

Descendants

000001 000001 000001 000001 ++++++

Descendants have Image1a in their ancestry.

 

If you know an asset's lineage, you can instantiate it using the newByLineage() method of the WebGUI::Asset class (located in the AssetLineage mix-in). However, for most purposes you're going to want "all children of this asset", or "all descendants of this asset.” For this, you can use the getLineage() method (also in the AssetLineage mix-in).

 

The getLineage Method

getLineage is the standard way to get particular assets from the asset tree. As such, it is an extremely powerful and flexible tool. At its simplest, it gets all the assets with a certain relationship to the current asset. At its most complex, it can select only particular types of assets, at particular positions, and even return instantiated objects for you.

 

getLineage( relations [, options ] )

 

“relations” is an array reference of relations to get. It can consist of the following values:

 

self

Add the current asset to the list of returned.

siblings

Add siblings of the current asset to the list returned.

children

Add children of the current asset to the list returned.

descendants

Add any descendant (including children).

ancestors

Add the ancestors.

 

So, if you want to get all of the children of the Article1 asset from above:

 

$article1->getLineage( [ 'children' ] );

> [ 'Image1a', 'Image1b' ]

 

Or, if you want to get every ancestor all the way up to the root asset:

 

$article1->getLineage( [ 'ancestors' ] );

> [ 'Root', 'Page' ]

 

If you want to get every descendant of the Page asset, and include the Page asset itself:

 

$page->getLineage( [ 'self', 'descendants' ] );

> [ 'Page', 'Article1', 'Image1a', 'Image1b', 'Article2', 'Image2a', 'Image2b' ]

 

Query the Asset Tree

What if you only want to get certain types of assets in your lineage? Or only a certain number? For this, you have “options.” options is a hash reference with various keys that control the type and number of assets you get.

 

In order to control which types of assets you get, you can use the includeOnlyClasses and excludeClasses options. These options allow you to specify an array reference of class names to include or exclude from your results, like so:

 

$page->getLineage( [ 'descendants' ], {

includeOnlyClasses => [ 'WebGUI::Asset::File::Image' ]

}

> [ 'Image1a', 'Image1b', 'Image2a', 'Image2b' ]



$page->getLineage( [ 'descendants', {

excludeClasses => ['WebGUI::Asset::File::Image']

}

> [ 'Article1', 'Article2' ]

 

By default, getLineage will only get assets that have a state of “published” and will do the right thing when it comes to versioning (status). For most purposes, this is what you want, since assets in the trash, in the clipboard, or in an open/pending version tag that you're not part of should not be part of your normal operations. However, for the times when you need assets with special states or statuses, you have the statesToInclude and statusToInclude options:

 

# Get all children of $asset that are pending

my $pendingChildren

= $asset->getLineage( [ 'children' ], {

statusToInclude => [ 'pending' ],

} );

 

# Get all assets that are in the trash. $rootAsset is the Root asset

my $assetsInTrash

= $rootAsset->getLineage( [ 'descendants' ], {

statesToInclude => [ 'trash' ],

} );

 

Since getLineage is the standard way to get assets out of the asset tree, there are ways to further limit which assets you get from the database.

 

The 'whereClause' option allows you to add an additional SQL where clause to the query. By default, all of the columns in the asset and the assetData table are available, so you can do things like...

 

# Get all Post and Thread assets that were created in the last 24 hours

# (Thread is a child asset of Post)

my $todaysPostsAndThreads

= $rootAsset->getLineage( [ 'descendants' ], {

includeOnlyClasses => [ 'WebGUI::Asset::Post',

'WebGUI::Asset::Post::Thread' ],

whereClause

=> 'creationDate >

UNIX_TIMESTAMP( DATE_SUB(NOW(), INTERVAL 1 DAY) )',

} );



# Get any asset that was added or modified in the last week

my $whatsNew

= $rootAsset->getLineage( [ 'descendants' ], {

whereClause

=> 'revisionDate >

UNIX_TIMESTAMP( DATE_SUB(NOW(), INTERVAL 7 DAY) )',

} );



# Get Wiki pages that were last modified by a certain user

my $wikiActivity

= $rootAsset->getLineage( [ 'descendants' ], {

whereClause => 'revisedBy = "3"',

} );



 

The 'joinClass' option allows you to specify an asset class and join all the tables that it uses so you can run queries against it. Combined with the 'whereClause' option, you can get very specific about the assets you get.

 

# Get all Event assets that occur on December 25, 2008

my $christmasEvents

= $rootAsset->getLineage( [ 'descendants' ], {

joinClass => 'WebGUI::Asset::Event',

whereClause => 'Event.startDate = "2008-12-25"',

} );

 

Due to the nature of inheritance and the class hierarchy, joinClass has a useful side-effect: joining a class will also include all child classes in the results (though you can't search on their attributes). If you don't want this side-effect, you can use the includeOnlyClasses or excludeClasses options.

 

# Get all Image and Photo assets that are JPGs

my $jpegs

= $rootAsset->getLineage( [ 'descendants' ], {

joinClass => 'WebGUI::Asset::File::Image',

whereClause => 'filename REGEXP "jpe?g"',

} );



# Only get Image assets, not Photo assets.

my $jpegs

= $rootAsset->getLineage( [ 'descendants' ], {

joinClass => 'WebGUI::Asset::File::Image',

includeOnlyClasses => [ 'WebGUI::Asset::File::Image' ],

whereClause => 'filename REGEXP "jpe?g"',

} );

 

As you get even more specific with your queries of the asset tree, it becomes important to be able to modify the order and number of the assets you get. By default, the assets are sorted by their "lineage" field, which is also known as sorting by rank (more on rank later). Using the orderByClause option, you can order by any field to which you have access.

 

# Get the most recent content in descending order

my $whatsNew

= $rootAsset->getLineage( [ 'descendants' ], {

orderByClause => 'revisionDate DESC',

} );



# Get the threads in the message board $mboard ordered by their rating

my $recentThreads

= $mboard->getLineage( [ 'descendants' ], {

joinClass => 'WebGUI::Asset::Post::Thread',

includeOnlyClasses => [ 'WebGUI::Asset::Post::Thread' ],

orderByClause => 'threadRating DESC',

} );

 

The above examples will return the assets in the order you want, but it will also return all the assets that match the query. Often you don't want all the assets, but only a few or only from certain depths in the tree. You have three ways to limit the number of assets you get. ancestorLimit and endingLineageLength establish a window of tree depths to retrieve, and limit determines the number of assets you get.

 

# Only get three levels down from the current asset $asset

my $threes

= $asset->getLineage( [ 'descendants' ], {

endingLineageLength => $asset->getLineageLength + 3,

} );



# Only get three levels up from the current asset $asset

my $christmasList

= $asset->getLineage( [ 'ancestors' ], {

ancestorLimit => 3,

} );



# Get the first Page Layout asset in our ancestry

my $parentPage

= $asset->getLineage( [ 'ancestors' ], {

includeOnlyClasses => [ 'WebGUI::Asset::Wobject::Layout' ],

limit => 1,

} );

 

To pull it all together, here are a few more real-world examples of uses of getLineage.

 

# Get the next five upcoming events from the calendar $calendar

my $comingUp

= $calendar->getLineage( [ 'children' ], {

joinClass => 'WebGUI::Asset::Event',

whereClause

=> q{startDate >= CURDATE() and

(startTime IS NULL or startTime >= CURTIME())},

orderByClause => 'startDate, startTime',

limit => 5,

} );



# Get all the Threads started by the user with userId of '3' ordered by age

my $adminThreads

= $rootAsset->getLineage( [ 'descendants' ], {

includeOnlyClasses => [ 'WebGUI::Asset::Post::Thread' ],

whereClause => 'ownerUserId = "3"',

orderByClause => 'creationDate DESC',

} );

 

addChild

Just as getLineage is the standard way to get assets from the asset tree, addChild is the standard way to put assets into the asset tree. addChild will instantiate the new asset, add it to the database, set the asset's parent to the current asset, and give you the new asset object, like so:

 

my $image2c = $article2->addChild( $properties );



 

Using more arguments to addChild, you can specify the assetId of the new asset, and the revisionDate. This is useful when creating asset installers, or other times when you decide on an assetId ahead of time, and must make sure that assetId is used.

 

# Create a new asset with a specific assetId that is shown as

# being created 60 seconds ago

my $image2d = $article2->addChild( $properties, 'Image2d', time - 60 );

 

Finally, the fourth argument is a hash reference of options. Currently, only a single option exists, skipAutoCommitWorkflows, but it is very important when writing new code that requires use of addChild. Without this option, if the asset you're adding has an auto-commit workflow, a new version tag will be created. Worse, since the version tag isn't sent through the commit process until requestAutoCommit is called, the version tag remains open, showing up in the Version Tags for anyone to join.

 

For most cases, when using addChild in your own application, you're going to need to use skipAutoCommitWorkflows.

 

# Add a new post but keep the same working tag

my $post

= $thread->addChild( $properties, undef, undef,

{ skipAutoCommitWorkflows => 1 }

);

 

Notice that you set both the assetId and revisionDate to "undef". This will cause addChild to generate them automatically, like you want.

 

Rank and Depth

When an asset is created, it's given a parentId, which is the assetId of the asset's parent, and a lineage, which is the same lineage as the asset's parent plus an additional six digits. Those six digits are also known as the asset's rank. As mentioned above, by default, getLineage sorts the assets it returns by rank, so rank can be important in making sure asset one is shown before asset two.

 

To modify an asset's rank, you can use the demote, promote and, if necessary, the swapRank methods.

 

# Move an asset above it's next-highest-ranked sibling

$asset->promote;



# Move an asset below it's next-lowest-ranked sibling

$asset->demote;



# Swap two assets in the rank

$asset1->swapRank( $asset2->get('lineage') );

 

These methods move an asset around on the same level of depth, but sometimes you want to move the asset to another place entirely. To do so, use the setParent method.

 

# Move $asset to a new parent $parentAsset

$asset->setParent( $parentAsset );

 

In addition to updating the parentId of $asset, setParent also calls cascadeLineage, which updates the lineage of $asset and all of the descendants of $asset. This is very important, so be sure to use setParent when you want to move assets up and down the asset tree.

Keywords: asset asset tree depth lineage rank root sql

Search | Most Popular | Recent Changes | Wiki Home
© 2018 Plain Black Corporation | All Rights Reserved