Skip to content

Instantly share code, notes, and snippets.

@joshnuss
Last active January 18, 2022 07:26
Show Gist options
  • Save joshnuss/6de436f82ea1c3c34fbeae238924bedf to your computer and use it in GitHub Desktop.
Save joshnuss/6de436f82ea1c3c34fbeae238924bedf to your computer and use it in GitHub Desktop.
ActiveRecord pattern with Ecto
defmodule ActiveRecord do
@doc false
defmacro __using__(_opts) do
quote do
use Ecto.Schema
import Ecto.{ Changeset, Query }
import ActiveRecord
Module.register_attribute(__MODULE__, :validations, accumulate: true, persist: true)
def changeset(record \\ __struct__(), attrs) do
record
|> cast(attrs, __schema__(:fields))
|> apply_validations()
end
def validations do
attributes = __MODULE__.__info__(:attributes)
rules = Keyword.get_values(attributes, :validations)
Enum.flat_map(rules, fn [{keys, value}] ->
for k <- List.wrap(keys), do: {k, value}
end)
end
defp apply_validations(changeset) do
validations() |> Enum.reduce(changeset, fn {field, opts}, changeset ->
presence = Keyword.get(opts, :presence)
if presence do
validate_required(changeset, field, opts)
else
# TODO: add more validations options
changeset
end
end)
end
end
end
defmacro scope(name, filters) do
quote do
def unquote(name)(query \\ __MODULE__) do
query |> unquote(filters)
end
end
end
defmacro validates(attributes, opts) do
quote do
@validations unquote({attributes, opts})
end
end
end
defmodule Org.Employee do
use ActiveRecord
schema "employees" do
field :role, :string
field :first_name, :string
field :last_name, :string
field :start_date, :utc_datetime
timestamps()
end
validates [:role, :first_name, :last_name, :start_date], presence: true
scope :managers, where(role: "manager")
scope :onboarding, where([e], e.start_date > ^DateTime.utc_now())
scope :first, limit(1)
end
alias Org.Employee
%{role: "Manager", first_name: "John", last_name: "Smith"}
|> Employee.changeset() # changeset logic is generated for you
|> Repo.insert!
# get all managers
Employee.managers |> Repo.all
# get all future employees
Employee.onboarding |> Repo.all
# pipe multiple scopes
Employee.managers |> Employee.onboarding |> Repo.all
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment