Packages
pow
0.1.0-alpha.4
1.0.39
1.0.38
1.0.37
1.0.36
1.0.35
1.0.34
1.0.33
1.0.32
1.0.31
1.0.30
1.0.29
1.0.28
1.0.27
1.0.26
1.0.25
1.0.24
1.0.23
1.0.22
1.0.21
1.0.20
1.0.19
1.0.18
1.0.17
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.1.0-rc.1
0.1.0-alpha.8
0.1.0-alpha.7
retired
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
0.1.0-alpha
Robust user authentication solution
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/pow/extension/ecto/schema.ex
defmodule Pow.Extension.Ecto.Schema do
@moduledoc """
Handles extensions for the user Ecto schema.
The macro will append attributes to the `:pow_fields` module attribute, and
run `validate!/2` when compilation is done.
## Usage
Configure `lib/my_project/users/user.ex` the following way:
defmodule MyApp.Users.User do
use Ecto.Schema
use Pow.Ecto.Schema
use Pow.Extension.Ecto.Schema,
extensions: [PowExtensionOne, PowExtensionTwo]
schema "users" do
pow_user_fields()
timestamps()
end
def changeset(user_or_changeset, attrs) do
user
|> pow_changeset(attrs)
|> pow_extension_changeset(attrs)
end
end
"""
alias Ecto.Changeset
alias Pow.{Config, Extension}
defmodule SchemaError do
defexception [:message]
end
@doc false
defmacro __using__(config) do
quote do
@pow_extension_config Config.merge(@pow_config, unquote(config))
unquote(__MODULE__).__register_extension_fields__()
unquote(__MODULE__).__pow_extension_methods__()
unquote(__MODULE__).__register_after_compile_validation__()
end
end
defmacro __register_extension_fields__ do
quote do
config = Module.get_attribute(__MODULE__, :pow_extension_config)
extension_attrs = unquote(__MODULE__).attrs(config)
for attr <- extension_attrs do
Module.put_attribute(__MODULE__, :pow_fields, attr)
end
end
end
defmacro __pow_extension_methods__ do
quote do
@spec pow_extension_changeset(Changeset.t(), map()) :: Changeset.t()
def pow_extension_changeset(changeset, attrs) do
unquote(__MODULE__).changeset(changeset, attrs, @pow_extension_config)
end
end
end
defmacro __register_after_compile_validation__ do
quote do
def validate_after_compilation!(env, _bytecode) do
unquote(__MODULE__).validate!(@pow_extension_config, __MODULE__)
end
@after_compile {__MODULE__, :validate_after_compilation!}
end
end
@doc """
Merge all extension attributes together to one list.
The extension ecto schema modules is discovered through the `:extensions` key
in the configuration, and the attribute list will be in the same order as the
extensions list.
"""
@spec attrs(Config.t()) :: [tuple]
def attrs(config) do
config
|> __schema_extensions__()
|> Enum.reduce([], fn extension, attrs ->
extension_attrs = extension.attrs(config)
Enum.concat(attrs, extension_attrs)
end)
end
@doc """
Merge all extension indexes together to one list.
The extension ecto schema modules is discovered through the `:extensions` key
in the configuration, and the index list will be in the same order as the
extensions list.
"""
@spec indexes(Config.t()) :: [tuple]
def indexes(config) do
config
|> __schema_extensions__()
|> Enum.reduce([], fn extension, indexes ->
extension_indexes = extension.indexes(config)
Enum.concat(indexes, extension_indexes)
end)
end
@doc """
This will run `changeset/3` on all extension ecto schema modules.
The extension ecto schema modules is discovered through the `:extensions` key
in the configuration, and the changesets will be piped in the same order
as the extensions list.
"""
@spec changeset(Changeset.t(), map(), Config.t()) :: Changeset.t()
def changeset(changeset, attrs, config) do
config
|> __schema_extensions__()
|> Enum.reduce(changeset, fn extension, changeset ->
extension.changeset(changeset, attrs, config)
end)
end
@doc """
This will run `validate!/2` on all extension ecto schema modules.
It's used to ensure certain fields are available, e.g. an `:email` field. The
method should either raise an exception, or return `:ok`. Compilation will
fail when the exception is raised.
"""
@spec validate!(Config.t(), atom()) :: :ok | no_return
def validate!(config, module) do
config
|> __schema_extensions__()
|> Enum.each(&(&1.validate!(config, module)))
:ok
end
@doc """
Fetches all existing Ecto.Schema modules in the extensions.
"""
@spec __schema_extensions__(Config.t()) :: [atom()]
def __schema_extensions__(config) do
Extension.Config.discover_modules(config, ["Ecto", "Schema"])
end
@doc """
Validates that the ecto schema has the specified field.
If the field doesn't exist, it'll raise an exception.
"""
@spec require_schema_field!(atom(), atom(), atom()) :: :ok | no_return
def require_schema_field!(module, field, extension) do
fields = module.__schema__(:fields)
fields
|> Enum.member?(field)
|> case do
true -> :ok
false -> raise_missing_field_error(module, field, extension)
end
end
defp raise_missing_field_error(module, field, extension) do
raise SchemaError, message: "A `#{inspect field}` schema field should be defined in #{inspect module} to use #{inspect extension}"
end
end