BYRD - Administrator’s and Developer’s Guide

Data Model

The Data Model script in BYRD consists of definitions for

  • Types and Constants
  • Attributes
  • Categories
  • Layouts
  • Validations

Further it can include a couple of special definitions used for role handling and publication.

A good way to see those definitions in action is the "Sandpit" demonstration model. For a comprehensive real-world example you should consult the "Food Regulation EU/1169" data model.

Ruby module

All Data Model definitions must be placed inside it's own Ruby module. Simply choose a Ruby compliant module name which describes your data model best, and extend the BYRD API's 'PIM' module.

module MyDataModel extend PIM
end

It is important that the 'extend PIM' statement is specified at the beginning of the module, for otherwise it would not be possible to further define the data model!

Types and Constants

You should start your data model by defining types and constants which you want to use later on. Types are actually 'attribute templates' which can be used to define options and parameters once and reuse them for multiple attributes.

To ease the definition of option based attributes, the following methods exist:

Method Description
Enum Defines a fixed list of options for single valued attributes.
EnumSet Defines a fixed list of options for multiple valued attributes.
OpenEnum Defines an open list of options for single values attributes.
OpenEnumSet Defines an an open list of options for multiple valued attributes.
Boolean Like 'Enum', but with a default list of options typically used for booleans. Note that attributes of this type are rendered by radio buttons instead of a selection list, so the list of options used here should not become to large.
Physical Defines a list of physical units for attributes containing physical values (quantity + unit).
Dimensional Defines a fixed list of 'dimensions', allowing an attribute to contain one single value per dimension.

Each of these methods has exactly one parameter, which is a hash denoting the option key and a default label.

Also you can make use of other system 'type' methods which don't take a parameter:

Method Description
Image Used for images, i.e. binary documents which are represented as image in the UI
Document Arbitrary binary documents

Don't forget to add empty braces for these, because they are like the option types actually methods and not constants!

Examples:

# Simple Enum type allowing only the specified options
SimpleEnum = Enum(:a => 'Option A', :b => 'Option B', :c => 'Option C', :d => 'Option D')

# Simple Boolean with 3 states
ThreeStateBoolean = Boolean(:yes => 'Yes', :no => 'No', :maybe => 'Maybe')

# Simple length based physical
Length = Physical(:MM => 'mm', :CM => 'cm', :DM => 'dm', :M => 'm')

# Simple image type
Image = Image()

Keep in mind that 'type' definitions are simply Ruby constants which cannot be retrieved outside the data model definition. This is in contrast to attribute, category and layout definitions which are available e.g. in the UI.

Attributes

Attribute definitions need at least to parameters: An attribute name, typically a valid Ruby symbol, and a parent. A parent is either a base class, a user or system defined 'type' or a previously defined attribute. Since the system already defines a couple of attributes, do not append '__' to your attribute's name!

Base classes

Class Description
String Simple string
Integer 64-bit integer number
Float 64-bit floating point number
Date Date value, without time part

Option types

See Types and Constants

Complex types

Additionally an attribute can be defined as a complex 'attribute container', which itself holds other attributes.

Method Description
Composite A simple composite of other attributes
Collection A collection of composite attributes
MultiDimensional A 'double' composite attribute, defining a list of 'key' attributes and a list of 'value' attributes

Examples:

attribute :simple_string, String
attribute :simple_image, Image()
attribute :simple_enum, SimpleEnum
attribute :composite, Composite(:simple_string, :simple_enum)
attribute :collection, Collection(:composite)
attribute :multi_dimensional, MultiDimensional([:key1, :key2], [:value1, :value2])

Attribute parameters

You can optionally define certain 'parameters' which can influence the way the attribute is represented in the UI or add implicit validations. Parameters are added inside a Ruby 'block', which is simply added after the attribute definition ('do ... end'). Each parameter can only be specified once, otherwise it overwrites the previous definition.

UI parameters:
Parameter Description
label A default label, used if no translation is specified
default_value A default value, used when an item is created
formatter A method to format a Ruby value to be displayed in the UI
parser A method to parse a value from the UI to be used in Ruby
renderer A hash containing further paramters used by the UI's 'custom template' mechanism

The 'label' and 'default_value' attribute parameters can also be specified as Ruby paramters after the parent paramter.

Examples:

attribute :my_string, String do
  label 'My String'
  default_value 'ABC'
end
attribute :my_text, String do
  renderer({ :type => 'TextArea', :rows => 5 })
