(code citation has been anonymized)
In my phoenix models I have some methods which are redundant, like this basic one :
def build(params) do
changeset(%__MODULE__{}, params)
end
Since I put them inside my model modules, they work fine, but I want to avoid code duplication and I want to make them available to all my models through a helper module like this :
defmodule MyApp.Helpers.Model do
defmodule Changeset do
defmacro __using__(_opts) do
quote do
def build(params) do
changeset(%__MODULE__{}, params)
end
end
end
end
end
Doing this, I'm getting an error :
== Compilation error on file lib/my_app/model/my_model.ex ==
** (CompileError) lib/my_app/model/my_model.ex:3: MyApp.Model.MyModel.__struct__/1 is undefined, cannot expand struct MyApp.Model.MyModel
(stdlib) lists.erl:1354: :lists.mapfoldl/3
The related model looks basically like this :
defmodule MyApp.Model.MyModel do
use MyApp.Helpers, :model
use MyApp.Helpers.Model.Changeset # here for comprehension, should be in MyApp.Helpers quoted :model method
schema "my_table" do
field :name, :string
timestamps()
end
@required_fields ~w(name)a
@optional_fields ~w()
@derive {Poison.Encoder, only: [:name]}
def changeset(model, params \\ %{}) do
model
|> cast(params, @required_fields)
|> cast(params, @optional_fields)
|> validate_required(@required_fields)
|> validate_format(:name, ~r/^[a-z]{3,}$/)
|> unique_constraint(:name)
end
end
I'm thinking that's because the module is not yet defined at compile time within the macro, but I'm not sure, nor how to fix this and make it works.
Some lights here would be much appreciated, thanks.
The problem is that the struct is defined by calling the
defstructmacro, and cannot be used earlier, since the compiler has no idea how to expand it. In case of ecto schemas the struct is declared by theschemamacro underneath.Fortunately, looking the documentation of
defstructwe can see it creates a function called__struct__/0on the module where the struct is declared. And functions can call other local functions, even before they are defined! Using that knowledge we can change your macro to:That said, defining functions in
__using__is generally considered a bad practice, as stated in the documentation forKernel.use/1Defining functions in
__using__has many downsides including: slow compilation (the function is compiled over and over in every module it's injected to), difficulty in debugging ("where is this coming from?") and poor compostability.A much better approach might be to define a single, reusable function. For example:
PS. The
@derivecalls have to be declared before the struct.