我的应用有一个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。现在我们需要创建适当的子类。
我正在采取的方法 - 有点 - 但我想知道这是否是最佳方式:
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 :(得分:0)
正如约翰吉布在评论中所说,你的主要问题是安全问题。您必须通过已批准的白名单过滤课程。
您在评论中提供的解决方案也并不完美。首先创建一个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
...