Rieles que extienden ActiveRecord :: Base

160

He leído un poco sobre cómo extender ActiveRecord: Clase base para que mis modelos tengan algunos métodos especiales. ¿Cuál es la manera fácil de extenderlo (tutorial paso a paso)?

xpepermint
fuente
¿Qué tipo de extensiones? Realmente necesitamos más para continuar.
jonnii

Respuestas:

336

Hay varios enfoques:

Usando ActiveSupport :: Preocupación (Preferido)

Lea la documentación de ActiveSupport :: Concern para obtener más detalles.

Cree un archivo llamado active_record_extension.rben el libdirectorio.

require 'active_support/concern'

module ActiveRecordExtension

  extend ActiveSupport::Concern

  # add your instance methods here
  def foo
     "foo"
  end

  # add your static(class) methods here
  class_methods do
    #E.g: Order.top_ten        
    def top_ten
      limit(10)
    end
  end
end

# include the extension 
ActiveRecord::Base.send(:include, ActiveRecordExtension)

Cree un archivo en el config/initializersdirectorio llamado extensions.rby agregue la siguiente línea al archivo:

require "active_record_extension"

Herencia (preferida)

Consulte la respuesta de Toby .

Mono parcheado (debe evitarse)

Cree un archivo en el config/initializersdirectorio llamado active_record_monkey_patch.rb.

class ActiveRecord::Base     
  #instance method, E.g: Order.new.foo       
  def foo
   "foo"
  end

  #class method, E.g: Order.top_ten        
  def self.top_ten
    limit(10)
  end
end

La famosa cita sobre las expresiones regulares de Jamie Zawinski se puede cambiar de propósito para ilustrar los problemas asociados con el parcheado de monos.

Algunas personas, cuando se enfrentan a un problema, piensan "Lo sé, usaré parches de mono". Ahora ellos tienen dos problemas.

El parcheado de monos es fácil y rápido. Pero, el tiempo y el esfuerzo ahorrados siempre se extraen en algún momento en el futuro; con interés compuesto En estos días limito los parches de mono para crear rápidamente una solución prototipo en la consola de rails.

Harish Shetty
fuente
3
Tienes al requirearchivo al final de environment.rb. He agregado este paso adicional a mi respuesta.
Harish Shetty
1
@HartleyBrody es solo una cuestión de preferencia. Si usa la herencia, debe introducir una nueva ImprovedActiveRecordy heredar de ella, cuando está usando module, está actualizando la definición de la clase en cuestión. Solía ​​usar la herencia (causa de años de experiencia en Java / C ++). En estos días uso principalmente módulos.
Harish Shetty
1
Es un poco irónico que su enlace en realidad estuviera contextualizando y señalando cómo la gente usa en exceso la cita. Pero con toda seriedad estoy teniendo problemas para entender por qué "parches de mono" no sería la mejor manera en este caso. Si desea agregar a múltiples clases, entonces un módulo es obviamente el camino a seguir. Pero si su objetivo es extender una clase, ¿no es por eso que Ruby hizo que extender las clases de esta manera fuera tan fácil?
MCB
1
@MCB, cada gran proyecto tiene pocas historias sobre un error difícil de localizar introducido debido a los parches de mono. Aquí hay un artículo de Avdi sobre los males de los parches: devblog.avdi.org/2008/02/23/… . Ruby 2.0 presenta una nueva característica llamada Refinementsque aborda la mayoría de los problemas con parches de mono ( yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice ). A veces hay una función que solo te obliga a tentar al destino. Y a veces lo haces.
Harish Shetty
1
@TrantorLiu Sí. He actualizado la respuesta para reflejar la documentación más reciente (parece que class_methods se introdujo en 2014 github.com/rails/rails/commit/… )
Harish Shetty
70

Simplemente puede extender la clase y simplemente usar la herencia.

class AbstractModel < ActiveRecord::Base  
  self.abstract_class = true
end

class Foo < AbstractModel
end

class Bar < AbstractModel
end
Toby Hede
fuente
Me gusta esta idea porque es una forma estándar de hacerlo, pero ... aparece un error La tabla 'moboolo_development.abstract_models' no existe: MOSTRAR CAMPOS DE abstract_models. ¿Dónde debería ponerlo?
xpepermint
23
Añadir self.abstract_class = truea tu AbstractModel. Rails ahora reconocerá el modelo como un modelo abstracto.
Harish Shetty
¡Guauu! No pensé que esto fuera posible. Lo probé antes y me di por vencido cuando ActiveRecord se atragantó buscando el AbstractModelen la base de datos. ¡Quién sabía que un setter simple me ayudaría a SECAR las cosas! (Estaba empezando a encogerme ... era malo). Gracias Toby y Harish!
dooleyo
En mi caso, definitivamente es la mejor manera de hacer esto: no estoy extendiendo mis habilidades de modelo con métodos extraños aquí, sino refactorizando métodos comunes para objetos de comportamiento similares de mi aplicación. La herencia tiene mucho más sentido aquí. ¡No hay una forma preferida sino 2 soluciones dependiendo de lo que quieras lograr!
Augustin Riedinger
Esto no funciona para mí en Rails4. Creé abstract_model.rb y lo puse en mi directorio de modelos. dentro del modelo tenía el self.abstract_class = true Luego hice que mis otros modelos heredaran ... Usuario <AbstractModel . En la consola obtengo: Usuario (llame a 'User.connection' para establecer una conexión)
Joel Grannas
21

También puede usar ActiveSupport::Concerny ser más Rails core idiomático como:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

ActiveRecord::Base.send(:include, MyExtension)

[Editar] siguiendo el comentario de @daniel

