0

我的应用有一个 STI 模型:

# 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 in。现在我们需要创建正确的子类。

我正在采取的方法 - 有点 - 但我想知道这是否是最好的方法:

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()在开发模式下完成缓存的方式,调用存在。它有效,但它真的很难看 - 请参阅这个问题以解释我为什么这样做。

有没有更好/更可靠的方法来做到这一点?

4

1 回答 1

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
  ...
于 2011-05-25T07:13:45.697 回答