Phoenix框架中的动态模型

vfo*_*one 7 elixir ecto phoenix-framework

有没有办法在凤凰城动态创建和使用模型?我有一个存储有关客户表的元数据的应用程序:它们设置了一些字段(列名和类型),然后发送CSV文件进行解析和存储.从存储的元数据中,我想生成一个模型,以便我可以使用Ecto来管理客户端表并对其执行查询.

我来自Django背景,我可以使用内置的ORM和type()函数来动态构建模型,然后使用它们而无需在应用程序中生成迁移或其他模型代码.

在Python中,我会(粗略地)做:

class MetaModel(models.Model):
    table_name = models.CharField(...)
    model_name = models.CharField(...)
    field_defs = JSONField(...)

    def resolve_fields(self):
        # takes values from `field_defs` and converts them into
        # django field instances

     def get_model(self):
         class Meta:
             app_label = 'dynamic'
             db_table = self.table_name
         fields = self.resolve_fields()
         attrs = {'__module__': 'dynamic', 'Meta': Meta}
         attrs.update(fields)
         model = type(self.model_name, (models.Model,), attrs)
         return model

     def create_table(self):
         with connection.schema_editor() as se:
             model = self.get_model()
             se.create_model(model)
Run Code Online (Sandbox Code Playgroud)

有了这个,我就可以在数据库中创建表,然后利用ORM来处理客户端提供的数据.

我知道我可以用原始SQL来做,只使用Ecto来运行命令和查询,但是我想让它更加成熟并依赖于Ecto的内部而不是编写和维护一堆SQL模板.

任何建议(即使是"不,你不能这样做")都是非常有帮助的.谢谢!

Dog*_*ert 11

是的,有可能Module.create/3.有一些注意事项:您需要为每个模块选择一个唯一的名称,模块的代码将存储在内存中,直到VM重新启动,因此您可能希望限制调用此函数的次数.

这是您可以构建的基本实现.它允许您传递模块名称,表名称以及字段名称和类型对的列表.

defmodule A do
  def go(module, table, fields) do
    Module.create(module, quote do
      use MyApp.Web, :model
      schema unquote(table) do
        unquote(for {name, type} <- fields do
          quote do
            field unquote(name), unquote(type)
          end
        end)
      end
    end, Macro.Env.location(__ENV__))
  end
end

A.go MyPost, "posts", [
  {:title, :string},
  {:content, :string},
]

# MyPost is now just like any other Schema module in Phoenix.

IO.inspect MyApp.Repo.all(MyPost)
Run Code Online (Sandbox Code Playgroud)

输出:

[debug] QUERY OK source="posts" db=2.8ms queue=0.1ms
SELECT p0."id", p0."title", p0."content" FROM "posts" AS p0 []
[%MyPost{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
  content: "Hello from Joe", id: 1, title: "Hello"},
 %MyPost{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
  content: "Hello from Mike", id: 2, title: "Hello"},
 %MyPost{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
  content: "Hello from Robert", id: 3, title: "Hello"}]
Run Code Online (Sandbox Code Playgroud)