Luego, todos sus modelos tendrán el método fooincluido como método de instancia y los métodos ClassMethodsincluidos como métodos de clase. Por ejemplo, en un FooBar < ActiveRecord::Basetendrá: FooBar.baryFooBar#foo

http://api.rubyonrails.org/classes/ActiveSupport/Concern.html

nikola
fuente
55
Tenga en cuenta que InstanceMethodsestá en desuso desde Rails 3.2, solo ponga sus métodos en el cuerpo del módulo.
Daniel Rikowski
Puse ActiveRecord::Base.send(:include, MyExtension)en un inicializador y luego esto funcionó para mí. Rails 4.1.9
6 pies Dan
18

Con Rails 4, el concepto de utilizar las preocupaciones para modularizar y SECAR sus modelos ha sido destacado.

Las preocupaciones básicamente le permiten agrupar un código similar de un modelo o en varios modelos en un solo módulo y luego usar este módulo en los modelos. Aquí hay un ejemplo:

Considere un modelo de artículo, un modelo de evento y un modelo de comentario. Un artículo o un evento tiene muchos comentarios. Un comentario pertenece a un artículo o evento.

Tradicionalmente, los modelos pueden verse así:

Modelo de comentario:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end

Modelo de artículo:

class Article < ActiveRecord::Base
  has_many :comments, as: :commentable 

  def find_first_comment
    comments.first(created_at DESC)
  end

  def self.least_commented
   #return the article with least number of comments
  end
end

Modelo de evento

class Event < ActiveRecord::Base
  has_many :comments, as: :commentable 

  def find_first_comment
    comments.first(created_at DESC)
  end

  def self.least_commented
   #returns the event with least number of comments
  end
end

Como podemos notar, hay un código significativo común tanto para el Modelo de Evento como para el Artículo. Usando preocupaciones, podemos extraer este código común en un módulo separado Comentario.

Para esto, cree un archivo commentable.rb en app / model / concern

module Commentable
    extend ActiveSupport::Concern

    included do 
        has_many :comments, as: :commentable 
    end

    # for the given article/event returns the first comment
    def find_first_comment
        comments.first(created_at DESC)
    end

    module ClassMethods     
        def least_commented
           #returns the article/event which has the least number of comments
        end
    end 
end

Y ahora tus modelos se ven así:

Modelo de comentario:

    class Comment < ActiveRecord::Base
      belongs_to :commentable, polymorphic: true
    end

Modelo de artículo:

class Article < ActiveRecord::Base
    include Commentable
end

Modelo de evento

class Event < ActiveRecord::Base    
    include Commentable
end

Un punto que me gustaría destacar al usar Preocupaciones es que las Preocupaciones deben usarse para la agrupación 'basada en el dominio' en lugar de la agrupación 'técnica'. Por ejemplo, una agrupación de dominio es como 'Commentable', 'Etiquetable', etc. Una agrupación de base técnica será como 'FinderMethods', 'ValidationMethods'.

Aquí hay un enlace a una publicación que encontré muy útil para comprender las preocupaciones en Modelos.

Espero que la redacción ayude :)

Aaditi Jain
fuente
7

Paso 1

module FooExtension
  def foo
    puts "bar :)"
  end
end
ActiveRecord::Base.send :include, FooExtension

Paso 2

# Require the above file in an initializer (in config/initializers)
require 'lib/foo_extension.rb'

Paso 3

There is no step 3 :)
Vitaly Kushner
fuente
1
Supongo que el paso 2 debe colocarse en config / environment.rb. No está funcionando para mí :(. ¿Puedes por favor escribir más ayuda? Gracias.
xpepermint
5

Los rieles 5 proporcionan un mecanismo incorporado para extender ActiveRecord::Base.

Esto se logra al proporcionar una capa adicional:

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  # put your extensions here
end

y todos los modelos heredan de ese:

class Post < ApplicationRecord
end

Ver, por ejemplo, este blog .

Adobe
fuente
4

Solo para agregar a este tema, pasé un tiempo resolviendo cómo probar tales extensiones (seguí la ActiveSupport::Concernruta).

Así es como configuro un modelo para probar mis extensiones.

describe ModelExtensions do
  describe :some_method do
    it 'should return the value of foo' do
      ActiveRecord::Migration.create_table :test_models do |t|
        t.string :foo
      end

      test_model_class = Class.new(ActiveRecord::Base) do
        def self.name
          'TestModel'
        end

        attr_accessible :foo
      end

      model = test_model_class.new(:foo => 'bar')

      model.some_method.should == 'bar'
    end
  end
end
Will Tomlins
fuente
4

Con Rails 5, todos los modelos se heredan de ApplicationRecord y es una buena manera de incluir o ampliar otras bibliotecas de extensiones.

# app/models/concerns/special_methods.rb
module SpecialMethods
  extend ActiveSupport::Concern

  scope :this_month, -> { 
    where("date_trunc('month',created_at) = date_trunc('month',now())")
  }

  def foo
    # Code
  end
end

Suponga que el módulo de métodos especiales necesita estar disponible en todos los modelos, inclúyalo en el archivo application_record.rb. Si queremos aplicar esto para un conjunto particular de modelos, entonces inclúyalo en las clases de modelo respectivas.

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  include SpecialMethods
end

# app/models/user.rb
class User < ApplicationRecord
  include SpecialMethods

  # Code
end

Si desea tener los métodos definidos en el módulo como métodos de clase, extienda el módulo a ApplicationRecord.

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  extend SpecialMethods
end

¡Espero que ayude a otros!

Ashik Salman
fuente
0

yo tengo

ActiveRecord::Base.extend Foo::Bar

en un inicializador

Para un módulo como el siguiente

module Foo
  module Bar
  end
end
Ed Richards
fuente