Phoenix 框架中的动态模型
Dynamic Models in Phoenix Framework
Phoenix 中有什么方法可以动态创建和使用模型吗?我有一个存储有关客户 table 的元数据的应用程序:他们设置了一些字段(列名和类型),然后将 CSV 文件发送给我进行解析和存储。从存储的元数据中,我想生成一个模型,以便我可以使用 Ecto 来管理客户端 table 并对其执行查询。
我有 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)
这样,我就可以在数据库中创建 table,然后利用 ORM 处理客户端提供的数据。
我知道我可以用原始的 SQL 来做到这一点,只需使用 Ecto 来 运行 命令和查询,但我想让它更加编码并依赖于 Ecto 的内部结构而不是编写和维护一堆 SQL 模板。
任何建议(甚至 "nope, you can't do that")都非常有帮助。谢谢!
是的,Module.create/3
是可能的。有一些注意事项:您需要为每个模块选择一个唯一的名称,并且模块的代码将驻留在内存中,直到 VM 重新启动,因此您可能希望限制调用此函数的次数。
这是您可以构建的基本实现。它允许您传递模块名称、table 名称以及字段名称和类型对列表。
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)
输出:
[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"}]
Phoenix 中有什么方法可以动态创建和使用模型吗?我有一个存储有关客户 table 的元数据的应用程序:他们设置了一些字段(列名和类型),然后将 CSV 文件发送给我进行解析和存储。从存储的元数据中,我想生成一个模型,以便我可以使用 Ecto 来管理客户端 table 并对其执行查询。
我有 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)
这样,我就可以在数据库中创建 table,然后利用 ORM 处理客户端提供的数据。
我知道我可以用原始的 SQL 来做到这一点,只需使用 Ecto 来 运行 命令和查询,但我想让它更加编码并依赖于 Ecto 的内部结构而不是编写和维护一堆 SQL 模板。
任何建议(甚至 "nope, you can't do that")都非常有帮助。谢谢!
是的,Module.create/3
是可能的。有一些注意事项:您需要为每个模块选择一个唯一的名称,并且模块的代码将驻留在内存中,直到 VM 重新启动,因此您可能希望限制调用此函数的次数。
这是您可以构建的基本实现。它允许您传递模块名称、table 名称以及字段名称和类型对列表。
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)
输出:
[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"}]