Skip to content

spawn relations

Harmen Janssen edited this page Dec 10, 2015 · 5 revisions

Relations

Relations between records in the database are bidirectional. They are stored in different fashions: hasOne, belongsTo, hasMany and hasAndBelongsToMany.

Relations of the hasOne and belongsTo type are configured in the Spawn model config. Relations of the hasMany type cannot be configured at all, but are instead deduced. All the many-to-many relations of the hasAndBelongsToMany type are configured in a single separate file.

Please see the elaborated explanation per type below for details.

Many-to-one relationships (foreign key column is in this model)

hasOne versus belongsTo

To define a relationship from multiple records of this model to one record of another (or the same) model, you can use the hasOne or belongsTo relation type. The difference between hasOne and belongsTo is one of dependence. Consider these cases:

User hasOne Category

This will result in a User.category_id relation column in the database, and a Category dropdown in the User cms edit form. The User will have an optional Category, which can be assigned to her, changed and be unassigned.

Profile belongsTo User

This will result in a Profile.user_id relation column in the database, and a User dropdown in the Profile cms edit form. The Profile will have a required User. This related User can be changed into another User, but cannot be unassigned. There always has to be a User attached. NOTE: In addition to hasOne, a belongsTo relationship results in the Profile being deleted when the related User is deleted.

Many-to-one (and one-to-many) relation properties

The relation properties with their defaults:

type

the only mandatory property

name

indicated by the key name to this relation object

label

friendly model name to display in cms

model

[relation name] Model name, in case this does not correspond with the relation name

weighable

false Whether records of this model should be custom sortable in the cms

inverse

true Whether a hasOne or belongsTo relationship from A to B should be automatically inverted to a hasMany relationship from B to A. See also preventing relation inversion

editable

true

Whether this relationship can be edited in the cms

visible

true

Wether this relation is visible in the CMS

info

A human-friendly explanation or note to accompany this field in the cms

required

Whether this relation is required. By default, only belongsTo relations are required. By setting this parameter to false, even belongsTo relations can be made optional, so that it can still benefit from cascading parent events (on delete and on change), as opposed to hasOne relations. Also, you can require a hasOne relation with this parameter. This results in a required CMS field, but an optional column from a database perspective.

primary

When true, foreign keys created from this relation will become the table's primary key.

max

Limits the amount of possibly related records. Only enforced by the frontend interface!

simpleSelect

false

Render as a simple select box, no modelPicker popup is shown

inline

false

Render as an inline grid; records can be made, related and unrelated from inside a form in a simple grid like UI

One-to-many relationships (foreign key column is in remote model) One-to-many or hasMany relationships are not configured in the Spawner configuration files. Instead, since relationships are bidirectional in Garp, they derive from their opposite hasOne or belongsTo relationship.

Consider the following: Child hasOne Father Father hasMany Child

The Child model would have this relationship configured:

{
	"relations": {
		"Father": {
			"type": "hasOne"
		}
	}
}

Which will result in a Child.father_id database column and accompanying foreign key. Also, a Father record will now have a Child relation tab in the cms. From a Php perspective, the Father model will be aware of the relationship with Child.

Preventing the automatic inversion of relationships Sometimes, you don’t want a hasOne or belongsTo relationship from model A to B to be inverted to a hasMany relationship from model B to A. In that case, just use the boolean inverse property in the relationship definiton. As follows, in model A:

{
	"relations": {
		"B": {
			"type": "hasOne",
			"inverse": false
		}
	}
}

Many-to-many relationships

Many-to-many relationships, or hasAndBelongsToMany, are defined in the configuration of the alphabetically first model of the relation.

A simple example:

{
	"relations": {
		"Video": {
			"type": "hasAndBelongsToMany"
		}
	}
}

In its simplest form, the hasAndBelongsToMany configuration files consists of a string array. The strings are composed of both models in the relationship, in alphabetical order, separated by an underscore.

Weighable (custom sortable) many-to-many relationships

A slightly more complex configuration is needed when a hasAndBelongsTo relationship should be weighable, that is to say, sortable by a cms user in a custom and storable fashion.

{
	"relations": {
		"Video": {
			"type": "hasAndBelongsToMany",
			"weighable": true
		}
	}
}

