Once you have defined relationships between your services, you can combine service builders together to include related association data when fetching
a given type of data. This is particularly helpful as you avoid the trouble of making many joins or sub queries yourself. It will also aggregate the data based on the nature of
the relationship and the aliases you have provided.
All the examples below will use our test database with the following service definitions
.include(...associations: InclusionInput)
The include method lets you query for as many related table data as you want.
An inclusion input can be one of the following:
- Service select builder
- Service: in this case it will be equivalent to
Service.select()
- An alias string as specified when you have created your relations. Again it will be equivalent in this case to
Service.select()
Posts
.select()
.include(Tags.select(), Comments, 'author')
.run()
.then(posts => {
});
If you use the version with select builders, you have to understand that any clause will be scoped to the given builder.
It makes it particularly easy to reason when you are building your query, especially if you want to paginate or filter your result: you don't have to worry
whether you are going to miss some data if you do not apply the limit
clause to the appropriate builder for example.
It just translates what you would say in plain English.
I want to get the ten latest posts including for each the author name and the three last published comments.
It may seem pretty easy, but it is actually not trivial SQL. Worst, some ORMs you think you could rely on would give you wrong result.
With ship-hold, it is easy. You just have to write your builders how you would say it in plain English.
const authors = Users.select('user_id', 'first_name', 'last_name');
const lastThreeComments = Comments
.select()
.orderBy('published_at', 'desc')
.limit(3);
Posts
.select()
.orderBy('published_at', 'desc')
.limit(10)
.include(authors, lastThreeComments)
.run()
.then(lastTenPosts => {
});
You have to include in your select clauses the required primary keys and foreign keys. ship-hold does not know much about your table columns, it just does what you are asking for.
You can also perform nested include. However the cost on the database will be higher although ship-hold performs quite well.
One last things good to know is that you can overwrite the aliases defined by your relations providing instead of a builder an object
with the value
property and the as
property. It is particularly handy if you want to include several times the same service with different filter clauses
I want to get details for user Laurent Renard including its comments split into two collections: an old one with comments older than 2010, and a recent one with comments published after 2010.
All comments should come with the title and the id of the post and should be sorted by publication date.
Easy !
const postReferences = Posts.select('post_id', 'title');
const getComments = (operator = '<') => Comments
.select()
.where('published_at', operator, new Date(2010, 1, 1))
.orderBy('published_at', 'desc')
.include(postReferences.clone());
const [user] = Users
.select()
.where('first_name', 'Laurent')
.and('last_name', 'Renard')
.include(
{as: 'oldComments', value: getComments()},
{as: 'recentComments', value: getComments('>')})
.run()
.then(laurentRenard => {
});
If the included builders finish on a condition clause, remember to call noop
method in order to revoke any ongoing proxy.