STI, один контроллер
Я новичок в rails, и я как бы застрял с этой проблемой дизайна, которую может быть легко решить, но я никуда не денусь:
У меня есть два различных вида рекламы: основные моменты и сделки. Оба они имеют одинаковые атрибуты: заголовок, описание и одно изображение (с скрепкой). У них также есть такие же действия, чтобы применить к ним: индексировать, создавать, редактировать, создавать, обновлять и уничтожать.
Я установил STI вот так:
Объявление Модель: объявление.РБ
class Ad < ActiveRecord::Base
end
модель сделки: сделка.РБ
class Bargain < Ad
end
выделите модель: выделите.РБ
class Highlight < Ad
end
проблема в том, что я хотел бы иметь только один контроллер (AdsController), который выполняет действия, которые я сказал на сделки или основные моменты в зависимости от URL, скажем www.foo.com/bargains [/...] или www.foo.com/highlights[/...].
например:
- получить www.foo.com/highlights = > список всех объявлений, которые являются основные моменты.
- получить www.foo.com/highlights/new = > форма для создания новой подсветки
так далее...
как я могу это сделать?
спасибо!
5 ответов:
первый. Добавьте несколько новых маршрутов:
resources :highlights, :controller => "ads", :type => "Highlight" resources :bargains, :controller => "ads", :type => "Bargain"и исправить некоторые действия в
AdsController. Например:def new @ad = Ad.new() @ad.type = params[:type] endдля лучшего подхода для всей этой работы контроллера посмотрите комментарий
вот и все. Теперь вы можете перейти к
localhost:3000/highlights/newи новаяHighlightбудет инициализирован.действие индекса может выглядеть так:
def index @ads = Ad.where(:type => params[:type]) endна
localhost:3000/highlightsи появится список основных моментов.
Аналогично сделки:localhost:3000/bargainsetc
URLS
<%= link_to 'index', :highlights %> <%= link_to 'new', [:new, :highlight] %> <%= link_to 'edit', [:edit, @ad] %> <%= link_to 'destroy', @ad, :method => :delete %>для того, чтобы быть полиморфным:)
<%= link_to 'index', @ad.class %>
fl00r имеет хорошее решение, однако я бы сделал одну корректировку.
Это может потребоваться или не потребоваться в вашем случае. Это зависит от того, какое поведение меняется в ваших моделях STI, особенно проверки и крючки жизненного цикла.
добавьте частный метод к контроллеру, чтобы преобразовать ваш тип param в фактическую константу класса, которую вы хотите использовать:
def ad_type params[:type].constantize endвыше небезопасно, однако. Добавьте белый список типов:
def ad_types [MyType, MyType2] end def ad_type params[:type].constantize if params[:type].in? ad_types endбольше на рельсы метод Констанции здесь: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize
затем в действиях контроллера вы можете сделать:
def new ad_type.new end def create ad_type.new(params) # ... end def index ad_type.all endи теперь вы используете фактический класс с правильным поведением, а не родительского класса с атрибутом типа.
Я просто хотел включить эту ссылку, потому что есть ряд интересных трюков и все связанные с этой темой.
[переписано с более простым решением, которое работает полностью:]
повторяя другие ответы, я придумал следующее решение для одного контроллера с наследованием одной таблицы, которое хорошо работает с сильными параметрами в Rails 4.1. Просто в том числе: введите в качестве разрешенного параметра вызвал
ActiveRecord::SubclassNotFoundошибка при вводе недопустимого типа. Кроме того, тип не обновляется, поскольку SQL-запрос явно ищет старый тип. Вместо этого,:typeнеобходимо обновить отдельно с update_column, если он отличается от текущего набора и является допустимым типом. Обратите внимание также, что мне удалось высушить все списки типов.# app/models/company.rb class Company < ActiveRecord::Base COMPANY_TYPES = %w[Publisher Buyer Printer Agent] validates :type, inclusion: { in: COMPANY_TYPES, :message => "must be one of: #{COMPANY_TYPES.join(', ')}" } end Company::COMPANY_TYPES.each do |company_type| string_to_eval = <<-heredoc class #{company_type} < Company def self.model_name # http://stackoverflow.com/a/12762230/1935918 Company.model_name end end heredoc eval(string_to_eval, TOPLEVEL_BINDING) endи в контроллере:
# app/controllers/companies_controller.rb def update @company = Company.find(params[:id]) # This separate step is required to change Single Table Inheritance types new_type = params[:company][:type] if new_type != @company.type && Company::COMPANY_TYPES.include?(new_type) @company.update_column :type, new_type end @company.update(company_params) respond_with(@company) endи маршрутов:
# config/routes.rb Rails.application.routes.draw do resources :companies Company::COMPANY_TYPES.each do |company_type| resources company_type.underscore.to_sym, type: company_type, controller: 'companies', path: 'companies' end root 'companies#index'наконец, я рекомендую использовать ответчики gem и установка лесов для использования responders_controller, который совместим с STI. Конфигурация для лесов является:
# config/application.rb config.generators do |g| g.scaffold_controller "responders_controller" end
Я знаю, что это старый вопрос, вот шаблон, который мне нравится, который включает в себя ответы от @flOOr и @Alan_Peabody. (Испытано в рельсах 4.2, вероятно работает в рельсах 5)
В вашей модели, создать свой белый список при запуске. В dev это должно быть нетерпеливо загружено.
class Ad < ActiveRecord::Base Rails.application.eager_load! if Rails.env.development? TYPE_NAMES = self.subclasses.map(&:name) #You can add validation like the answer by @dankohn endТеперь мы можем ссылаться на этот белый список в любом контроллере, чтобы построить правильную область, а также в коллекции для A :type select на форме и т. д.
class AdsController < ApplicationController before_action :set_ad, :only => [:show, :compare, :edit, :update, :destroy] def new @ad = ad_scope.new end def create @ad = ad_scope.new(ad_params) #the usual stuff comes next... end private def set_ad #works as normal but we use our scope to ensure subclass @ad = ad_scope.find(params[:id]) end #return the scope of a Ad STI subclass based on params[:type] or default to Ad def ad_scope #This could also be done in some kind of syntax that makes it more like a const. @ad_scope ||= params[:type].try(:in?, Ad::TYPE_NAMES) ? params[:type].constantize : Ad end #strong params check works as expected def ad_params params.require(:ad).permit({:foo}) end endмы должны справиться с нашей формы, потому что маршрут должен быть отправлен на контроллер базового класса, несмотря на фактический тип объекта. Для этого мы используем "becomes", чтобы обмануть построитель форм в правильную маршрутизацию, и директиву :as, чтобы заставить входные имена быть базовым классом. Эта комбинация позволяет нам использовать немодифицированные маршруты (ресурсы :объявления), а также сильную проверку параметров на парах[:ad], возвращающихся из формы.
#/views/ads/_form.html.erb <%= form_for(@ad.becomes(Ad), :as => :ad) do |f| %>
Comments