Ecto Schema Store v1.2.4 EctoSchemaStore

This library is used to create customizable data stores for individual ecto schemas.

With the following schema:

  defmodule Person do
    use EctoTest.Web, :model

    schema "people" do
      field :name, :string
      field :email, :string

      timestamps
    end

    def changeset(model, params) do
      model
      |> cast(params, [:name, :email])
    end
  end

You can create a store with the following:

  defmodule PersonStore do
    use EctoSchemaStore, schema: Person, repo: MyApp.Repo
  end

## Querying ##

The following functions are provided in a store for retrieving data.

  • all - Fetch all records
  • one - Return a single record

    Sample Queries:

  # Get all records in a table.
  PersonStore.all

  # Get all records fields that match the provided value.
  PersonStore.all %{name: "Bob"}
  PersonStore.all %{name: "Bob", email: "bob@nowhere.test"}
  PersonStore.all name: "Bob", email: "bob@nowhere.test"

  # Return a single record.
  PersonStore.one %{name: "Bob"}
  PersonStore.one name: "Bob"

  # Return a specific record by id.
  PersonStore.one 12

Options:

  • preload - An atom or list of atoms with the model association keys to preload. Providing :all will cause all associations to be preloaded.
  # Get all records in a table.
  PersonStore.all %{}, preload: :field_name

  # Get all records fields that match the provided value.
  PersonStore.all %{name: "Bob"}, preload: [:field_name_1, :field_name_2]

  # Return a single record.
  PersonStore.one %{name: "Bob"}, preload: :all

  # Return a specific record by id.
  PersonStore.one 12, preload: :all

## Filter Operators ##

Stores support a special syntax for changing the comparison operator in the passed filter map or keyword list.

Operators:

  • {:==, value} - The field equals this value. This is the default operator when the value is not nil.
  • {:!=, value} - The field does not equal the value.
  • {:==, nil} - The field is nil or null in the database. This is the default operator when the value is nil.
  • {:!=, nil} - The field is not nil or null in the database.
  • {:<, value} - The field is less than the value.
  • {:<=, value} - The field is less than or equal the value.
  • {:>, value} - The field is greater than the value.
  • {:>=, value} - The field is greater than or equal the value.
  • {:in, []} - the field is in the list of provided values.
  PersonStore.all %{name: nil}
  PersonStore.all %{name: {:==, nil}}
  PersonStore.all name: {:!=, nil}
  PersonStore.all name: {:!=, "Bob"}
  PersonStore.all name: {:in, ["Bob"]}, email: "bob@nowhere.test"

## Editing ##

The following functions are provided in a store for editing data.

  • insert - Insert a record based upon supplied parameters map.
  • insert_fields - Insert the record without using a changeset.
  • insert! - Same as insert but throws an error instead of returning a tuple.
  • insert_fields! - Same as insert_fields but throws an error instead of returning a tuple.
  • update - Update a record based upon supplied parameters map.
  • update_fields - Update the record without using a changeset.
  • update! - Same as update but throws an error instead of returning a tuple.
  • update_fields! - Same as update_fields but throws an error instead of returning a tuple.
  • delete - Delete a record.
  • delete! - Same as delete but throws an error instead of returning a tuple.

    Sample Usage:

  # Using Map
  bob = PersonStore.insert! %{name: "Bob", email: "bob@nowhere.test"}
  bob = PersonStore.update! bob, %{email: "bob2@nowhere.test"}
  PersonStore.delete bob

  # Using Keyword List
  bob = PersonStore.insert! name: "Bob", email: "bob@nowhere.test"
  bob = PersonStore.update! bob, email: "bob2@nowhere.test"

  # Updates/deletes can also occur by id.
  PersonStore.update! 12, %{email: "bob2@nowhere.test"}
  PersonStore.delete 12

## Changesets ##

