Schienen erweitern ActiveRecord :: Base


160

Ich habe etwas darüber gelesen, wie die ActiveRecord: Base-Klasse erweitert werden kann, damit meine Modelle einige spezielle Methoden haben. Was ist der einfache Weg, um es zu erweitern (Schritt-für-Schritt-Tutorial)?


Welche Art von Erweiterungen? Wir brauchen wirklich mehr, um weiterzumachen.
Jonnii

Antworten:


336

Es gibt verschiedene Ansätze:

Verwenden von ActiveSupport :: Concern (bevorzugt)

Weitere Informationen finden Sie in der Dokumentation zu ActiveSupport :: Concern .

Erstellen Sie eine Datei, die active_record_extension.rbim libVerzeichnis aufgerufen wird .

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)

Erstellen Sie eine Datei in dem config/initializersVerzeichnis mit dem Namen extensions.rbund fügen Sie der Datei die folgende Zeile hinzu:

require "active_record_extension"

Vererbung (bevorzugt)

Siehe Tobys Antwort .

Affenflicken (sollte vermieden werden)

Erstellen Sie eine Datei im config/initializersVerzeichnis mit dem Namen 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

Das berühmte Zitat über reguläre Ausdrücke von Jamie Zawinski kann neu definiert werden, um die Probleme zu veranschaulichen, die mit dem Patchen von Affen verbunden sind.

Einige Leute denken, wenn sie mit einem Problem konfrontiert werden: "Ich weiß, ich werde Affen-Patches verwenden." Jetzt haben sie zwei Probleme.

Das Patchen von Affen ist einfach und schnell. Die eingesparte Zeit und Mühe wird jedoch immer irgendwann in der Zukunft zurückgewonnen. mit Zinseszins. Heutzutage beschränke ich das Patchen von Affen, um schnell eine Lösung in der Rails-Konsole zu erstellen.


3
Sie müssen requiredie Datei am Ende von environment.rb. Ich habe diesen zusätzlichen Schritt zu meiner Antwort hinzugefügt.
Harish Shetty

1
@ HartleyBrody ist es nur eine Frage der Präferenz. Wenn Sie die Vererbung verwenden, müssen Sie eine neue einführen ImprovedActiveRecordund von dieser erben. Wenn Sie die Vererbung verwenden module, aktualisieren Sie die Definition der betreffenden Klasse. Ich habe früher die Vererbung verwendet (aufgrund jahrelanger Java / C ++ - Erfahrung). Heutzutage benutze ich meistens Module.
Harish Shetty

1
Es ist ein wenig ironisch, dass Ihr Link tatsächlich kontextualisiert und darauf hingewiesen hat, wie Leute das Zitat falsch verwenden und überbeanspruchen. Aber im Ernst, ich habe Probleme zu verstehen, warum "Affen-Patching" in diesem Fall nicht der beste Weg wäre. Wenn Sie mehrere Klassen hinzufügen möchten, ist ein Modul natürlich der richtige Weg. Aber wenn Ihr Ziel darin besteht, eine Klasse zu erweitern, hat Ruby es dann nicht so einfach gemacht, Klassen auf diese Weise zu erweitern?
MCB

1
@MCB, Jedes große Projekt enthält nur wenige Geschichten über einen schwer zu lokalisierenden Fehler, der durch das Patchen von Affen verursacht wurde. Hier ist ein Artikel von Avdi über die Übel des Patchens : devblog.avdi.org/2008/02/23/… . Ruby 2.0 führt eine neue Funktion ein, Refinementsdie die meisten Probleme beim Patchen von Affen behebt ( yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice ). Manchmal gibt es eine Funktion, die Sie nur dazu zwingt, das Schicksal in Versuchung zu führen. Und manchmal tust du es.
Harish Shetty

1
@TrantorLiu Ja. Ich habe die Antwort aktualisiert, um die neueste Dokumentation wiederzugeben (anscheinend wurde class_methods 2014 eingeführt github.com/rails/rails/commit/… )
Harish Shetty

70

Sie können die Klasse einfach erweitern und einfach die Vererbung verwenden.

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end

Ich mag diese Idee, weil es eine Standardmethode ist, aber ... Ich erhalte eine Fehler-Tabelle 'moboolo_development.abstract_models' existiert nicht: SHOW FIELDS FROM abstract_models. Wo soll ich es hinstellen?
Xpepermint

23
Fügen Sie self.abstract_class = trueIhrem hinzu AbstractModel. Rails erkennt das Modell nun als abstraktes Modell.
Harish Shetty

Beeindruckend! Ich hätte nicht gedacht, dass das möglich ist. Versuchte es früher und gab auf, als ActiveRecord erstickte und nach dem AbstractModelin der Datenbank suchte . Wer wusste, dass ein einfacher Setter mir helfen würde, die Dinge zu trocknen! (Ich fing an zu schaudern ... es war schlimm). Danke Toby und Harish!
Dooleyo

In meinem Fall ist dies definitiv der beste Weg, dies zu tun: Ich erweitere meine Modellfähigkeiten hier nicht mit fremden Methoden, sondern überarbeite Commmon-Methoden für Objekte mit ähnlichem Verhalten meiner App. Vererbung macht hier viel mehr Sinn. Es gibt keinen bevorzugten Weg, sondern 2 Lösungen, je nachdem, was Sie erreichen möchten!
Augustin Riedinger

