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 = > форма для создания новой подсветки
    так далее...


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



спасибо!

562   5  

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

etc

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

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

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

Alex Reisner-наследование одной таблицы в Rails

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

повторяя другие ответы, я придумал следующее решение для одного контроллера с наследованием одной таблицы, которое хорошо работает с сильными параметрами в 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

    Ничего не найдено.