STI, один контроллер - PullRequest
       7

STI, один контроллер

58 голосов
/ 09 марта 2011

Я новичок в рельсах, и я застрял с этой проблемой дизайна, которая может быть легко решена, но я никуда не денусь: у меня есть два различных вида рекламы: основные моменты и сделки.Оба они имеют одинаковые атрибуты: заголовок, описание и одно изображение (со скрепкой).У них также есть такие же действия, которые можно применить к ним: индексировать, создавать новые, редактировать, создавать, обновлять и уничтожать.

Я установил ИППП следующим образом:

Модель объявления: ad.rb

class Ad < ActiveRecord::Base
end

Сделка Модель: bargain.rb

class Bargain < Ad
end

Модель Highlight: highlight.rb

class Highlight < Ad
end

Проблема в том, что я хотел бы иметь толькоодин контроллер (AdsController), который выполняет действия, которые я сказал по сделкам или выделениям в зависимости от URL, например, www.foo.com/bargains [/ ...] или www.foo.com/highlights [/ ...].

Например:

  • GET www.foo.com/highlights => список всех наиболее ярких объявлений.
  • GET www.foo.com /lights / new => форма для создания новой подсветки и т. д. *

Как я могу это сделать?

Спасибо!

Ответы [ 5 ]

99 голосов
/ 10 марта 2011

Во-первых.Добавьте несколько новых маршрутов:

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/bargains

и т. Д.

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 %>
63 голосов
/ 10 марта 2011

fl00r имеет хорошее решение, но я бы сделал одну настройку.

Это может или не может потребоваться в вашем случае.Это зависит от того, какое поведение изменяется в ваших моделях STI, особенно от проверок и перехватов жизненного цикла.

Добавьте приватный метод в свой контроллер, чтобы преобразовать ваш параметр типа в фактическую константу класса, которую вы хотите использовать:

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

И теперь вы используете реальный класс с правильным поведением вместо родительского класса с установленным типом атрибута.

12 голосов
/ 26 марта 2011

Я просто хотел включить эту ссылку, потому что есть ряд интересных трюков, связанных с этой темой.

Алекс Рейснер - Наследование в одной таблице в Rails

0 голосов
/ 17 августа 2017

Я знаю, что это старый вопрос. Вот пример, который мне нравится, который включает в себя ответы @flOOr и @Alan_Peabody. (Протестировано в Rails 4.2, возможно, работает в Rails 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

Теперь мы можем ссылаться на этот белый список в любом контроллере для построения правильной области, а также в коллекции для: 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

Нам нужно обработать наши формы, потому что маршрутизацию следует отправлять контроллеру базового класса, несмотря на фактический тип объекта. Для этого мы используем «становление», чтобы обмануть построителя форм в правильной маршрутизации, и директиву: as, чтобы входные имена также были базовым классом. Эта комбинация позволяет нам использовать неизмененные маршруты (ресурсы: реклама), а также строгую проверку параметров для параметров [: ad], возвращающихся из формы.

#/views/ads/_form.html.erb
<%= form_for(@ad.becomes(Ad), :as => :ad) do |f| %>
0 голосов
/ 24 июля 2014

[Переписано с более простым решением, которое работает полностью:]

Итерируя другие ответы, я придумал следующее решение для одного контроллера с наследованием одной таблицы, которое хорошо работает со строгими параметрами в Rails 4.1. Просто включение: type в качестве разрешенного параметра вызвало ошибку 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'

Наконец, я рекомендую использовать гем responseders и настроить scaffolding для использования responseders_controller, который совместим с STI. Конфиг для строительных лесов:

# config/application.rb
    config.generators do |g|
      g.scaffold_controller "responders_controller"
    end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...