Skip to content

Implement ORM global scopes (registration + without_global_scopes + get() wiring)#165

Open
tmgbedu wants to merge 1 commit into
mainfrom
task/orm-global-scopes-777
Open

Implement ORM global scopes (registration + without_global_scopes + get() wiring)#165
tmgbedu wants to merge 1 commit into
mainfrom
task/orm-global-scopes-777

Conversation

@tmgbedu

@tmgbedu tmgbedu commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements global scopes for the async masoniteorm layer (task #777).

1. Registration API

  • Model.__global_scopes__ — declarative {action: {name: callable(builder)}} map.
  • scope_* discovery — any scope_<name>(self, query) method is auto-registered as a select scope.
  • Model.add_global_scope(name, callback, action="select") — programmatic, copy-on-write per class so subclasses never mutate a shared dict.

All three feed Model._boot_global_scopes() (merged across the MRO) into the instance _global_scopes, which QueryBuilder.set_model() already propagates to the builder.

2. without_global_scopes()

  • Added to QueryBuilder (clears registered scopes, returns self) and exposed as a Model classmethod.
  • Satisfies the five pivot call sites in relationships/BelongsToMany.py that previously raised AttributeError. Also added QueryBuilder.table() / connection_name and Model.table(), and normalized the pivot chains onto Pivot.on(name).table(t).

3. get() applies scopes

  • QueryBuilder.get() now calls run_scopes() (the # TODO: apply scopes), matching to_sql() / to_qmark(). run_scopes() is made idempotent so scopes are applied exactly once regardless of entry point (no duplicate WHEREs).
  • QueryBuilder.create() honors an explicit table override so pivot inserts land on the pivot table rather than the model default.

Tests

New real-sqlite suite tests/masoniteorm/sqlite/models/test_global_scopes.py:

  • declared scope filters get(); scope is compiled into SQL
  • without_global_scopes() bypass (mutation-checked: 2 vs 3 rows)
  • Model.without_global_scopes() helper
  • scope_* discovery filters + bypasses
  • runtime add_global_scope() registration (mutation-checked)
  • BelongsToMany.attach pivot-insert path writes to the correct pivot table

Verification

  • uv run pytest --ignore=tests/masoniteorm/postgres --cov: 1784 passed, 7 skipped, total coverage 75.96% (≥ 68%).
  • ruff check and ruff format: clean.
  • Backward-compatible: no existing tests changed.

…() wiring

Add a declarative and programmatic global-scope system to the async ORM:

- Model.__global_scopes__ declaration, scope_* method discovery, and
  Model.add_global_scope(name, callback, action) all feed a booted
  per-instance _global_scopes that set_model propagates to the QueryBuilder.
- QueryBuilder.without_global_scopes() (plus a Model classmethod) clears
  registered scopes, and QueryBuilder gains table()/connection_name helpers.
- QueryBuilder.get() now applies scopes via an idempotent run_scopes(),
  matching to_sql()/to_qmark() without double-applying wheres.
- Normalize BelongsToMany pivot chains onto Pivot.on(name).table(t), and
  make QueryBuilder.create() honor an explicit table override so pivot
  inserts land on the right table instead of the model default.

Covered by a real sqlite harness: declared scopes filter, scope_* discovery,
runtime registration, without_global_scopes() bypass (mutation-checked), and
a BelongsToMany attach pivot-insert path.
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.87234% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...src/fastapi_startkit/masoniteorm/models/builder.py 95.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant