Current section
Files
Jump to
Current section
Files
lib/ecto_vista.ex
defmodule EctoVista do
@moduledoc """
Provides the macros and functions to define and manage
PostgreSQL views with Ecto.
## Using EctoVista
To use `EctoVista`, you need to add `use EctoVista` to your Elixir
files. This gives you access to the functions and macros defined
in the module.
example:
def App.Catalog do
use Ecto.Schema
use EctoVista,
repo: App.Repo
table_name: "catalog"
schema @table_name do
field(:name, :string)
field(:product_count, :integer)
end
end
The `@table_name` will be defined in macro as `{table_name}_v{version}` (version is 1 by default)
This naming convention facilitates 0-downtime view updates and will be handled automagically in future versions.
## Generating Views
Views can be generated via regular migration, just put the definition inside `change` or `up` migration methods.
For the schema definition like the one above, view can be generated as:
execute(\"\"\"
CREATE MATERIALIZED VIEW catalog_v1 AS
SELECT c.*, count(p.id) AS product_count
FROM categories c
LEFT JOIN products p ON c.id = p.category_id
GROUP BY c.id
;
\"\"\")
## Updating Views
If you need to update the view, generate a new migration and then just update the version number in the schema definition:
def App.Catalog do
use Ecto.Schema
use EctoVista,
repo: App.Repo
table_name: "catalog"
version: 2
...
end
## Refreshing Views
Use the `refresh/0` function
It will run `REFRESH MATERIALIZED VIEW [view_name];` query.
iex> Catalog.refresh
:ok
"""
require Ecto.Query
defmacro __using__(opts \\ []) do
unless repo = Keyword.get(opts, :repo) do
raise ArgumentError,
"""
expected :repo to be given as an option. Example:
use EctoVista, repo: App.Repo
"""
end
unless table_name = Keyword.get(opts, :table_name) do
raise ArgumentError,
"""
expected :table_name to be given as an option. Example:
use EctoVista, table_name: "categories"
"""
end
version = Keyword.get(opts, :version, 1)
quote do
import Ecto
@table_name "#{unquote(table_name)}_v#{unquote(version)}"
def repo, do: unquote(repo)
def source, do: __MODULE__.__struct__().__meta__.source
@doc """
A function that refreshes a current version of the view,
defined in module.
Currently support only materialized views.
iex> Catalog.refresh
:ok
"""
@spec refresh() :: :ok | {:error, String.t()}
def refresh do
@table_name
|> refresh_query()
|> repo().query()
|> handle_refresh_result()
end
defp refresh_query(name) do
"REFRESH MATERIALIZED VIEW #{name};"
end
defp handle_refresh_result({:ok, _}), do: :ok
defp handle_refresh_result({:error, %Postgrex.Error{postgres: error_hash}}) do
{:error, error_hash.message}
end
end
end
end