I would like all of my models to have access to a new
convenience function that returns a custom changeset.
Something like:
def new(attrs) do
changeset(%__MODULE__{}, attrs)
end
That way, when I need changeset validation, I could call:
Project.Model.new(%{param1: "param1"})
Instead of:
Project.Model.changeset(%Model{}, %{param1: "param1"})
The problem I run into is that when I implement the following macro:
defmodule Project.Model do
defmacro __using__(_) do
quote do
use Ecto.Schema
import Ecto.Changeset
def new(attrs) do
changeset(%__MODULE__{}, attrs)
end
end
end
end
... it doesn't work, because Ecto's schema "model" do ... end
needs to be compiled before my use Project.Model
statement otherwise I'll get an error which basically says my module does not have a struct defined.
I could limit the macro simply to the new
function and place it in the middle of the module that uses it but that seems confusing.
Any ideas?
By request, full code below:
Once again, the purpose of all this, is to have models that use Project.Model
gain a convenience function new
that accepts attributes and puts them inside a changeset
so that they will be validated before database insertion.
The error I get, as some have alluded to, is that schema
must be expanded before I can use __MODULE__
in my macro because the struct will not have yet been defined.
project/user.ex
defmodule Project.User do
use Project.Model
schema "users" do
field :email, :string
field :first_name, :string
field :last_name, :string
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:email, :first_name, :last_name])
|> validate_required([:email, :first_name, :last_name])
end
end
project/model.ex
defmodule Project.Model do
defmacro __using__(_) do
quote do
use Ecto.Schema
import Ecto.Changeset
def new(attrs) do
changeset(%__MODULE__{}, attrs)
end
end
end
end
To use a struct before it's defined, you can try this trick:
def new(attrs) do
changeset(struct(__MODULE__), attrs)
end
changeset
is something you should define per model (and might have a different shape) so I'm not sure it's a good idea to use it inside new
in a general manner.