Trilhos estendendo ActiveRecord :: Base


160

Eu li algumas coisas sobre como estender a classe ActiveRecord: Base para que meus modelos tenham alguns métodos especiais. Qual é a maneira mais fácil de estendê-lo (tutorial passo a passo)?


Que tipo de extensões? Nós realmente precisamos de mais para continuar.
21810 jonnii

Respostas:


336

Existem várias abordagens:

Usando ActiveSupport :: Concern (preferencial)

Leia a documentação do ActiveSupport :: Concern para obter mais detalhes.

Crie um arquivo chamado active_record_extension.rbno libdiretório

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)

Crie um arquivo no config/initializersdiretório chamado extensions.rbe adicione a seguinte linha ao arquivo:

require "active_record_extension"

Herança (Preferida)

Consulte a resposta de Toby .

Remendo de macaco (deve ser evitado)

Crie um arquivo no config/initializersdiretório chamado 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

A famosa citação sobre expressões regulares de Jamie Zawinski pode ser redefinida para ilustrar os problemas associados à correção de macacos.

Algumas pessoas, quando confrontadas com um problema, pensam: "Eu sei, usarei remendo de macacos". Agora eles tem dois problemas.

O patch dos macacos é fácil e rápido. Mas, o tempo e o esforço economizados são sempre extraídos de volta em algum momento no futuro; com juros compostos. Hoje em dia, limitei a aplicação de patches de macaco para prototipar rapidamente uma solução no console do rails.


3
Você tem que requireo arquivo no final de environment.rb. Eu adicionei esta etapa extra à minha resposta.
Harish Shetty

1
@HartleyBrody, é apenas uma questão de preferência. Se você usa herança, é necessário introduzir um novo ImprovedActiveRecorde herdá-lo; ao usar module, você está atualizando a definição da classe em questão. Eu costumava usar herança (causa de anos de experiência em Java / C ++). Hoje em dia eu uso principalmente módulos.
Harish Shetty

1
É um pouco irônico que seu link esteja realmente contextualizando e apontando como as pessoas usam e abusam da citação. Mas com toda a seriedade, estou tendo problemas para entender por que "remendar macacos" não seria a melhor maneira nesse caso. Se você deseja adicionar a várias classes, um módulo é obviamente o caminho a seguir. Mas se seu objetivo é estender uma classe, não é por isso que Ruby tornou tão fácil estender as classes?
MCB

1
@ MCB, Todo grande projeto tem poucas histórias sobre um bug difícil de localizar introduzido devido à aplicação de patches em macacos. Aqui está um artigo da Avdi sobre os males do patch: devblog.avdi.org/2008/02/23/… . O Ruby 2.0 apresenta um novo recurso chamado Refinementsque aborda a maioria dos problemas relacionados à aplicação de patches de macaco ( yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice ). Às vezes, um recurso existe apenas para obrigá-lo a tentar o destino. E às vezes você faz.
amigos estão dizendo

1
@TrantorLiu Sim. Atualizei a resposta para refletir a documentação mais recente (parece que class_methods foi introduzido em 2014 github.com/rails/rails/commit/… )
Harish Shetty

70

Você pode apenas estender a classe e simplesmente usar herança.

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end

Eu gosto dessa idéia porque é uma maneira padrão de fazê-lo, mas ... Recebo um erro A tabela 'moboolo_development.abstract_models' não existe: SHOW FIELDS FROM abstract_models. Onde devo colocá-lo?
Xpepermint

23
Adicione self.abstract_class = trueao seu AbstractModel. O Rails agora reconhecerá o modelo como um modelo abstrato.
Harish Shetty

Uau! Não achava que isso era possível. Tentei mais cedo e desisti quando o ActiveRecord engasgou procurando o AbstractModelno banco de dados. Quem sabia que um simples levantador me ajudaria a secar as coisas! (Eu estava começando a me encolher ... era ruim). Obrigado Toby e Harish!
Doeleyo

No meu caso, é definitivamente a melhor maneira de fazer isso: não estou estendendo minhas habilidades de modelo com métodos estrangeiros aqui, mas refatorando métodos comuns para objetos de comportamento semelhante do meu aplicativo. Herança faz muito mais sentido aqui. Não há maneira preferida, mas 2 soluções, dependendo do que você deseja alcançar!
Augustin Riedinger 26/09

