Skip to content
Extends Ecto to allow rescoping of the default schema query
Branch: master
Clone or download
Latest commit 7eaf2be Jul 11, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
config Initial commit Jul 11, 2019
lib add caveats link to docs Jul 11, 2019
priv/test_repo/migrations Initial commit Jul 11, 2019
test Initial commit Jul 11, 2019
.formatter.exs Initial commit Jul 11, 2019
.gitignore Initial commit Jul 11, 2019
.travis.yml Initial commit Jul 11, 2019
CHANGELOG.md Initial commit Jul 11, 2019
LICENSE.md Initial commit Jul 11, 2019
README.md Initial commit Jul 11, 2019
dialyzer.ignore_warnings Initial commit Jul 11, 2019
mix.exs add caveats link to docs Jul 11, 2019
mix.lock Initial commit Jul 11, 2019

README.md

Ecto.Rescope

travis ci badge

Extends Ecto to allow rescoping of the default schema query.

A typical usecase for this functionality is excluding soft-deleted records by default.

Usage

The most basic example for rescoping is to add directly to a schema with an inline function.

defmodule User do
  use Ecto.Schema

  import Ecto.Query, only: [from: 2]
  import Ecto.Rescope

  schema "users" do
    field(:is_deleted, :boolean)
  end

  rescope(fn query -> 
    from(q in query, where: q.is_deleted == false)
  end)
end

In a more abstracted example, we might move the logic to a separate module for reuse.

defmodule SoftDelete do
  import Ecto.Query, only: [from: 2]

  def exclude_deleted(query) do
    from(q in query, where: q.is_deleted == false)
  end
end

defmodule User do
  use Ecto.Schema
  
  import Ecto.Rescope

  schema "users" do
    field(:is_deleted, :boolean)
  end

  rescope(&SoftDelete.exclude_deleted/1)
end

When we want to query without the default scoping applied, we can do so with unscoped/0, which is added by the rescope/1 macro.

User.unscoped()

Caveats

Using with Ecto.Query API

When using from or join macros from the Ecto.Query api, the query builder defines the queryable source at runtime, thus ignoring __schema__(:query) and the associated override.

Because of this, queries such as the following will not work as expected:

from(q in User, join: t in Team, on: t.id == q.team_id)

# SELECT u0."id", u0."name", u0."is_deleted", u0."team_id" FROM "users" AS u0 INNER JOIN "teams" AS t1 ON 
# t1."id" = u0."team_id" []

Note the lack of (u0."is_deleted" = FALSE) or (t1."is_deleted" = FALSE) in the associated SQL log.

However, this can be worked around using the scoped/0 function that is defined by the rescope/0 macro.

from(q in User.scoped(), join: t in ^Team.scoped(), on: t.id == q.team_id)

# SELECT u0."id", u0."name", u0."is_deleted", u0."team_id" FROM "users" AS u0 INNER JOIN "teams" AS t1 ON 
# (t1."is_deleted" = FALSE) AND (t1."id" = u0."team_id") WHERE (u0."is_deleted" = FALSE) []

NOTE: Although this can seem a bit cumbersome, when using query building libraries such as DockYard's Inquisitor, the problem is avoided as the queryable module is converted to a query struct prior to being used within the from macro.

Private API

This library overrides private reflection functions defined on schemas by the ecto library, specifically __schema__(:query) (source). While this technique has been used stable in production for multiple years, there is no guarantee ecto won't change the underlying functionality at some point in the future.

Installation

The package can be installed by adding ecto_rescope to your list of dependencies in mix.exs:

def deps do
  [
    {:ecto_rescope, "~> 0.1.0"}
  ]
end

Documentation can be found at https://hexdocs.pm/ecto_rescope.

License

The source code is under the Apache 2 License.

Copyright (c) 2019 Erik Reedstrom

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

You can’t perform that action at this time.