The Data Model script in BYRD consists of definitions for
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.
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!
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.
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!
Class | Description |
---|---|
String | Simple string |
Integer | 64-bit integer number |
Float | 64-bit floating point number |
Date | Date value, without time part |
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])
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.
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'
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
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
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'
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 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 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,
]
}
]
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
]
}
]
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
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.
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.