Мое приложение имеет модель 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, пользователь выбирает один из его многочисленных подклассов в раскрывающемся списке. Форма возвращает имя класса в MeteredServicesController#create в params['metered_services']['class']
в виде строки. Теперь нам нужно создать правильный подкласс.
Подход, который я использую, работает - вроде как - но мне интересно, лучший ли это способ:
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()
существует из-за способа кэширования в режиме разработки. Это работает, но это действительно уродливо - см. вопрос для объяснения того, почему я это делаю.
Есть ли лучший/надежный способ сделать это?
const_get
при вводе данных пользователем. теоретически они могли пройти во что угодно. Может быть, вы могли бы фильтровать с помощью белого списка? - person John Gibb   schedule 25.05.2011MeteredService.new(:type => 'PGEResidential')
, я могу сделатьMeteredService.new(...) {|m| m.type = 'PGEResidential'}
, что (удивительно) кажется правильным и (кивок на @John Gibb) оказывается безопасным, поскольку тип проверяется при сохранении. - person fearless_fool   schedule 25.05.2011