Extra input fields in many-to-many relationships

Sometimes, just connecting two records is not enough. If you also want extra columns in the binding model table, add an inputs node to the relation configuration, just like you would configure the inputs in the model itself.

{
	"relations": {
		"Video": {
			"type": "hasAndBelongsToMany",
			"inputs": {
				"rating": {
					"type": "numeric",
					"required": false
				},
				"modified": {
					"type": "datetime"
				}
			}
		}
	}
}

For the syntax of the inputs configuration within a relation context, you can just refer to the regular inputs configuration in the model.

Important At the time of writing, the inputs configured in a relation can only be optional text-fields. It's possible to tweak this a little in the extended model, see for instance the Cinema model in the We Want Cinema project.

Also, you always need to add the following configuration to the extended model:

this.addColumn({ dataIndex: "relationMetadata", hidden: true, virtual: true });

Three-way many-to-many relationships

An example of such a relation:

A geographical ZipcodeArea gets a Discount at the pizza place, but the discount varies on a Yearly basis.

A theoretical intersection table looks like this:

zipcode_area_id INT NOT NULL,
discount_id INT NOT NULL,
year_id INT NOT NULL,
PRIMARY(zipcode_area_id, discount_id, year_id)

Right now a HABTM relation can only occur between max TWO models. You can cheat the system by creating a regular model that acts as the bindingModel:

{
  "listFields": ["zipcode_area_id"]
  "inputs": {},
  "relations": {
    "ZipCodeArea": {
      "type": "hasOne",
      "primary": true
    },
    "Discount": {
      "type": "hasOne",
      "primary": true
    },
    "Year": {
      "type": "hasOne",
      "primary": true
    }
  }
}

Note that this will get you a semi-regular model, so it will get an id column, as well as created and modified columns. But that's okay, it's a workable intersection model.

Conventionally this model should be prefixed by an underscore, to match the automatically generated binding tables.

Future improvements would include allowing a via property of a relation, to specify a third (or nth) model(s) to be included in the intersection.

Meta properties

Next to inputs and behaviors, a model can also be fitted with some optional meta properties in a Spawner model configuration.

listFields

The mostly optional listFields property defines which fields are displayed by default in the cms, wherever the context of a list is appropriate.

{
	"listFields": ["name", "posse"]
}

If not configured, Spawner will look for usable columns such as name or a first name and last name combination, in the case of person type records.

order

The optional order property enables you to set a default order in which records of this model should be displayed. It is configured as a MySql value.

label

A human-friendly label, to use as the model name in the cms. In case of a single language site, this can also be used for translation.

route

An optional (relative) url route. (...)

creatable

Optional boolean property that indicates whether a record of this model can be inserted by a user in the cms.

deletable

Optional boolean property that indicates whether a record of this model can be deleted by a user in the cms.

quickAddable

Optional boolean property that indicates whether a record of this model can be inserted from a relation panel by a user in the cms. When true, in the case of relating a Video record to the Blog record that is being edited, a New Video button is presented in the Video relate panel.

module

In case of ‘default’

Optional string property that defaults to "default". For both Php and Javascript, a base model will be generated, as well as a layer on top of that: the extended model.

  • create or overwrite php base model, deriving from the generic Garp_Model_Db
  • create php extended model, if it does not exist
  • create or overwrite javascript base model
  • create javascript extended model, if it does not exist

In case of ‘garp’

When set to "garp" however, the generated Php base model will be derived from the Php model in the Garp namespace that carries the same name. On the Javascript side, there will not be a generated base model in the Application scope at all. The extended model is loaded, and next to that the Garp Javascript model carrying the same name will be loaded.

create or overwrite php base model, deriving from G_Model_MyFunkyModel create php extended model, if it does not exist include Garp javascript model from js/garp/models/MyFunkyModel.js create javascript extended model, if it does not exist

visible

Sometimes you’ll want to Spawn a module that should not be visible in the cms at all. That’s where the visible property comes in; just set it to false.

unique

You can set the unique property of an individual field, but if you want to combine multiple fields into a uniquified index, you should set the unique property of the model itself. This takes an array of column names.

Clone this wiki locally