end
attribute :my_integer, Integer, 'My Integer'
attribute :my_value, String, 'My Value', 'X'
Validation parameters:
Parameter Description
is_mandatory Attribute value is mandatory
has_checksum Validates using a 'EAN' checksum calculation
length Either a maximum character length of the value, or - if specified as array - a list of allowed exact length values
regexp A regular expression to check
range A numeric Ruby range to validate a numeric value for
min_value The minimal allowed numeric value
max_value The maximal allowed numeric value
min_value_excluding Same as min_value, but excluding the specified value
max_value_excluding Same as max_value, but excluding the specified value
keys A list of allowed keys for Hash like types (implicitly used by Dimensional and Physical types)
key_class The Ruby class allowed for Hash like types (implicitly used by MultiDimensional types)
values A list of allowed values for Array like types (implicitly used by all variants of Enum types)
value_class The Ruby class allowed for Hash and Array like types (implicitly used by Dimensional and MultiDimensional types)

Examples:

attribute :string1, String, 'String1' do
  is_mandatory
  length 10
  regexp /^[A-Z]*$/
end
attribute :gtin, String, 'GTIN' do
  is_mandatory
  has_checksum
  length([8, 12, 13, 14])
  regexp /^[0-9]*$/
end
attribute :amount, Integer, 'Amount' do
  min_value_excluding 0
end

Categories

Data model categories are defined by Ruby classes which either directly or indirectly extends the 'PIM::Item' class. In the category class you can add attributes and specify how the primary key of this class is derived. A category class can also extend another category class, thus inheriting all attribute and primary key definitions of the parent class.

To define a category class, the following methods are inherited from 'PIM::Item':

Method Description
attribute Adds a single attribute or a list of attributes - previously defined in the data model - to the item class
label Sets an optional label, used if no translation entry exists
hidden If set 'true', the category does not show up in the category list
primary_key Specifies the primary key definition (see 'Primary key definition')

Examples:

class MyBasicItem < PIM::Item
  label 'My Basic Item'
  hidden true
  attribute :gtin
  attribute :name
  primary_key :gtin
end

class MyDerivedItem < MyBasicItem
  label 'My Derived Item'
  attribute :amount, :length
end

Primary key definition

The primary key definition can either be specified by a list of attributes, it derives from, a block which dynamically creates the primary key value, or both.

When a list of attributes is specified, the corresponding attribute values are concatenated with a ':' character.

Examples:

class MyItem < PIM::Item
  attribute :gtin
  attribute :gln
  attribute :target_market
  primary_key :gtin, :gln, :target_market
end

item = MyItem.new
item.gtin = '123'
item.gln = '456'
item.target_market = '276'
item.primaryKey__ == '123:456:276'

When a block is specified, it is expected to 'return' the calculated primary key. If also a list of attributes is provided, the result of the block is appended to automatically created primary key.

Examples:

class MyItem1 < PIM::Item
  attribute :gtin
  attribute :gln
  primary_key do |values|
    "#{values['gtin']}-#{values['gln']}"
  end
end

item1 = MyItem1.new
item1.gtin = '123'
item1.gln = '456'
item1.primaryKey__ == '123-456'

class MyItem2 < PIM::Item
  attribute :gtin
  attribute :gln
  attribute :target_market
  primary_key :gtin, :gln do |values|
    "#{values['target_market'].rjust(3, '0')}"
  end
end

item2 = MyItem1.new
item2.gtin = '123'
item2.gln = '456'
item2.target_market = '72'
item2.primaryKey__ == '123:456:072'

Category calculation

To determine the correct item category for an imported item, where the category is not explicitly set, you can optionally define a Ruby block.

class MyBaseItem < PIM::Item
  ...
  attribute :base, Boolean()
end
class MyPackageItem < PIM::Item
  ...
  attribute :package, Boolean()
end
class MyPalletItem < PIM::Item
  ...
  attribute :pallet, Boolean()
end

category_calculation do |values|
  if values['pallet'] == 'true'
    MyPalletItem
  elsif values['package'] == 'true'
    MyPackageItem
  else
    MyBaseItem
  end
end

System attributes

System attributes are already attached to the 'PIM::Item' class and thus inherited by all data model category classes. To distinguish system attributes from user defined attributes, system attributes are suffixed with two underscore characters ('__'). So when defining an attribute in your data model, please do not add this suffix to any attribute name!

These are the main system defined attributes:

Attribute Description
category__ The category class name of the item, prepended with the module name (e.g. 'MyDataModel::MyBaseItem')
primary_key__ The item's derived primary key
createdAt__ The date and time the item was created
updatedAt__ The date and time the item was last changed
checksum__ A md5 checksum calculated over all attributes
compliant__ A 'true'/'false' value, indicating if the item is compliant to all validation rules
published__ A 'true'/'false' valuea, indicating if the item has ever been published
publishedAt__ The date and time the item was last published
validation__ A string list of validation rules, which failed to validate for the item

Please note that this list is not complete. Certain other attributes exists, but are just used for internal reasons.

Layouts

Layouts define which attributes of an item are displayed in the UI. Two types of layouts exist: A simple 'row' based layout and a complex 'tab' based layout. A 'row' based layout is used to define a list of attributes to show in a grid of items, while the 'tab' based layout can further organize the attributes in different 'sections'.

