A relationship in Gadget is a connection between two models that allows you to link records of one model with the other. Relationships are created using relationship fields like belongs to, has one, has many, and has many through. They enable easy data fetching in the API and help in organizing data in a normalized fashion.
Types of relationships
There are four relationship types available in Gadget:
belongs to relationships are used to express that a record references zero or one other parent record. belongs to represents the side of a one-to-one or one-to-many relationship which stores the id.
has one relationships are part of one-to-one relationships between two models.
has many relationships are part of many-to-one relationships between models.
has many through relationships are used when describing a many-to-many relationship between two models. A has many through requires an in-between join model that has one row per unique
combination of the two related models.
Belongs To relationships
The belongs to relationship establishes a link between a record of one model and a record of another model. It is used when one record is directly associated or categorized under another record. By defining a belongs to relationship, you create a connection that allows easy navigation and retrieval of associated records.
A belongs to relationship can be used to represent a parent-child relationship or one side of a has many or has one relationship. Specifically, belongs to is the side that stores the id value of the relationship. For this reason, you can liken the belongs to relationship to a table storing a FOREIGN KEY in an SQL database.
For example, if you have a Blog Post model representing each post to a blog, you might also have a Comment model that allows readers to discuss each post. In this case, each comment pertains directly to one post, and so we say the comment belongs to the post and create a belongs to relationship from the Comment model to the Post model.
Each Comment record will then have an associated Post object. Different comments can have different posts, but each comment has only one post. belongs to relationships create a one-way relationship from the model they are on to the related model. If you want to
access the relationship from the other way around, to say, access all the comments for a given post, you would need to create a has one or has many relationship
on the other model.
In the example above, a belongs to on the Comment model will create a post field on each Comment object in the GraphQL API that returns that comment's parent Post record. Comment records will also have a postId field if you need to access just the ID of a comment's parent post.
Here's what the setup looks like in Gadget:
Other examples of belongs to relationships include:
a Blog Post record belonging to an Author record in a blogging app
a Product Image record belonging to an Product record in an ecommerce app
a Person record belonging to an Company record in a CRM app
a User record belonging to a Team record in a multi-tenant SaaS app
Has One relationships
has one relationships set up a connection between one record of a model to one record of another
model. has one relationships should be used any time two models are always siblings and always only have zero or one
records on the other side of the relationship.
The has one relationship does not have a direct equivalent in SQL because it doesn't store anything --
it just sets up useful API fields for accessing and mutating a related record.
has one relationship fields work together with the belongs to field to create a one-to-one relationship.
The belongs to side stores the id of the has one side, whereas the has one side
creates a field on the GraphQL API query that returns the related record on the belongs to side.
For example, we might have a User model in an application that may or may not have set up a billing relationship with the
company running the app. In this instance, each user can only have one Billing Account, and each billing account
corresponds to exactly one user. We have a one-to-one relationship, and so we add a belongs to to
the Billing Account model and a has one to the User model:
Here, our User model has oneBilling Account, but nothing referencing Billing Account is stored in the User record.
Instead, the has one of the User model will create a billingAccount field on each User object in the
GraphQL API that returns the related billing account records. Billing Account would have a User column storing the associated record's id.
has one relationships tend to be somewhat rare. If the two models that have a one-to-one relationship have the same
lifecycle, such that they are created and deleted at the same time, it can be annoying to manage them as two separate models. It's often
easier to just combine them into the same model and not have any relationship at all. If the two models in a one-to-one relationship have
different lifecycles, has one can be useful to allow one model to come and go independently of the other.
It can also be difficult to determine which side of the relationship should get the has one and which side should get the belongs to. Usually, it is best to select the model that tends to exist for longer as the owner of the has one. That way, when the model that gets deleted more often gets deleted, you don't have to update the other model that is still around because the belongs to field's value has just been deleted. If there isn't really a pattern between when the two models get created and deleted, then it tends to not matter which side gets which relationship type.
Here's what the setup looks like in Gadget:
The has one and belongs to combination can be used to create non-strict one-to-one relationships.
This is best illustrated with an example. Referring back to the Billing Account and User models, a billing account can only have one
user associated to it, because the belongs to (billing) side only has room for one id. However, no such restriction exists
on the has one (user) side, and its possible for a user to create multiple billing accounts when they should only have one.
This can be fixed by adding a uniqueness validation on the belongs to field that points to the has one side.
Has Many relationships
has many relationships set up a connection between one record of a model to many records of another
model. has many relationships should be used anytime one record owns or categorizes several records of another model.
Like has one, adding a has many doesn't have a direct equivalent in SQL because it doesn't store anything -- it just sets up useful API
fields for accessing and mutating the list of related records.
belongs to and has many work together to represent the one-to-many relationship.
has many relationship fields create a field on the GraphQL API object of the related model which is used to query the belongs to side,
and the belongs to side stores the id of the has many.
In the example of a blog app, we might have a Blog Post model and a Comment model where each comment belongs to a post, and so the Comment model would have a belongs to relationship pointing to the Post model. In this case, we'd want each post record to have many comments so that several different users could write comments. We say that the post has many comments, and create a has many relationship on the Post model linking the Comment model so that in the API, the list of comments from each post can be fetched easily.
In this example, the Comments model will store the Blog Postid. The has many on Blog Post will create a comments field on each BlogPost object in the GraphQL API. This represents a query
which scans all Comments objects to find references to the current post, and returns them all as a list.
Here's what the setup looks like in Gadget:
Another example of a has many relationship could be:
an Author record having many Blog Post records in a blogging app
Has Many Through relationships
has many through relationships set up a many-to-many relationship between two models. has many through relationships should be used when both sides of a relationship
have many records on the other side, or there's no clear owner in a relationship between two models.
Because they represent many-to-many relationships, there is no correct place to put an id reference on either of the related models, as that would only let us model a relationship to one record instead of to many. For this reason, Gadget uses a third, intermediate model, where each record of this third model represents one instance of the relationship between the first two models. Because of this, has many through relationships require more configuration than usual to set up.
For example, let's consider a school registration system where we have a Student model representing each person enrolled and a Course model representing each subject being taught. Students need to enroll in many courses because they take more than one course at once, and courses are taught by teachers who can teach more than one student at once. Each side of this relationship has more than one related record on the other side, so we call it a many-to-many relationship and use a has many through to represent it. We create a third model that sits between Student and Course called Registration, which will have one record for each course that each student has enrolled in.
We can demonstrate this more concretely with data in the student and course relations:
If one student enrolls in three courses, that will generate three Registration records representing those three enrollments. If a second student then enrolls in the same three courses, that will generate three more Registration records. These Registration records have independent lifecycles, so to model a student dropping a course, we'd delete the relevant Registration record and leave the Student and Course records as they were.
In the example above, a has many through on the Student model will create a courses field on each Student object in the GraphQL API that returns that students' list of registered Course records using the Registration model to build the list underneath.
Here's what the setup looks like in Gadget:
another example of a has many through relationship could be:
a Category record having many Product records through Categorization records in an ecommerce app. Since products can be in many categories, and categories can have many products, there must be an intermediate through model to model the many-to-many relationship.
Adding a relationship
As an example, let's create a relationship between a Post model and a Comment model through a has many relationship.
On the Post model, we start by adding a new field called Comments, and assigning it the has manyChildren field type. Note that we call this field Comments, as this field represents zero-to-many Comment records associated with a given Post record.
Then we select Comment which will be the child model of Post:
Once we've selected the Comment model, we can instantly create a belongs to field to reflect this relationship on the Comment model:
And with that, our relationship between Post and Comment is created on both models.
If you look at the data shape of our Comment model in the API reference, you'll notice that there is a postID field present, even though you didn't manually create this field. When a relationship is created between two models, Gadget automatically creates an ID field to store the foreign keys needed to link instances of the related models to one another.
GraphQL
type Comment {
"""
The time at which this record was first created. Set once upon record creation and never changed. Managed by Gadget.
"""
createdAt: DateTime!
"""
The globally unique, unchanging identifier for this record. Assigned and managed by Gadget.
"""
id: GadgetID!
"""
The time at which this record was last changed. Set each time the record is successfully acted upon by an action. Managed by Gadget.
"""
updatedAt: DateTime!
post: Post
postId: GadgetID
"""
Get all the fields for this record. Useful for not having to list out all the fields you want to retrieve, but slower.
"""
_all: JSONObject!
}