Skip to main content

Model Scopes

Scopes package reusable query constraints on a model. Ormed supports:

Prerequisites

What You’ll Learn

  • How to define global and local scopes
  • How generated and inline scopes differ
  • How scope registration affects query behavior across the app

Step Outcome

By the end of this page, you should be able to:

  • Register generated scopes correctly during bootstrap
  • Apply local scopes for reusable query constraints
  • Disable global scopes intentionally in exceptional flows

Ormed supports:

  • Generated scopes (declared on the model with @OrmScope)
  • Inline scopes (registered at runtime on a ScopeRegistry)
Snippet context
  • The examples focus on the scope-related lines and omit full bootstrapping.
  • Where setup is required, the snippet calls bootstrapOrm() to wire generated registrations.

Define scopes as static methods that accept a Query<$Model> as the first parameter.

Global scopes apply to every query for the model:

  (global: true)
static Query<$ScopedUser> activeOnly(Query<$ScopedUser> query) =>
query.whereEquals('active', true);

Local scopes are opt-in and can accept positional or named arguments:

  ()
static Query<$ScopedUser> withDomain(
Query<$ScopedUser> query,
String domain,
) => query.whereLike('email', '%@$domain');
  ()
static Query<$ScopedUser> roleIs(
Query<$ScopedUser> query, {
required String role,
}) => query.whereEquals('role', role);

Register generated scopes once during bootstrap:

  // bootstrapOrm() wires up generated scopes (and other generated helpers).
final registry = bootstrapOrm();

Use generated query helpers:

  // Local scopes compose via generated extensions
final admins = await dataSource.context
.query<$ScopedUser>()
.withDomain('example.com')
.roleIs(role: 'admin')
.get();

Global scopes apply automatically:

  // Global scope filters inactive rows automatically
final active = await dataSource.context.query<$ScopedUser>().get();

Disable a specific global scope when needed:

  // Opt out of globals when needed
final allRows = await dataSource.context
.query<$ScopedUser>()
.withoutGlobalScope('activeOnly')
.get();

Rules & notes

  • Static only: scopes must be static so both the user model type and generated $Model query APIs can reference them consistently.
  • Global scopes must be argument-free (beyond the initial Query<T>).
  • Identifiers default to the method name; override with @OrmScope(identifier: 'custom') when needed.

Practical Guidance

  • Use global scopes only for always-on constraints (for example soft-delete filters).
  • Use local scopes for business filters reused across endpoints.
  • Prefer explicit scope names that describe intent (active, recent, forTenant).

Verify Scope Behavior

  1. Test that global scopes apply automatically.
  2. Test withoutGlobalScope(...) for override paths.
  3. Test local scope arguments (positional + named) in queries.

Read This Next