2017-10-20 128 views
3

是否有任何方法可以在Phoenix中動態創建和使用模型?我有一個應用程序存儲有關客戶端表格的元數據:他們設置了一些字段(列名稱和類型),然後將CSV文件發送給解析和存儲。從存儲的元數據中,我想生成一個模型,以便我可以使用Ecto來管理客戶端表並對其執行查詢。Phoenix Framework中的動態模型

我來自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) 

就這樣,我能夠在數據庫中創建表,然後充分利用ORM與客戶端提供的數據工作。

我知道我可以用原始SQL做這件事,只是使用Ecto來運行命令和查詢,但我想讓它更加規範化,依賴於Ecto的內部,而不是編寫和維護一堆SQL模板。

任何建議(即使是「不行,你不能這樣做」)是超級有用的。謝謝!

回答

5

是的,這可能與Module.create/3。有一些注意事項:您需要爲每個模塊選擇一個唯一的名稱,並且該模塊的代碼將存儲在內存中,直到虛擬機重新啓動,因此您可能需要限制調用此函數的次數。

這是您可以構建的基本實現。它允許您傳遞模塊名稱,表格名稱以及字段名稱和類型對的列表。

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"}] 
+0

這是真的很酷,你會如何去重新定義一個特定的模塊? – webdeb

+1

明白了,您可以用相同的名稱/原子覆蓋模塊 – webdeb

+0

這非常棒!非常感謝你! – vforgione