2011-05-25 19 views
0

我的應用程序有一個STI模型:RoR:如何在控制器中創建一個對象,該控制器的類是動態確定的?

# file: app/models/metered_service.rb 
class MeteredService < ActiveRecord::Base 
    ... 
end 
# file: app/models/metered_services/pge_residential.rb 
class PGEResidential < MeteredService 
    ... 
end 
# file: app/models/metered_services/sce_residential.rb 
class SCEResidential < MeteredService 
    ... 
end 

和架構支持STI:

# file: db/schema.rb 
create_table "metered_services", :force => true do |t| 
    t.integer "premise_id" 
    t.string "type" 
end 

MeteredService是一個嵌套的資源(雖然這不是真正相關的這個問題):

# file: config/routes.rb 
resources :premises do 
    resources :metered_services  
end 

因此,這裏的交易:要創建一個MeteredService,用戶在下拉列表中選擇其中的許多子類之一。該表單將類名作爲字符串返回到params['metered_services']['class']中的MeteredServicesController#create。現在我們需要創建適當的子類。

我正在工作的方法 - 排序 - 但我不知道這是不是最好的辦法:

def create 
    @premise = Premise.find(params[:premise_id]) 
    MeteredService.descendants() # see note 
    class_name = params["metered_service"].delete("class") 
    @metered_service = Object.const_get(class_name).new(params[:metered_service].merge({:premise_id => @premise.id})) 
    if @metered_service.save 
    ... standard endgame 
    end 
end 

我在做什麼是剝類名出params['metered_service']這樣我就可以使用剩餘的參數來創建計量服務。並且class_name被解析爲一個類(通過Object.const_get),所以我可以調用它的.new方法。

MeteredServices.descendants()調用是因爲在開發模式下完成緩存的方式。它的工作原理,但它確實很難看 - 請參閱this question瞭解我爲什麼這樣做的原因。

有沒有更好/更可靠的方法來做到這一點?

+0

要用戶輸入小心調用'const_get'。他們理論上可以傳遞任何東西。也許你可以過濾白名單? – 2011-05-25 05:14:55

+0

我剛纔發現雖然我不能''MeteredService.new(:type =>'PGEResidential')',我**可以做'MeteredService.new(...){| m | m.type ='PGEResidential'}',(驚人地)似乎做了正確的事情,並且(向@John Gibb點頭)最終變得安全,因爲類型在保存時被檢查。 – 2011-05-25 05:52:50

回答

0

正如John Gibb在他的評論中所說,你的主要問題是安全性。您必須通過您批准的白名單過濾課程。

您在評論中給出的解決方案也不完美。首先創建一個MeteredService實例,然後您只需使用另一個類的名稱更改文本屬性。你正在使用的實例仍然是基類。例如,如果你在遞減類上定義了一些驗證,這可能會導致一些問題。

做這樣的事情:

AVAILABLE_CLASSES = {"PGEResidential" => PGEResidential, 
        "SCEResidential" => SCEResidential } # You may automatize this 

def create 
    #.... 
    class_name = params["metered_service"].delete("class") 
    if c = AVAILABLE_CLASSES[class_name] 
    @metered_service = c.new(params[:met... 
    else 
    handle_error_somehow 
    end 
    ... 
+0

給你的支票,因爲你的方法可行。我最終放棄了STI模式。 MeteredService現在是一個普通的模型,而過去的子類現在是簡單的(非AR)類。節省很多頭痛。 – 2011-07-19 17:40:04

相關問題