STI, un controlador

Soy nuevo en los Rails y estoy atascado con este problema de diseño, que podría ser fácil de resolver, pero no llego a ningún lado: tengo dos tipos diferentes de anuncios: destacados y gangas. Ambos tienen los mismos atributos: título, descripción y una imagen (con clip). También tienen el mismo tipo de acciones para aplicar en ellas: indexar, nuevo, editar, crear, actualizar y destruir.

Establecí una STI como esta:

Modelo de anuncio: ad.rb

class Ad < ActiveRecord::Base end 

Modelo de oferta: bargain.rb

 class Bargain < Ad end 

Resaltar modelo: highlight.rb

 class Highlight < Ad end 

El problema es que me gustaría tener un solo controlador ( AdsController ) que ejecute las acciones que dije en las ofertas o destacados según la URL, por ejemplo, www.foo.com/bargains [/ …] o www.foo. com / highlights [/ …].

Por ejemplo:

  • OBTENGA www.foo.com/highlights => una lista de todos los anuncios destacados.
  • OBTENER formulario www.foo.com/highlights/new => para crear un nuevo resaltado, etc.

¿Cómo puedo hacer eso?

¡Gracias!

Primero. Agregue algunas rutas nuevas:

 resources :highlights, :controller => "ads", :type => "Highlight" resources :bargains, :controller => "ads", :type => "Bargain" 

Y arregle algunas acciones en AdsController . Por ejemplo:

 def new @ad = Ad.new() @ad.type = params[:type] end 

Para un mejor enfoque para todo este trabajo de controlador mira este comentario

Eso es todo. Ahora puede ir a localhost:3000/highlights/new y se Highlight nuevo Highlight .

La acción del índice puede verse así:

 def index @ads = Ad.where(:type => params[:type]) end 

Vaya a localhost:3000/highlights y aparecerá una lista de destacados.
La misma manera para las gangas: 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 %> 

por ser polimórfico 🙂

 < %= link_to 'index', @ad.class %> 

fl00r tiene una buena solución, sin embargo, haría un ajuste.

Esto puede o no ser requerido en su caso. Depende de qué comportamiento está cambiando en sus modelos de STI, especialmente las validaciones y los ganchos de ciclo de vida.

Agregue un método privado a su controlador para convertir su tipo param a la constante de clase real que desea usar:

 def ad_type params[:type].constantize end 

Lo anterior es inseguro, sin embargo. Agregue una lista blanca de tipos:

 def ad_types [MyType, MyType2] end def ad_type params[:type].constantize if params[:type].in? ad_types end 

Más sobre el método de constante de raíles aquí: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize

Luego, en las acciones del controlador puedes hacer:

 def new ad_type.new end def create ad_type.new(params) # ... end def index ad_type.all end 

Y ahora está utilizando la clase real con el comportamiento correcto en lugar de la clase padre con el tipo de atributo establecido.

Solo quería incluir este enlace porque hay una serie de trucos interesantes relacionados con este tema.

Alex Reisner – Herencia de tabla única en Rails

[Reescrito con una solución más simple que funciona completamente:]

Iterando en las otras respuestas, he encontrado la siguiente solución para un único controlador con herencia de tabla única que funciona bien con Strong Parameters en Rails 4.1. Simplemente incluyendo: escriba como un parámetro permitido provocó un error ActiveRecord::SubclassNotFound si se ingresó un tipo no válido. Además, el tipo no se actualiza porque la consulta SQL busca explícitamente el tipo anterior. En su lugar,: el :type debe actualizarse por separado con update_column si es diferente de lo que está configurado actualmente y es un tipo válido. Tenga en cuenta también que he tenido éxito en SECAR todas las listas de tipos.

 # 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 

Y en el controlador:

  # 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 

Y rutas:

 # 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' 

Finalmente, recomiendo usar la gem del respondedor y establecer el andamiaje para usar un responders_controller, que es compatible con STI. Config para andamios es:

 # config/application.rb config.generators do |g| g.scaffold_controller "responders_controller" end 

Sé que esta es una vieja pregunta por aquí es un patrón que me gusta, que incluye las respuestas de @flOOr y @Alan_Peabody. (Probado en Rails 4.2, probablemente funcione en Rails 5)

En su modelo, cree su lista blanca al inicio. En dev esto debe estar ansioso cargado.

 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 

Ahora podemos hacer referencia a esta lista blanca en cualquier controlador para comstackr el ámbito correcto, así como en una colección para: seleccionar tipo en un formulario, etc.

 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 

Necesitamos manejar nuestros formularios porque el enrutamiento debe enviarse al controlador de la clase base, a pesar del tipo real: del objeto. Para hacer esto, usamos "se convierte" para engañar al creador de formularios en el enrutamiento correcto, y la directiva: como forzar a los nombres de entrada a ser también la clase base. Esta combinación nos permite usar rutas no modificadas (recursos: anuncios) así como la verificación de params fuertes en los parámetros [: ad] que regresan del formulario.

 #/views/ads/_form.html.erb < %= form_for(@ad.becomes(Ad), :as => :ad) do |f| %>