Soy nuevo en rails y estoy un poco atascado con este problema de diseño, que puede ser fácil de resolver, pero no la puedo conseguir en cualquier lugar:
Tengo dos diferentes tipos de anuncios: destacados y ofertas. Ambos tienen los mismos atributos: título, descripción y una imagen (con clip). Ellos también tienen el mismo tipo de acciones a aplicar sobre ellos: índice, nuevo, editar, crear, actualizar y destruir.

Me puse una ITS como este:

Modelo Ad: ad.rb

class Ad < ActiveRecord::Base
end

Modelo de negocio: negociar.rb

class Bargain < Ad
end

Resaltar Modelo: resaltar.rb

class Highlight < Ad
end

El problema es que me gustaría tener sólo un controlador (AdsController) que ejecuta las acciones que he dicho en gangas o los puntos de luz dependiendo de la dirección URL, dicen www.foo.com/bargains[/…] o www.foo.com/highlights[/…].

Por ejemplo:

  • OBTENER www.foo.com/highlights => una lista de todos los anuncios que se destaca.
  • OBTENER www.foo.com/highlights/new => formulario para crear un nuevo punto culminante
    etc…

¿Cómo puedo hacer eso?

Gracias!

InformationsquelleAutor Pizzicato | 2011-03-09

5 Comentarios

  1. 99

    Primera. Agregar algunas nuevas rutas:

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

    Y corregir algunas acciones en AdsController. Por ejemplo:

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

    Para el mejor enfoque para todos este controlador mirada este comentario

    Eso es todo. Ahora usted puede ir a localhost:3000/highlights/new y nueva Highlight será inicializado.

    Índice de acción puede tener este aspecto:

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

    Ir a localhost:3000/highlights y la lista de destacados aparecerá.

    Misma manera para los negocios: localhost:3000/bargains

    etc

    URL

    <%= link_to 'index', :highlights %>
    <%= link_to 'new', [:new, :highlight] %>
    <%= link_to 'edit', [:edit, @ad] %>
    <%= link_to 'destroy', @ad, :method => :delete %>

    por ser polimórficos 🙂

    <%= link_to 'index', @ad.class %>
    • Sí!! Gracias, funciona a la perfección!! Yo sabía que había una manera de hacer esto en un lugar SECO y Tranquilo manera. El único problema que tengo ahora es cómo establecer las rutas de acceso y url correctamente, dependiendo del tipo de anuncio. Supongo que puedo hacerlo sólo comprobar el tipo de valor, pero no me parece que, muy inteligente… ¿alguna idea?
    • bien, ya he terminado, he utilizado polymorphic_path para configurar las rutas de acceso en los puntos de vista así como de Alan idea (justo debajo) para el uso de la clase adecuada dependiendo de la dirección URL (las iluminaciones o las gangas)… no estoy totalmente convencido, pero funciona y se ve bien.
    • He actualizado el post para las urls 🙂
    • Hmmm… no sé acerca de eso… cuando usted tiene un controlador que utiliza las mismas vistas, en este caso, ads_controller, no se debe especificar :destacados, o :gangas. La manera en que yo lo hice: link_to "new", new_polymorphic_path(@ad.class)
    • usted puede :). Se le enviará a, por ejemplo, higlights_path que reffers a ads_controller. Acabo de probarlo :). En realidad, esto es polymorphic_path así
    • Sí, se puede, pero lo que quiero decir es que, esa visión puede ser llamado con una ganga o un resalte objeto. El uso de plymorphic_path, no importa, este helper figuras para usted… no sé si estoy siendo claro… 🙁
    • oh, lo he Conseguido. usted puede tratar de <%= link_to 'index', @ad.class %> así como la forma abreviada para polymorphic_path
    • Si usted watn a prueba de controladores con rspec, uso xhr :get, :new, :use_route => 'new_ highlights'
    • Sugiero establecer una dinámica constante, por ejemplo, AdType en un before_filter para hacer que el código sea un poco más fácil a la vista.

  2. 64

    fl00r tiene una buena solución, sin embargo me gustaría hacer un ajuste.

    Esto puede o puede no ser necesaria en su caso. Depende de lo que el comportamiento está cambiando en tu ITS modelos, especialmente las validaciones & ciclo de vida de los ganchos.

    Agregar un método privado para su controlador para convertir el tipo de parámetro a la actual constante de la clase que desea utilizar:

    def ad_type
      params[:type].constantize
    end

    El de arriba es inseguro, sin embargo. Agregar 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 los rieles constantize método aquí: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize

    Luego en el controlador de acciones que se pueden 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 de padre con el tipo de atributo establecido.

    • buen punto, +1.
    • FWIW, tuve que establecer el método create a ser: ad_type.new(params[params[:tipo].downcase])
    • Creo que su método debe ser actualizado a la params[:type].underscore. Ejemplo: si el tipo es BrandNewType, a continuación, usted debe referirse a params['brand_new_type'] no params['brandnewtype']
    • no utilice este método tal como está en el código de producción. puede no ser evidente a primera, pero por defecto, este Anuncio#ad_type método implícitamente confía en datos proporcionados por el usuario, rompiendo una regla básica de la aplicación web de la seguridad. Imaginar una solicitud POST a /ads?type=User con una capacidad de carga incluyendo is_admin=1 (o lo que sea aplicable el mecanismo de autenticación en uso). Ahora el atacante usuario tiene su propia cuenta de usuario con derechos de administrador! (Técnicamente, este tipo de ataque podría ser derrotado por la alteración o eliminación del defecto ads rutas, pero una nueva ruta más tarde podría involuntariamente abrir el agujero de nuevo.)
    • Solución para el problema de seguridad es proporcionar una lista blanca. Algo así como: allowed_types = [MyType, MyType2], entonces params[:type].constantize if params[:type].in? allowed_types
    • La matriz de válido ad_types deben ser convertidos en cadenas antes de la comprobación de la inclusión de params[:type]

  3. 12

    Sólo quería incluir este vínculo, porque hay una serie de trucos interesantes todos los relacionados con este tema.

    Alex Reisner – Tabla Única Herencia en Rails

  4. 1

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

    En su modelo, crear su lista blanca en el inicio. En dev esto debe ser cargado con ganas.

    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 construir el ámbito correcto, así como en una colección para una :escriba select 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

    Que necesitamos para manejar nuestras formas porque el enrutamiento debe ser enviado a la clase base controller, a pesar de la actual :tipo de objeto. Para hacer esto usamos «se convierte en» engañar el constructor del formulario, en la ruta correcta, y el :como la directiva para forzar la entrada de los nombres de la clase base así. Esta combinación nos permite el uso sin modificar las rutas (recursos :ads), así como la fuerte parámetros de verificación en el params[:ad] de regresar de la forma.

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

    [Reescrito con la solución más sencilla que funciona de forma completamente:]

    La iteración en las otras respuestas, he llegado a la siguiente solución para un único controlador con Mesa Única Herencia que funciona bien con Fuerte Parámetros en Rails 4.1. Sólo incluyendo :tipo como parámetro permite provocó un ActiveRecord::SubclassNotFound de error si un tipo no válido se introduce. Por otra parte, el tipo no se actualiza porque la consulta SQL explícitamente busca el tipo antiguo. En su lugar, :type necesita ser actualizado por separado con update_column si es diferente de lo que es actual y es un tipo válido. Nota también que he logrado en Secando 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'

    Por último, recomiendo el uso de la los socorristas joya y la configuración de los andamios para utilizar un responders_controller, que es compatible con las ITS. Config para el andamio es:

    # config/application.rb
        config.generators do |g|
          g.scaffold_controller "responders_controller"
        end
    • Me gusta el validador. Echa un vistazo a mi la variación que se basa la lista blanca basada en la subclase, y se ajusta a los formularios en lugar de tener rutas para cada tipo. Esto es bueno si usted tiene un montón de rutas anidadas y no quiere un enorme bloque de cerca-duplicados para cada una de las subclases.

Dejar respuesta

Please enter your comment!
Please enter your name here