【发布时间】:2018-01-18 15:05:45
【问题描述】:
我正在使用 Elixir 开始我的冒险,我需要一点帮助。
我正在尝试通过使用宏来简化我的结构定义和验证。目标是根据使用它的模块中提供的选项自动注入 defstruct 和 Vex 库验证器。
我想出了如下代码:
defmodule PdfGenerator.BibTypes.TypeDefinition do
@callback valid?(%{}) :: boolean
defmacro __using__(mod: mod, style: style, required: required, optional: optional) do
required_props = required |> Enum.map(&{:"#{&1}", nil})
optional_props = optional |> Enum.map(&{:"#{&1}", nil})
quote location: :keep do
defstruct unquote([{:style, style}] ++ required_props ++ optional_props)
@behaviour PdfGenerator.BibTypes.TypeDefinition
use Vex.Struct
def cast(%{} = map) do
styled_map = Map.put(map, :style, unquote(style))
struct_from_map(styled_map, as: %unquote(mod){})
end
defp struct_from_map(a_map, as: a_struct) do
keys =
Map.keys(a_struct)
|> Enum.filter(fn x -> x != :__struct__ end)
processed_map =
for key <- keys, into: %{} do
value = Map.get(a_map, key) || Map.get(a_map, to_string(key))
{key, value}
end
a_struct = Map.merge(a_struct, processed_map)
a_struct
end
validates(
:style,
presence: true,
inclusion: [unquote(style)]
)
end
Enum.each(required, fn prop ->
quote location: :keep do
validates(
unquote(prop),
presence: true
)
end
end)
end
end
我正在另一个模块中使用这个宏:
defmodule PdfGenerator.BibTypes.Booklet do
use PdfGenerator.BibTypes.TypeDefinition,
mod: __MODULE__,
style: "booklet",
required: [:title],
optional: [:author, :howpublished, :address, :month, :year, :note]
end
我希望PdfGenerator.BibTypes.Booklet 模块在宏展开后如下所示:
defmodule PdfGenerator.BibTypes.Booklet do
defstruct style: "booklet",
title: nil,
author: nil,
howpublished: nil,
address: nil,
month: nil,
year: nil,
note: nil
@behaviour PdfGenerator.BibTypes.TypeDefinition
use Vex.Struct
def cast(%{} = map) do
styled_map = Map.put(map, :style, "booklet")
struct_from_map(styled_map, as: %PdfGenerator.BibTypes.Booklet{})
end
defp struct_from_map(a_map, as: a_struct) do
keys =
Map.keys(a_struct)
|> Enum.filter(fn x -> x != :__struct__ end)
processed_map =
for key <- keys, into: %{} do
value = Map.get(a_map, key) || Map.get(a_map, to_string(key))
{key, value}
end
a_struct = Map.merge(a_struct, processed_map)
a_struct
end
validates(
:style,
presence: true,
inclusion: ["booklet"]
)
validates(
:title,
presence: true
)
end
如您所见,基于required 选项,我正在尝试扩展为Vex 特定的宏(反过来应该在Vex.Struct 宏定义中进一步扩展)validates(:<PROP_NAME>, presence: true) 的每个值在required 列表中。
当我从__using__ 宏中删除最后一个块时,此宏代码有效(但没有这些验证器来获取所需值):
Enum.each(required, fn prop ->
quote location: :keep do
validates(
unquote(prop),
presence: true
)
end
end)
但是,当我尝试在iex 控制台中发出以下命令时:%PdfGenerator.BibTypes.Booklet{}
我明白了:
** (CompileError) iex:1: PdfGenerator.BibTypes.Booklet.__struct__/1 is undefined, cannot expand struct PdfGenerator.BibTypes.Booklet
任何想法,我做错了什么?任何提示将不胜感激,因为我对整个 Elixir 和宏世界都很陌生。
【问题讨论】:
-
没有 MCVE--downvoted。