我已经定义了模块和结构:
defmodule User do
defstruct [:id, :name, :email, :photo]
def labels, do: [:user]
end
有一个标签方法,以便我能够映射到数据库记录和从数据库记录映射。如果我需要在数据库中插入%User{}
结构,我可以抓取user.__struct__
属性并在其上调用labels
,这样我就知道记录需要具有哪些标签
但是,我不确定如何采取其他方式...鉴于我的标签为:user
,我该如何将其映射回User
结构
现在我可以用所有这些映射声明一个哈希,但是在User
结构本身上定义映射会很不错。
注意:由于(1)它不明确,(2)它感觉很脆弱,我宁愿不按照约定来制作标签。
谢谢!
答案 0 :(得分:0)
我不确定如何在结构本身中定义这样的映射。如果您不知道要查看User
结构,那么该结构中的地图如何帮助您找到它?
看起来像模块标签的地图是正确的方法。
您可以在编译时使用宏构建映射,也可以在运行时通过查找具有labels
函数的所有模块并将每个标签/模块对添加到映射来构建映射。这样就无需单独维护地图。
答案 1 :(得分:0)
我可以想到两种相对简单的方法来做到这一点。
只需在某处(labeled_records = [User, Photo, ...]
声明标记记录列表,并使用record_module.labels
返回值作为键来构建映射(并且记录模块将自己别名为值)。
你会对结构名称稍微重复一下,但至少你不必担心保持标签到记录的地图同步,因为你是从源头动态生成它的 - 真相record_module.labels
函数。
受Mix.Tasks.Help
实施的启发。
让所有记录以相同的前缀开头,例如MyApp.Records.User
,MyApp.Records.Photo
等。然后,在运行时,要查找给定标签的记录模块,首先找到带有上述前缀的.beam
文件。然后,在匹配的模块上调用.labels
以匹配输入标签。
注意:当然,如果你想在生产中使用它,你应该缓存.beam
文件遍历的结果,而不是每次你需要从标签 - >记录。以下只是一个POC。
records.ex:
defmodule MyApp.Records.User do
defstruct [:id, :name, :email, :photo]
def labels, do: [:user, :person]
end
defmodule MyApp.Records.Photo do
defstruct [:id, :user_id, :url]
def labels, do: [:photo, :pic]
end
record_finder.ex:
defmodule MyApp.RecordFinder do
defp record_module_from_path(filename) do
base = String.replace_suffix(Path.basename(filename), ".beam", "")
prefix = "Elixir.MyApp.Records."
if String.starts_with?(base, prefix), do: String.to_atom(base)
end
defp get_record_modules do
for(dir <- :code.get_path,
{:ok, files} = :erl_prim_loader.list_dir(to_char_list(dir)),
file <- files,
mod = record_module_from_path(file),
do: mod)
|> Enum.uniq
end
def module_from_label(label) do
Enum.find get_record_modules, fn m ->
label in m.labels
end
end
end
测试脚本:
Enum.each [:user, :person, :photo, :pic], fn label ->
module = MyApp.RecordFinder.module_from_label(label)
IO.puts "#{label}: #{module}"
end
输出:
user: Elixir.MyApp.Records.User
person: Elixir.MyApp.Records.User
photo: Elixir.MyApp.Records.Photo
pic: Elixir.MyApp.Records.Photo