Skip to content

Instantly share code, notes, and snippets.

@dbernheisel
Last active January 12, 2021 13:05
Show Gist options
  • Save dbernheisel/af66c4769452721af4de363cec2b4500 to your computer and use it in GitHub Desktop.
Save dbernheisel/af66c4769452721af4de363cec2b4500 to your computer and use it in GitHub Desktop.
Elixir module to use for Ecto models. This macro will implement Rails-like finder functions for the given module.
defmodule Common.FinderFunctions do
@moduledoc """
Certainly not a perfect replication of Rails' finder methods, but at least gives Rails developers
some sense of home with Ecto. This does not reach into associations, nor is it huge-db friendly.
## Usage:
0. Don't. Just learn Ecto.
1. In your given module that implements Ecto's schema, let it
`use Common.FinderFunctions, repo: MyApp.Repo`. This also assumes there
is a changeset function on your model.
2. Anywhere you're using the module, (eg User) you can now use the functions
below; eg:
`User.find(1)`
`User.find_or_create_by(email: "test@user.com")`
`User.where(token: nil)`
"""
defmacro __using__(opts) do
repo = Keyword.get(opts, :repo)
quote bind_quoted: [repo: repo] do
import Ecto.Query
@doc """
Find records by ID
If given one ID, returns a struct
If given a list of IDs, returns a list of structs.
## Examples
iex> User.find(1)
%MyApp.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
email: "test@user.com",
id: 1,
password: "mysecretpassword",
inserted_at: #Ecto.DateTime<2016-09-19 15:48:52>,
updated_at: #Ecto.DateTime<2016-09-19 19:12:00>}
iex> User.find([1, 2, 3])
[%MyApp.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
email: "test@user.com",
id: 1,
password: "mysecretpassword",
inserted_at: #Ecto.DateTime<2016-09-19 15:48:52>,
updated_at: #Ecto.DateTime<2016-09-19 19:12:00>}]
"""
def find_by(params), do: unquote(repo).get_by(__MODULE__, params)
def find(queryable, id), do: from row in queryable, where: [id: ^id]
def find(ids) when is_list(ids), do: where(id: ids)
def find(id), do: unquote(repo).get(__MODULE__, id)
def count(queryable \\ __MODULE__) do
queryable |> select([r], count(r.id)) |> unquote(repo).one
end
def all(queryable \\ __MODULE__), do: unquote(repo).all(queryable)
def where(params) do
params
|> find_by_fields
|> unquote(repo).all
end
def find_or_create_by(params) when is_list(params) do
params
|> Enum.into(%{})
|> find_or_create_by
end
def find_or_create_by(params) do
case find_by(params) do
nil -> create_by(params)
record -> record
end
end
def find_or_initialize_by(params) when is_list(params) do
params
|> Enum.into(%{})
|> find_or_initialize_by
end
def find_or_initialize_by(params) do
case find_by(params) do
nil -> initialize_by(params)
record -> record
end
end
def create_by(params) when is_list(params) do
params
|> Enum.into(%{})
|> create_by
end
def create_by(params) do
changeset = initialize_by(params)
case changeset.valid? do
false -> {:error, changeset}
true ->
params
|> initialize_by
|> unquote(repo).insert
end
end
def initialize_by(params) when is_list(params) do
params
|> Enum.into(%{})
|> initialize_by
end
def initialize_by(params) do
%{ __MODULE__.changeset(__MODULE__.__struct__, params) | action: :insert }
end
defp find_by_field(queryable, db_field, search_term) when is_list(search_term) do
from row in queryable, where: field(row, ^db_field) in ^search_term
end
defp find_by_field(queryable, db_field, search_term) when is_nil(search_term) do
from row in queryable, where: is_nil(field(row, ^db_field))
end
defp find_by_field(queryable, db_field, search_term) when is_bitstring(search_term) do
db_field = String.to_existing_atom(db_field)
from row in queryable, where: field(row, ^db_field) == ^search_term
end
defp find_by_field(queryable, db_field, search_term) do
from row in queryable, where: field(row, ^db_field) == ^search_term
end
defp find_by_fields(params) do
params
|> Enum.into(%{})
|> Enum.reduce(
__MODULE__,
fn({db_field, value}, query) -> find_by_field(query, db_field, value) end
)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment