Overview
The hasMany relationship represents a one-to-many (1:N) association where a single parent record has multiple related records. For example, a User has many Posts.
This is the inverse of the belongsTo relationship.
Method Signature
hasMany(RelatedClass, foreignKey = null, localKey = 'id')
The related Model class that belongs to this model
foreignKey
string
default:"{CurrentModel._table}_id"
The foreign key column in the related model that references this model.If not specified, defaults to the current model’s table name + _id (e.g., user_id if called from User model).
The primary key column in the current model that the foreign key references.Typically the id column.
Basic Usage
Define the Relationship
class User extends Model {
static _table = "Users";
posts() {
return this.hasMany(Post, "user_id", "id");
}
}
Query with Eager Loading
Use with() to load the relationship efficiently:
// Load users with their posts
const users = User.with("posts").get();
users.each(user => {
Logger.log(`${user.name} has ${user.posts.count()} posts`);
// posts is a Collection, not an array
user.posts.each(post => {
Logger.log(` - ${post.title}`);
});
});
Return Value
When eager loaded with with(), the relationship property returns a Collection instance, not a plain array.
hasMany always returns a Collection with powerful filtering, sorting, and aggregation methods.
const user = User.with("posts").first();
// user.posts is a Collection
const publishedPosts = user.posts.where("published", true);
const totalViews = user.posts.sum("views");
const avgRating = user.posts.avg("rating");
Examples
User Has Many Posts
class User extends Model {
static _table = "Users";
posts() {
return this.hasMany(Post, "user_id", "id");
}
}
class Post extends Model {
static _table = "Posts";
author() {
return this.belongsTo(User, "user_id");
}
}
// Usage
const users = User.with("posts").get();
users.each(user => {
const postCount = user.posts.count();
const totalViews = user.posts.sum("views");
Logger.log(`${user.name}: ${postCount} posts, ${totalViews} views`);
});
class Post extends Model {
static _table = "Posts";
comments() {
return this.hasMany(Comment, "post_id");
}
}
class Comment extends Model {
static _table = "Comments";
post() {
return this.belongsTo(Post, "post_id");
}
}
// Load posts with comments
const posts = Post.with("comments").get();
posts.each(post => {
Logger.log(`${post.title} - ${post.comments.count()} comments`);
// Filter approved comments only
const approvedComments = post.comments.where("approved", true);
approvedComments.each(comment => {
Logger.log(` - ${comment.text}`);
});
});
Nested Relationships
// Load users with their posts and each post's comments
const users = User.with("posts.comments").get();
users.each(user => {
Logger.log(`User: ${user.name}`);
user.posts.each(post => {
Logger.log(` Post: ${post.title}`);
Logger.log(` Comments: ${post.comments.count()}`);
});
});
Working with Collections
Since hasMany returns a Collection, you have access to powerful methods:
const user = User.with("posts").first();
// Filter posts
const published = user.posts.where("published", true);
const recent = user.posts.where("created_at", ">", "2024-01-01");
// Sort posts
const topPosts = user.posts.sortByDesc("views").take(5);
// Aggregate data
const totalViews = user.posts.sum("views");
const avgViews = user.posts.avg("views");
const maxViews = user.posts.max("views");
// Group posts
const byCategory = user.posts.groupBy("category");
// Get specific fields
const titles = user.posts.pluck("title");
// Convert to array if needed
const postsArray = user.posts.all();
const postsJSON = user.posts.toJSON();
Database Schema
For a hasMany relationship, the foreign key is stored in the related model’s table:
Users table:
┌────┬──────────┬─────────────────┐
│ id │ name │ email │
├────┼──────────┼─────────────────┤
│ 1 │ John Doe │ [email protected]│
│ 2 │ Jane Doe │ [email protected]│
└────┴──────────┴─────────────────┘
Posts table:
┌────┬─────────┬────────────────┬─────────┐
│ id │ user_id │ title │ body │
├────┼─────────┼────────────────┼─────────┤
│ 1 │ 1 │ First Post │ ... │
│ 2 │ 1 │ Second Post │ ... │
│ 3 │ 2 │ Jane's Post │ ... │
└────┴─────────┴────────────────┴─────────┘
User 1 has 2 posts (post 1 and post 2).
Multiple Relationships
Define multiple hasMany relationships in a single model:
class User extends Model {
static _table = "Users";
posts() {
return this.hasMany(Post, "user_id");
}
comments() {
return this.hasMany(Comment, "user_id");
}
orders() {
return this.hasMany(Order, "customer_id", "id");
}
}
// Load all relationships
const users = User.with("posts", "comments", "orders").get();
users.each(user => {
Logger.log(`${user.name}:`);
Logger.log(` Posts: ${user.posts.count()}`);
Logger.log(` Comments: ${user.comments.count()}`);
Logger.log(` Orders: ${user.orders.count()}`);
});
Filtered Relationships with Constraints
Relationship constraints are mentioned in the documentation but may require additional configuration. Check the source code for constraint implementation details.
You can define filtered versions of relationships:
class User extends Model {
static _table = "Users";
// All posts
posts() {
return this.hasMany(Post, "user_id");
}
// Only published posts (conceptual example)
publishedPosts() {
return this.hasMany(Post, "user_id", "id").where(query => {
return query.where("published", true)
.orderBy("created_at", "desc");
});
}
}
Always use eager loading with with() to load related records efficiently and avoid N+1 queries.
Good: Eager Loading (2 queries)
// ✅ Efficient: 2 queries (Users + Posts)
const users = User.with("posts").get();
users.each(user => {
user.posts.each(post => {
Logger.log(post.title); // No additional queries
});
});
Bad: N+1 Problem (N+1 queries)
// ❌ Inefficient: 1 + N queries
const users = User.all();
users.each(user => {
const posts = Post.where("user_id", user.id).get(); // Query per user!
posts.each(post => Logger.log(post.title));
});
Load Only What You Need
// Conditionally load relationships
let query = User.query();
if (includePosts) {
query = query.with("posts");
}
if (includeComments) {
query = query.with("comments");
}
const users = query.get();
- BelongsTo - Define many-to-one relationships (inverse of hasMany)
- HasOne - Define one-to-one relationships
- ManyToMany - Define many-to-many relationships with pivot tables
- Collection - Work with collections of models