Dies funktioniert bei mir in Rails4 nicht. Ich habe abstract_model.rb erstellt und in mein Modellverzeichnis gestellt. innerhalb des Modells hatte es die self.abstract_class = true Dann habe ich eines meiner anderen Modelle geerbt ... User <AbstractModel . In der Konsole erhalte ich: Benutzer (rufen Sie 'User.connection' auf, um eine Verbindung herzustellen)
Joel Grannas

21

Sie können auch verwenden ActiveSupport::Concernund mehr Rails Kern idiomatisch sein wie:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

[Bearbeiten] nach dem Kommentar von @daniel

Dann haben alle Ihre Modelle die Methode fooals Instanzmethode und die Methoden ClassMethodsals Klassenmethoden. ZB auf einem haben FooBar < ActiveRecord::BaseSie: FooBar.barundFooBar#foo

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


5
Beachten Sie, dass dies InstanceMethodsseit Rails 3.2 veraltet ist. Fügen Sie einfach Ihre Methoden in den Modulkörper ein.
Daniel Rikowski

Ich habe ActiveRecord::Base.send(:include, MyExtension)einen Initialisierer eingesetzt und dann hat das bei mir funktioniert. Schienen 4.1.9
6ft Dan

18

Mit Rails 4 wurde das Konzept der Verwendung von Bedenken zur Modularisierung und Trocknung Ihrer Modelle hervorgehoben.

Bedenken ermöglichen es Ihnen grundsätzlich, ähnlichen Code eines Modells oder über mehrere Modelle hinweg in einem einzigen Modul zu gruppieren und dieses Modul dann in den Modellen zu verwenden. Hier ist ein Beispiel:

Betrachten Sie ein Artikelmodell, ein Ereignismodell und ein Kommentarmodell. Ein Artikel oder eine Veranstaltung hat viele Kommentare. Ein Kommentar gehört entweder zum Artikel oder zum Ereignis.

Traditionell sehen die Modelle folgendermaßen aus:

Kommentar Modell:

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

Artikelmodell:

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

Ereignismodell

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

Wie wir feststellen können, gibt es einen wichtigen Code, der sowohl dem Ereignis- als auch dem Artikelmodell gemeinsam ist. Mit Bedenken können wir diesen gemeinsamen Code in einem separaten Modul Commentable extrahieren.

Erstellen Sie dazu eine commentable.rb-Datei in App / Model / Concerns.

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

Und jetzt sehen Ihre Modelle so aus:

Kommentar Modell:

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

Artikelmodell:

class Article < ActiveRecord::Base
    include Commentable
end

Ereignismodell

class Event < ActiveRecord::Base    
    include Commentable
end

Ein Punkt, den ich bei der Verwendung von Bedenken hervorheben möchte, ist, dass Bedenken eher für die domänenbasierte Gruppierung als für die technische Gruppierung verwendet werden sollten. Beispielsweise ist eine Domänengruppierung wie "Kommentierbar", "Taggable" usw. Eine technisch basierte Gruppierung ist wie "FinderMethods", "ValidationMethods".

Hier ist ein Link zu einem Beitrag , den ich sehr nützlich fand, um Bedenken in Modellen zu verstehen.

Hoffe das Aufschreiben hilft :)


7

Schritt 1

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

Schritt 2

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

Schritt 3

There is no step 3 :)

1
Ich denke, Schritt 2 muss in config / environment.rb platziert werden. Es funktioniert nicht für mich :(. Können Sie bitte noch etwas Hilfe schreiben? Thx.
xpepermint

5

Die Schienen 5 bieten einen eingebauten Mechanismus zum Ausfahren ActiveRecord::Base.

Dies wird durch die Bereitstellung einer zusätzlichen Schicht erreicht:

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

und alle Modelle erben von diesem:

class Post < ApplicationRecord
end

Siehe zB diesen Blogpost .


4

Um dieses Thema zu ergänzen, habe ich eine Weile daran gearbeitet, solche Erweiterungen zu testen (ich bin den ActiveSupport::ConcernWeg gegangen .)

So richte ich ein Modell zum Testen meiner Erweiterungen ein.

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

Mit Rails 5 werden alle Modelle von ApplicationRecord geerbt und bieten eine gute Möglichkeit, andere Erweiterungsbibliotheken einzuschließen oder zu erweitern.

# 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

Angenommen, das Modul für spezielle Methoden muss für alle Modelle verfügbar sein, und fügen Sie es in die Datei application_record.rb ein. Wenn wir dies für einen bestimmten Satz von Modellen anwenden möchten, fügen Sie es in die jeweiligen Modellklassen ein.

# 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

Wenn Sie die Methoden im Modul als Klassenmethoden definieren möchten, erweitern Sie das Modul auf ApplicationRecord.

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

Hoffe es hilft anderen!


0

ich habe

ActiveRecord::Base.extend Foo::Bar

in einem Initialisierer

Für ein Modul wie unten

module Foo
  module Bar
  end
end
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.