RoR: как я могу создать объект в контроллере, класс которого определяется динамически? - PullRequest
0 голосов
/ 25 мая 2011

Мое приложение имеет модель 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'], чтобы я мог использовать оставшиеся параметры для создания измеряемой службы.И имя_класса преобразуется в класс (через Object.const_get), поэтому я могу вызвать для него метод .new.

Вызов MeteredServices.descendants() происходит из-за способа кэширования в режиме разработки.Это работает, но это действительно уродливо - см. этот вопрос для объяснения того, почему я это делаю.

Есть ли лучший / более надежный способ сделать это?

1 Ответ

0 голосов
/ 25 мая 2011

Как сказал Джон Гибб в своем комментарии, ваша главная проблема - безопасность.Вы должны отфильтровать классы через одобренный белый список.

Решение, которое вы дали в своем комментарии, также не идеально.Сначала создается экземпляр 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
  ...
...