The content-type system in LocomotiveCMS allows you to define and use various types of relations between models.
Let's go through classical Author <-> Book example to learn each relation type provided by LocomotiveCMS. First we're going to have the very basic models of Authors and Books containing only name fields:
bundle exec wagon generate content_type authors name:string
bundle exec wagon generate content_type books name:string
1. Belongs To: the Book belongs to the Author
With this type of relation we can store a link to an Author along with the Book, which immediately becomes callable as a property of the book instance:
Who wrote the book "{{ book.name }}"? {{ book.author.name }} did that!
"Belongs To" is the easiest relation to set up. You just need to add the following to content_types/books.yml
:
- author:
label: Author
type: belongs_to
class_name: authors
The key author
here is a property that we will call on a book instance. It does not have to match the name of related model. For example, we could name our relation writer
.
The class_name
parameter (vice versa) must contain the slug of the model; otherwise, we couldn't know which model exactly we want to bind here.
Also, we may want to add the required: true
option if the book without an author shouldn't exist in our library.
Ok. Once we're done, we may want to add some records to see how it works.
Let's add the following to the data/authors.yml
:
- "Leo Tolstoy"
- "Anton Tchekhov"
...and these records to the data/books.yml
:
- "War and Peace":
author: Leo Tolstoy
- "The Cherry Orchard":
author: Anton Tchekhov
- "Anna Karenina":
author: Leo Tolstoy
Now if we add the following snippet to the index page...
{% for book in contents.books %}
Who wrote the book "{{ book.name }}"? {{ book.author.name }} did that!
{% endfor %}
...we'll see the list of the books with their authors:
Who wrote the book "War and Peace"? Leo Tolstoy did that!
Who wrote the book "The Cherry Orchard"? Anton Tchekhov did that!
Who wrote the book "Anna Karenina"? Leo Tolstoy did that!
As you may notice it's perfectly legal to create an arbitrary amount of Books pointing to one Author. And this brings us to the next type of relation.
2. Has Many: the Author has many Books
It is quite unusual for the Author to have only one Book published. That's why we need another type of relation to describe how Books are connected to the Author.
We did most of the work for establishing has_many
relation in the last section. From the point of view of the root object this type of relation is no more than an inverted belongs_to
. This structure is quite obvious: book belongs to author and author has many books belonging to the author. That's it! We've come full circle.
Let's add a books
property to content_type/authors.yml
:
- books:
label: Books
type: has_many
class_name: books
inverse_of: author
ui_enabled: true
Some new parameters are introduced here. The parameter inverse_of
specifies which property of the foreign model is used to establish the belongs_to
relation with the current model. Its name is self-explanatory: what property of the RELATED model would be an inversion of the property that we're going to define on THIS model
?
Or what property of the related model could tell us if we're connected
?
It makes perfect sense if you just imagine the Tolstoy
record asking Anna Karenina
: "Is that me who has created you?"
The last parameter ui_enabled
allows you to select whether the related item should be editable right from the editing screen of the current model in the admin panel. If you turn on this option you'll be able to conveniently edit or create related items during editing or creation of the root item.
Let's take a look at how it works. To do so, we'll take the first author which happens to be Tolstoy
and ask him which books he wrote:
{% for book in contents.authors.first.books %}
Who wrote the book "{{ book.name }}"? {{ book.author.name }} did that!
{% endfor %}
The output would be the following, as expected:
Who wrote the book "War and Peace"? Leo Tolstoy did that!
Who wrote the book "Anna Karenina"? Leo Tolstoy did that!
3. Many to Many: the case of the Book written in collaboration
Let's imagine that Tolstoy and Tchekhov wrote a book in co-authorship which in fact never happened even though they lived in the same time. But for learning purpose it would be fun to think that their partnership brought a brilliant book called "Anna Karenina in the Cherry Orchard" to the world.
It breaks our perfect world in no time. Now one Author can have many Books, and one Book can be written by a group of Authors. This type of relation is called Many-to-Many and it is considered the most complex relation.
Let's make some changes in content_type/books.yml
- authors:
label: Author
type: many_to_many
class_name: authors
inverse_of: books
ui_enabled: true
...and content_type/authors.yml
- books:
label: Books
type: many_to_many
class_name: books
inverse_of: authors
ui_enabled: true
That's it. Now we can attach as many Books to each Author as we want, and vice versa.
The only thing left is to take a look at the seed data for this kind of relation (data/books.yml
):
- "War and Peace":
authors: [Leo Tolstoy]
- "The Cherry Orchard":
authors: [Anton Tchekhov]
- "Anna Karenina":
authors: [Leo Tolstoy]
- "Anna Karenina in the Cherry Orchard":
authors: [Leo Tolstoy, Anton Tchekhov]
Now if we take the last book and iterate over authors, we will see Leo Tolstoy
and Anton Tchekhov
:
{% for author in contents.books.last.authors %}
{{ author.name }}
{% endfor %}
will output
Leo Tolstoy
Anton Tchekhov
4. Tags: special relation
Locomotive CMS has embedded support for tags. To use them, you just have to add a field of type tags
to your model. Then you'll be able to scope by tags, like this:
{% with_scope tags: params['tag'] %}
...
{% endwith_scope %}
A common mistake with relations involves mistreatment of the property. Just remember, that for any relation except the belongs_to
you have a collection even though it consists of only one element.