All skills

Waterline ORM query language, model definitions, associations, and data access patterns for Sails.js. Use this skill when writing, reviewing, or debugging Waterline queries, model attributes, associations, lifecycle callbacks, validations, or any database interaction in a Sails.js application.

Use this Skill: https://skilld.dev/gh/sailscastshq/boring-stack/waterline

This session only. Nothing lands on disk.

rulesassociations.md

≈1.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Associations

Associations represent relationships between models. They are NOT automatically included in query results -- you must explicitly call .populate().

One-to-Many (Most Common)

One record ("the one") links to multiple records ("the many"). The "many" side holds the foreign key.

// api/models/User.js -- the "one" side
module.exports = {
  attributes: {
    fullName: { type: 'string' },
    invoices: {
      collection: 'invoice',
      via: 'creator'
    }
  }
}

// api/models/Invoice.js -- the "many" side (holds the FK)
module.exports = {
  attributes: {
    invoiceNumber: { type: 'string' },
    totalAmount: { type: 'number' },
    creator: {
      model: 'user'
    }
  }
}

Create an associated record by passing the parent's primary key:

await Invoice.create({
  invoiceNumber: 'INV-001',
  totalAmount: 5000,
  creator: userId // Just the ID value
})

One-to-One

Use model on both sides. Add unique: true on the FK side to enforce one-to-one at the database level:

// api/models/User.js
module.exports = {
  attributes: {
    name: { type: 'string' },
    pet: {
      collection: 'pet',
      via: 'owner'
    }
  }
}

// api/models/Pet.js
module.exports = {
  attributes: {
    name: { type: 'string' },
    owner: {
      model: 'user',
      unique: true // Enforces one-to-one
    }
  }
}

Many-to-Many

Both sides use collection and via. Waterline automatically creates a junction table:

// api/models/User.js
module.exports = {
  attributes: {
    fullName: { type: 'string' },
    courses: {
      collection: 'course',
      via: 'purchasers'
    }
  }
}

// api/models/Course.js
module.exports = {
  attributes: {
    title: { type: 'string' },
    purchasers: {
      collection: 'user',
      via: 'courses'
    }
  }
}

Manage many-to-many with collection methods:

// Add association
await User.addToCollection(userId, 'courses', [courseId])
await Course.addToCollection(courseId, 'purchasers', [userId])

// Remove association (does NOT delete the records themselves)
await User.removeFromCollection(userId, 'courses', [courseId])
await Course.removeFromCollection(courseId, 'purchasers', [userId])

// Replace all associations
await User.replaceCollection(userId, 'courses', [course1, course2, course3])

One-Way Association (No via)

When only one side defines the association. Changes on one side do NOT sync to the other:

// api/models/Course.js
module.exports = {
  attributes: {
    title: { type: 'string' },
    purchasers: { collection: 'user' } // No 'via' -- one-way
  }
}

Self-Referencing Associations

A model can associate with itself:

// api/models/Comment.js -- threaded comments
module.exports = {
  attributes: {
    body: { type: 'string' },
    parentComment: {
      model: 'comment',
      columnName: 'parent_comment'
    }
  }
}

// api/models/Invoice.js -- template invoices
module.exports = {
  attributes: {
    templateInvoice: {
      model: 'invoice',
      columnName: 'template_invoice'
    }
  }
}

Multiple Associations to the Same Model

// api/models/Creator.js
module.exports = {
  attributes: {
    invoices: { collection: 'invoice', via: 'creator' },
    clients: { collection: 'client', via: 'creator' },
    expenses: { collection: 'expense', via: 'creator' },
    settings: { collection: 'setting', via: 'creator' }
  }
}

populate()

Load associated records. Must be called explicitly:

// Single populate
var invoice = await Invoice.findOne({ id: invoiceId }).populate('client')

// Multiple populates
var invoice = await Invoice.findOne({ publicId: id, creator: userId })
  .populate('items')
  .populate('creator')
  .populate('client')

// Populate with subcriteria (filter, sort, limit the populated records)
var ticket = await Ticket.findOne({ id: ticketId })
  .populate('ticketType', { select: ['name'] })
  .populate('order')

// Populate on find (returns array of records, each with populated data)
var users = await User.find().populate('invoices')

After populate, the associated data is available as a nested object or array:

var subscription = await Subscription.findOne({ id: subId }).populate('user')
// subscription.user.organization is now accessible

Populate Default Limit

Waterline applies a default limit of 30 on .populate() results. Override in config/models.js or per-query.

Cross-Datastore Associations

Sails supports populating associations across different databases (e.g., Users in PostgreSQL + Comments in MongoDB). Waterline handles the multi-step querying automatically.

For many-to-many across datastores, use dominant: true on one side to specify where the junction table lives:

// api/models/User.js (MySQL)
module.exports = {
  datastore: 'mysql',
  attributes: {
    wishlist: {
      collection: 'product',
      via: 'wishlistedBy',
      dominant: true // Junction table created in MySQL
    }
  }
}

// api/models/Product.js (Redis)
module.exports = {
  datastore: 'redis',
  attributes: {
    wishlistedBy: {
      collection: 'user',
      via: 'wishlist'
    }
  }
}

Association Keywords Reference

Keyword Used On Meaning
model: 'user' FK holder ("many" side) Points to one record in another model
collection: 'pet' Inverse side ("one" side) References multiple records
via: 'owner' Used with collection Names the FK attribute on the other model
unique: true On model attribute Enforces one-to-one at DB level
dominant: true On collection attribute Determines which datastore hosts the junction table

Source: SKILL.md on GitHub

1 warning17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill provides comprehensive documentation and best practice rules for writing Waterline ORM queries and model definitions in Sails.js applications. It contains only Markdown files and does not include any executable scripts or malicious instructions.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    10/13 files flagged

Signed by skilld at 383ecf8. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 8 months ago
Other metadata
metadata
{
  "author": "sailscastshq",
  "version": "1.0.0",
  "tags": "waterline, sails, orm, database, query, models"
}

README badge

README badge for sailscastshq/boring-stack/waterline