# Categories & Attributes

Content Server provides the $LLIAPI.AttrData class for working with categories. It's an object-like design similar to RHObject. A Frame instance of AttrData encapsulates the category and attribute information of a node, and provides methods to operate on the data. Saving the category data commits the data stored in .fData to the database.

The difficulty is with the $LLIAPI.AttrData programming interface is that it is incomplete and oriented towards specific types of web requests. It provides no simple way for a developer to set, get, or query values, which is likely why so many developers access the .fData structure directly:

attrData.fData.{CatID,VersionID}.Values[1].(SetID).Values[SetIndex].(AttrID).Values[AttrIndex] = newValue

This is bad. In the example there are no checks whether CatID, VersionID, SetID, SetIndex, AttrID, or AttrIndex is valid, in range, or if newValue is of the correct data type. Any wrong assumption about the parameters, structure, or values can corrupt the data and cause all sorts of problems.

RHCore abstracts away many of these difficulties by:

  • providing setter and getter methods that are easy to use regardless of the data type, if it's multi-valued, or within a set;
  • abstracting away the need to traverse, manipulate, or even look at .fData;
  • doing all the required checks and validation to enforce data integrity;
  • managing auditing (AttrData doesn't audit automatically); and
  • providing an interface to query on attribute values, independent of IDs, and without having to write any SQL queries.

Some of these features are already available on AttrData, but are incomplete. For example, the ValueSet() method can be used to set an attribute value and has the following interface:

function Assoc ValueSet(List spec, Dynamic value)

The spec parameter is a List that defines the attribute to manipulate. That looks something like this: {{29502,95},{1,1,11,2,12,2}}. The numbers refer to the category ID, category version ID, set ID, set index, attribute ID, and the attribute index. The function doesn't handle auditing and fails if a multi-valued attribute or set hasn't been extended to accommodate the index. It's not developer friendly at all.

# RHAttrData

RHCore introduces a subclass of $LLIAPI.AttrData called $RHCore.RHAttrData and adds methods to implement the requirements mentioned earlier. The subclass does not modify the original code, which means $RHCore.RHAttrData can be used in place of $LLIAPI.AttrData without side-effects. The advantages come with the added methods. For example, the SetValue() method can be used to set an attribute value and does the following:

  • validates the value is of the correct type (e.g., date, integer, etc.) and attempts to cast it if not;
  • handles auditing;
  • adds workarounds to common misuse of the API that could otherwise corrupt the category (it's easy to do if you're not careful);
  • automatically extends the row count of any multi-valued set or attribute (when required); and
  • adds the category to the node if not already applied.

It also has a friendlier programming interface. For example:

function Assoc SetValue(Dynamic CatID, Dynamic AttrID, Dynamic value, Integer AttrIndex=1, Integer SetIndex=1)

With this a developer can set a value to an attribute:

Assoc results = attrdata.SetValue(CatID, AttrID, "New Value")

Or, set the 2nd value of a multi-valued attribute on the 3rd row of a multi-valued set:

Assoc results = attrdata.SetValue(CatID, AttrID, "New Value 2", 2, 3)

You'll notice the interface doesn't accept a set ID. The extension determines if an attribute is contained within a set and handles it for you. You just need to provide the attribute ID.

Getting a value is just as easy:

Assoc results = attrdata.GetValue(CatID, AttrID)

if results.ok
	value = results.Value
end

To save the changes:

results = attrdata.commit()

The commit() method makes the appropriate call to llnode.NodeCategoriesUpdate() without having to do any extra DAPINODE or llnode lookups. It also handles auditing without having to manually set the change to the fAttrChangePrefix or fValueChanges features on attrData (which is otherwise necessary for auditing to work).

Many other methods are available for performing operations such as cloning of categories, making an attribute read-only, profiling the properties of the category (e.g., what type of attributes it contains), rendering for a custom form, validating required attributes are fulfilled, etc.

# Attribute Identifiers

See Identifiers on using identifiers with categories and attributes.

# Querying

Querying on Content Server attributes requires complex and complicated joins with LLAttrData. The query also depends on category and attribute IDs, which means a SQL statement must be refactored if it's to be used on another system.

RHCore abstracts this away with a simple extension to RHNodeQuery, which permits filtering by categories and attributes without having to write any SQL. For example, to query the system for all documents having a date attribute value in the future:

Frame query = $RHCore.RHNodeQuery.New(prgCtx) \
				.filter('subtype', '==', $TypeDocument) \
				.filterAttribute(CatID, AttrID, '>', Date.Now())

The filterAttribute() method does all the necessary joins to make the query work, and also accepts identifiers as described in the previous section.

# Fast access

... rewrite this to discuss node.valueForKeyPath('categoryvalues').

Last Updated: 5/20/2019, 11:42:35 AM