Isso não funciona para mim no Rails4. Eu criei abstract_model.rb e coloquei no meu diretório de modelos. dentro do modelo ele tinha o self.abstract_class = true Então eu criei um dos meus outros modelos herdando ... User <AbstractModel . Na consola eu recebo: Usuário (chamada 'User.connection' para estabelecer uma conexão)
Joel Grannas

21

Você também pode usar ActiveSupport::Concerne ser mais idiomático do núcleo do Rails, como:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

[Editar] após o comentário de @daniel

Todos os seus modelos terão o método fooincluído como método de instância e os métodos ClassMethodsincluídos como métodos de classe. Por exemplo, em um FooBar < ActiveRecord::Basevocê terá: FooBar.bareFooBar#foo

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


5
Observe que InstanceMethodsestá obsoleto desde o Rails 3.2, basta colocar seus métodos no corpo do módulo.
Daniel Rikowski 12/12

Coloquei ActiveRecord::Base.send(:include, MyExtension)um inicializador e, em seguida, isso funcionou para mim. Rails 4.1.9
6ft Dan

18

Com o Rails 4, o conceito de usar preocupações para modularizar e secar seus modelos tem sido destaque.

As preocupações basicamente permitem agrupar código semelhante de um modelo ou entre vários modelos em um único módulo e, em seguida, usar esse módulo nos modelos. Aqui está um exemplo:

Considere um modelo de artigo, um modelo de evento e um modelo de comentário. Um artigo ou evento tem muitos comentários. Um comentário pertence ao artigo ou evento.

Tradicionalmente, os modelos podem ser assim:

Modelo de comentário:

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

Artigo Modelo:

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 observar, há um pedaço significativo de código comum ao modelo de eventos e artigos. Usando preocupações, podemos extrair esse código comum em um módulo separado.

Para isso, crie um arquivo commentable.rb em app / model / preocupações.

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

E agora seus modelos ficam assim:

Modelo de comentário:

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

Artigo Modelo:

class Article < ActiveRecord::Base
    include Commentable
end

Modelo de Evento

class Event < ActiveRecord::Base    
    include Commentable
end

Um ponto que gostaria de destacar ao usar o Concerns é que o Concerns deve ser usado para agrupamentos 'baseados em domínio' em vez de agrupamentos 'técnicos'. Por exemplo, um agrupamento de domínio é como 'Comentável', 'Taggable' etc. Um agrupamento técnico será como 'FinderMethods', 'ValidationMethods'.

Aqui está um link para um post que achei muito útil para entender as preocupações nos modelos.

Espero que a redação ajude :)


7

Passo 1

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

Passo 2

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

etapa 3

There is no step 3 :)

1
Eu acho que a etapa 2 deve ser colocada em config / environment.rb. Não está funcionando para mim :(. Você pode, por favor, escrever mais alguma ajuda? Thx.
xpepermint

5

O Rails 5 fornece um mecanismo interno para extensão ActiveRecord::Base.

Isso é conseguido fornecendo uma camada adicional:

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

e todos os modelos herdam desse:

class Post < ApplicationRecord
end

Veja, por exemplo, este post do blog .


4

Apenas para adicionar a este tópico, passei um tempo tentando descobrir como testar essas extensões (segui o ActiveSupport::Concerncaminho).

Veja como eu configuro um modelo para testar minhas extensões.

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

4

Com o Rails 5, todos os modelos são herdados do ApplicationRecord e oferecem uma boa maneira de incluir ou estender outras bibliotecas de extensão.

# 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

Suponha que o módulo de métodos especiais precise estar disponível em todos os modelos, inclua-o no arquivo application_record.rb. Se quisermos aplicar isso a um conjunto específico de modelos, inclua-o nas respectivas classes de modelo.

# 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

Se você quiser ter os métodos definidos no módulo como métodos de classe, estenda o módulo para ApplicationRecord.

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

Espero que ajude os outros!


0

eu tenho

ActiveRecord::Base.extend Foo::Bar

em um inicializador

Para um módulo como abaixo

module Foo
  module Bar
  end
end
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.