Relationships
What is a relationship?
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. Examples: books belong to one author, blog comments belong to one blog post. belongs to denotes the inverse of a has many or has one relationship.
- has one relationships are for one-to-one relationships between two models.
- has many relationships are for many-to-one relationships where one parent record is referenced by many children records. Examples: blog posts have many comments, authors have many books
- has many through relationships are used when describing a many-to-many association between two models. Examples: students can have many courses, and courses have many students; in ecommerce, a single product can be categorized into multiple categories, while each category holds many products. 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.
When adding a belongs to relationship to a Gadget model, it is similar to adding a something_id column and a FOREIGN KEY constraint to a table in an SQL database. This relationship represents ownership or categorization between the two models, where one record belongs to another.
By defining a belongs to relationship, you create a connection that allows easy navigation and retrieval of associated records. This relationship is particularly useful when modeling hierarchical or parent-child relationships in your data.
In summary, belongs to relationships provide a way to establish ownership or categorization connections between records in different models, similar to how a FOREIGN KEY constraint links tables 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 relationship field on the other model. belongs to powers one model's side of a one-to-many or one-to-one relationship so that you can use a has one or has many on the other model to govern how many related records the other model can have.
belongs to relationships store an id value in the Gadget database under the hood. This value powers the belongs to relationship itself and allows Gadget to fetch the related record when asked. It also powers the has many or has one relationships that might be on the other side.
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.
has one relationship fields represent one side of a one-to-one relationship while belongs to represents the other. You must use a belongs to on one model -- you can't use two has one fields or two belongs to fields. This is because Gadget needs to know which side of the relationship to store the ID reference data.
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:
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.
In the example above, a has one on the User model will create a billingAccount
field on each User
object in the GraphQL API that returns that user's singular BillingAccount
record.
Here's what the setup looks like in Gadget:
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. 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.
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.
has many relationship fields create a one-way relationship from the model they are on to a related model, and they require a belongs to relationship field on the related model to work. This is because they are powered by the data stored in the other model's relationship. For example, the list of comments for a particular blog post is determined by looking at all the comments and seeing which ones have a stored reference to the post in question in their belongs to relationship field. has many and belongs to together power a one-to-many relationship.
In the example above, a has many on the Blog Post model will create a comments
field on each BlogPost
object in the GraphQL API that returns that post's list of Comment
records.
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 connection between two models where records on each side of the relationship can have many related records on the other side. 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.
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 many Children 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.
1type Comment {2 """3 The time at which this record was first created. Set once upon record creation and never changed. Managed by Gadget.4 """5 createdAt: DateTime!6 """7 The globally unique, unchanging identifier for this record. Assigned and managed by Gadget.8 """9 id: GadgetID!10 """11 The time at which this record was last changed. Set each time the record is successfully acted upon by an action. Managed by Gadget.12 """13 updatedAt: DateTime!14 post: Post15 postId: GadgetID16 """17 Get all the fields for this record. Useful for not having to list out all the fields you want to retrieve, but slower.18 """19 _all: JSONObject!20}