To determine which layout to use in a UI component, you can either define your layout with the corresponding UI component's mapping name, or define a mapping block, which dynamically determines the layout to use, depending on the UI component and the user.

The following default mappings exist:

Mapping Description
browse Used in the item library grid
edit Used in the item editor
catalog Used in the item catalog grid
detail Used in the item catalog default view
mapping Used in the item import mapping list

A layout is defined by specifying a name and a list of sections. Each section consists of a section name, a section label and a mandatory list of attributes. Note that for the 'browse', 'catalog' and 'mapping' layouts, only the first section is used, thus the section name and label can be omitted.

Examples:

layout 'browse', [
  {
    :attributes => [
      :gtin,
      :gln,
      :target_market
    ]
  }
]

layout 'edit', [
  {
    :name => 'OVERVIEW',
    :label => 'Overview',
    :attributes => [
      :gtin,
      :gln,
      :target_market
    ]
  },
  {
    :name => 'ADDITIONAL',
    :label => 'Additional',
    :attributes => [
      :length,
      :price,
      :amount,
    ]
  }
]

Section attributes

The list of attributes for a section can either contain simple attribute names - or symbols -, or a more complex attribute section definition, specified as hash. This 'section action' hash must at least contain a 'name' parameter, to identify the attribute to use. Other usable parameters are:

Parameter Description
readonly Set 'true' or 'false, if the attribute should not be editable in this section
labelWidth The width of the label part when displaying the attribute in an editor component
inputWidth The width of the input or value part when displaying the attribute in an editor component

By using the 'width' parameters for a section attribute, you can manipulate how much space either part of the attribute is provided in an editor component. The total amount of space per row is 12, with a default labelWidth of 4 and a default inputWidth of 8. Note that a single row is always filled up with a maximum of 12 spaces, so it is possible to show multiple attributes in one row. It is also possible to 'hide' the attribute label or the value by setting it's corresponding width to 0.

Examples:

layout 'sample', [
  {
    :attributes => [
      :name, # Attribute 'name' displayed with 4 spaces for the label and 8 spaces for the input component
      { :name => :description, :inputWidth = 6 }, # Show a smaller input component ....
      { :name => :checkbox, :labelWidth => 0, :inputWidth => 2 } # ... so that the next attribute is display on the same line
    ]
  }
]

Layout mapping

The optional layout mappping block is given two arguments: The user's name and a hash containing the default mapping to use. This way you can define different layouts for a UI component and map them depending on the user's role.

layout 'readonly_edit', [
  {
    ...
    :attributes => [
      { :name => :gtin, :readonly => true },
      ...
    ]
    ...
  }
]

layout_mapping do |user, mapping|
  # User only has the 'viewer' role
  if (user.get_roles - %w[viewer]).empty?
    mapping.edit = 'readonly_edit'
  end
end

Validations

Additionally to the "implicit" attribute validations defined by attribute parameters, it is possible to define more complex validation rules for one or more attributes.

A simple validation rule is defined by a unique name, a list of attributes to validate and a Ruby block to do the actual validation. The block itself has a single parameter of the class 'ValidationContext', which contains methods to retrieve the value to validate, as well as all other values of the item and some other "helper" methods.

# Validation rule to ensure that value1, value2 and value3 are not equal to valuex
validation :simple_validation, :value1, value2, value3 do |c|
  next if is_empty?(c.value) # Don't validate, when the value to validate is empty
  c.failure "Attribute #{c.attribute.label} must not be equal to attribute 'ValueX'" if c.value == c.values[:valuex]
end

When you change an attribute value in the item editor, all validation rules defined for this attribute are evaluated. But if a validation rule depends on a different attribute value, this rule will be left out, because there is no dependency between a rule and the attribute values it is using. To overcome this shortcoming, there is another you can define a validation rule.

This complex definition only takes a argument - the name of the validation rule - and a block, which itself consists of 3 parts: The attribute or list of attributes which are validated, an attribute or a list of attributes the rule depends on, and a validation block, just like in the simply validation rule definition.

validation :dependent_validation do
  validate_attributes :value1, :value2, :value3
  depends_on :valuex
  validate do |c|
    next if is_empty?(c.value) # Don't validate, when the value to validate is empty
    c.failure "Attribute #{c.attribute.label} must not be equal to attribute 'ValueX'" if c.value == c.values[:valuex]
  end
end

For better readability you can also use "validate_attribute" and "depend_on" instead of "validate_attributes" and "depends_on" respectively.

Other

Roles

Using the data model you can also define "roles", which you can assign to users. The "roles" method takes a Ruby Hash object as single parameter, where the key defines the role's name, and the value part is a list of system defined rights.

Example:

roles({
  :viewer => [
    "edit.items",
    "view.catalog",
    "view.dashboard",
    "view.tasks",
    "view.upload",
  ]
})

For the existing system rights and their usage, please see User Administration -> Privileges.