处理双向映射的惯用方法

时间:2016-03-22 15:05:05

标签: elixir

我已经定义了模块和结构:

defmodule User do
  defstruct [:id, :name, :email, :photo]

  def labels, do: [:user]
end

有一个标签方法,以便我能够映射到数据库记录和从数据库记录映射。如果我需要在数据库中插入%User{}结构,我可以抓取user.__struct__属性并在其上调用labels,这样我就知道记录需要具有哪些标签

但是,我不确定如何采取其他方式...鉴于我的标签为:user,我该如何将其映射回User结构

现在我可以用所有这些映射声明一个哈希,但是在User结构本身上定义映射会很不错。

注意:由于(1)它不明确,(2)它感觉很脆弱,我宁愿不按照约定来制作标签。

谢谢!

2 个答案:

答案 0 :(得分:0)

我不确定如何在结构本身中定义这样的映射。如果您不知道要查看User结构,那么该结构中的地图如何帮助您找到它?

看起来像模块标签的地图是正确的方法。

您可以在编译时使用宏构建映射,也可以在运行时通过查找具有labels函数的所有模块并将每个标签/模块对添加到映射来构建映射。这样就无需单独维护地图。

答案 1 :(得分:0)

我可以想到两种相对简单的方法来做到这一点。

只需在某处(labeled_records = [User, Photo, ...]声明标记记录列表,并使用record_module.labels返回值作为键来构建映射(并且记录模块将自己别名为值)。

你会对结构名称稍微重复一下,但至少你不必担心保持标签到记录的地图同步,因为你是从源头动态生成它的 - 真相record_module.labels函数。

德塞夫勒

Mix.Tasks.Help实施的启发。

让所有记录以相同的前缀开头,例如MyApp.Records.UserMyApp.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