The insert and update functions by default use a changeset on the provided schema name :changeset for inserting and updating. This can be overridden and a specific changeset name provided.

  bob = PersonStore.insert! %{name: "Bob", email: "bob@nowhere.test"}, changeset: :insert_changeset
  bob = PersonStore.insert! [name: "Bob", email: "bob@nowhere.test"], changeset: :insert_changeset
  bob = PersonStore.update! bob, %{email: "bob2@nowhere.test"}, changeset: :update_changeset
  bob = PersonStore.update! bob, [email: "bob2@nowhere.test"], changeset: :update_changeset
  bob = PersonStore.update! bob, %{email: "bob2@nowhere.test"}, changeset: :my_other_custom_changeset

It is also possible to pass a function reference in as the changeset.

  def my_changeset(model, params) do
    model
    |> cast(params, [:name, :email])
    |> validate_required([:name])
  end

  insert [name: "Bob"], changeset: &my_changeset/2

## References ##

The internal references to the schema and the provided Ecto Repo are provided as convience functions.

  • schema - returns the schema reference used internally by the store.

  • repo - returns the Ecto Repo reference used internally by the store.

    ## Custom Actions ##

    Since a store is just an ordinary module, you can add your actions and build off private APIs to the store. For convience Ecto.Query is already fully imported into the module.

    A store is provided the following custom internal API:

  • build_query - Builds a Ecto.Query struct based upon the map params input.

  defmodule PersonStore do
    use EctoSchemaStore, schema: Person, repo: MyApp.Repo

    def get_all_ordered_by_name do
      build_query
      |> order_by([:name])
      |> all
    end

    def find_by_email(email) do
      %{email: email}
      |> build_query
      |> order_by([:name])
      |> all
    end

    def get_all_ordered_by_name_using_ecto_directly do
      query = from p in schema,
              order_by: [p.name]
      
      repo.all query
    end
  end

## Schema Field Aliases ##

Sometimes field names get changed or the developer wishes to have an alias that represents another field. These work for both querying and editing schema models.

  defmodule PersonStore do
    use EctoSchemaStore, schema: Person, repo: MyApp.Repo

    alias_fields email_address: :email
  end

  PersonStore.all %{email_address: "bob@nowhere.test"}
  PersonStore.update! 12, %{email_address: "bob@nowhere.test"}

## Filter or Params Map/Keyword List ##

Many of the API calls used by a store take a map of fields as input. Normal Ecto requires param maps to be either all atom or string keyed but not mixed. A schema store will convert every map provided into atom keys before aliasing and passing on to Ecto. This means you can provide a mixture of both. This will allow a developer to combine multiple maps together and not worry about what kind of keys were used.

However, if you provide the same value twice as both an atom and string key then only one will be used.

  PersonStore.insert! %{"name" => "Bob", email: "bob2@nowhere.test"}

## Lexical Atom Query ##

The store provides a function for taking a lexical query atom with a list of values and returning the results. Currently only supports _and_ clauses.

Functions:

  • get_all_by - Return all results. Equivalent to using the all function.
  • get_by - Return a single result. Equivalent to using the one function.
  # Regular
  PersonStore.all %{name: "Bob", email: "bob@nowhere.test"}
  # Lexical Atom
  PersonStore.get_all_by :name_and_email, ["Bob", "bob@nowhere.test"]

  # Regular
  PersonStore.one %{name: "Bob", email: "bob@nowhere.test"}
  # Lexical Atom
  PersonStore.get_by :name_and_email, ["Bob", "bob@nowhere.test"]

## Edit Events ##

A store supports the concept of an event after an edit action is successful in the Ecto repo.

Events:

  • :after_insert
  • :after_update
  • :after_delete
  defmodule PersonStore do
    use EctoSchemaStore, schema: Person, repo: MyApp.Repo

    on(:after_delete, model) do
      IO.inspect "Delete #{schema} id: #{model.id}"
    end

    on([:after_insert, :after_update], model) do
      IO.inspect "Changed #{schema} id: #{model.id}"
    end
  end