Associations with Pop: 1 to n

--

Model relations with Pop are difficult to understand. Let’s fix that.

In this second article, you’ll learn how to properly write a one to many association and how to use it in a real context. But before going further, what’s a one to many association?

Photo by Miguel Á. Padriñán from Pexels

Definition

Note: If you want to learn more about 1 to 1 associations before heading to 1 to n ones, you can find the first article of this series here:

In database theory, we’re used to name “entities” the objects we work on. These entities can have a different number of relations. Today, we’ll focus on the 1 to n (or 1 to many) association: this relation links one object to many others.

For instance, the relation between a fruit and a tree is a 1 to n relation: “a fruit is attached to one and only one tree”, but “a tree can produce 0, 1 or as many fruits as it can”.

A standard notation of the previous example.

The “1” on the drawing is the same as the “1” in the 1 to 1 relation. It means you can only have a single relation in this side, and this relation is mandatory (a fruit can’t just appear without growing on a tree! — or maybe, if you try with some bananas and a microwave).

The “0..n” notation is new here: it describes a range, saying you can have 0 to as many objects as you want (that’s what “n” means). If I replace the “0” with a “1” (so “1..n”), it’ll mean you can’t have a tree if it doesn’t have a fruit on it (“1” have a mandatory value, here).

“1” can also be written “1..1”, but why would you want to write something longer?

How to use it with pop

Note: the following code can be found in a full example here: https://github.com/stanislas-m/pop-associations/tree/master/onetomany

Pop deals with relations using meta tags you can attach on your model structs: “has_many” and “belongs_to”. Before using them, we have to create the matching tables in the database:

Database migrations

First, let’s define our tree and fruit as tables, using fizz migrations:

create_table("trees") {
t.Column("id", "integer", {"primary": true})
t.Column("name", "string", {})
t.DisableTimestamps()
}
create_table("fruits") {
t.Column("id", "integer", {"primary": true})
t.Column("tree_id", "integer", {})
t.ForeignKey("tree_id", {"trees": ["id"]}, {})
t.DisableTimestamps()
}

So a tree has an id (it’s primary key) and a name (“Apple tree”, for instance). A fruit has its own id, and a reference to a tree id which we enforce using a foreign key.

For both tables, I disabled the auto-timestamp feature because I don’t need it here.

has_many

Now that your tables are properly created in the database, we can map them to proper entities:

type Fruit struct {
ID int `json:"id,omitempty" db:"id"`
TreeID int `json:"-" db:"tree_id"`
Tree *Tree `json:"tree,omitempty" belongs_to:"tree"` }
type Tree struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Fruits []Fruit `json:"fruits,omitempty" has_many:"fruits"` }

A one to many relationship is a relation between a single entity and a collection (or slice, in Go) of entities. We then need to use different struct tags to define both sides of the relationship. Here’s how it works:

This has_many tag is more or less a plural version of has_one: it defines the following side, i.e. the one letting the owning side do the relationship work.

In our example, the following side will be the tree, since the table doesn’t contain an extra field for the relation. As a reminder, you can say “the tree has many fruits”.

That’s it! You can now use the relation in a query. :)

Query Usage

Before fetching some data, we need to insert it into the database:

// Create some fruits
fruits := make([]models.Fruit, 5)
for i:= 0; i < 5; i++ {
fruits[i] = models.Fruit{}
}
b := &models.Tree{
Name: "Tree",
Fruits: fruits,
}
if err := c.Eager().Create(b); err != nil {
return err
}

In this piece of code, we created 5 fruits, then a tree and we attached the 5 fruits to the tree. Thanks to the Eager() modifier, the Create() method saved the whole tree with its fruits in the database!

Now we have some data in the database, let’s try to fetch some trees:

trees := &models.Trees{}if err := c.All(trees); err != nil {
log.Printf("err: %v", err)
return
}
log.Printf("basic fetch: %v", trees)

Here’s an example with 3 trees:

2019/06/11 20:18:18 basic fetch trees: [{"id":1,"name":"Tree 0"},{"id":2,"name":"Tree 1"},{"id":3,"name":"Tree 2"}]

As you can see, the trees were fetch without the apples. To fetch the trees with their apples, we need to use the Eager() modifier:

trees = &models.Trees{}if err := c.Eager().All(trees); err != nil {
log.Printf("err: %v", err)
return
}
log.Printf("eager fetch: %v", trees)

And here is the new result:

2019/06/11 20:18:18 eager fetch trees: [{"id":1,"name":"Tree 0"},{"id":2,"name":"Tree 1","fruits":[{"id":1}]},{"id":3,"name":"Tree 2","fruits":[{"id":2},{"id":3}]}]

This is the end of this second article about the 1 to n associations with Pop. The next article will cover the last kind of association: n to n (or many to many).

--

--