Rel memperluas ActiveRecord :: Base

160

Saya telah melakukan beberapa bacaan tentang cara memperluas ActiveRecord: Kelas dasar sehingga model saya akan memiliki beberapa metode khusus. Apa cara mudah untuk memperpanjangnya (tutorial langkah demi langkah)?

xpepermint
sumber
Ekstensi seperti apa? Kami benar-benar membutuhkan lebih banyak untuk melanjutkan.
Jonnii

Jawaban:

336

Ada beberapa pendekatan:

Menggunakan ActiveSupport :: Concern (Lebih disukai)

Baca dokumentasi ActiveSupport :: Concern untuk lebih jelasnya.

Buat file yang disebut active_record_extension.rbdi libdirektori.

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)

Buat file di config/initializersdirektori bernama extensions.rbdan tambahkan baris berikut ke file:

require "active_record_extension"

Warisan (Diutamakan)

Lihat jawaban Toby .

Penambalan monyet (Harus dihindari)

Buat file di config/initializersdirektori yang disebut 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

Kutipan terkenal tentang ekspresi Reguler oleh Jamie Zawinski dapat bertujuan kembali untuk mengilustrasikan masalah yang terkait dengan patch-monyet.

Beberapa orang, ketika dihadapkan dengan masalah, berpikir "Saya tahu, saya akan menggunakan tambalan monyet." Sekarang mereka memiliki dua masalah.

Penambalan monyet mudah dan cepat. Tetapi, waktu dan upaya yang dihemat selalu diambil kembali di masa depan; dengan bunga majemuk. Hari ini saya membatasi patch monyet untuk dengan cepat membuat prototipe solusi di konsol rel.

Harish Shetty
sumber
3
Anda harus ke requirefile di akhir environment.rb. Saya telah menambahkan langkah ekstra ini untuk jawaban saya.
Harish Shetty
1
@ Hartley Brody itu hanya masalah preferensi. Jika Anda menggunakan warisan, Anda harus memperkenalkan yang baru ImprovedActiveRecorddan mewarisinya, saat Anda menggunakan module, Anda memperbarui definisi kelas yang dimaksud. Saya dulu menggunakan warisan (karena pengalaman Java / C ++ bertahun-tahun). Saat ini saya lebih banyak menggunakan modul.
Harish Shetty
1
Agak ironis bahwa tautan Anda benar-benar mengontekstualisasikan dan menunjukkan bagaimana orang menyalahgunakan dan mengutip terlalu banyak. Tetapi dalam semua keseriusan saya mengalami kesulitan memahami mengapa "tambalan monyet" tidak akan menjadi cara terbaik dalam kasus ini. Jika Anda ingin menambahkan ke beberapa kelas maka modul jelas cara untuk pergi. Tetapi jika tujuan Anda adalah untuk memperluas satu kelas maka bukankah itu sebabnya Ruby membuat perluasan kelas dengan cara ini begitu mudah?
MCB
1
@ MCB, Setiap proyek besar memiliki beberapa cerita tentang bug yang sulit ditemukan karena patching monyet. Berikut adalah artikel oleh Avdi tentang kejahatan patching: devblog.avdi.org/2008/02/23/… . Ruby 2.0 memperkenalkan fitur baru yang disebut Refinementsyang mengatasi sebagian besar masalah dengan patch monyet ( yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice ). Terkadang ada fitur yang hanya memaksa Anda untuk menggoda nasib. Dan terkadang Anda melakukannya.
Harish Shetty
1
@ ManajerLiu Ya. Saya telah memperbarui jawaban untuk mencerminkan dokumentasi terbaru (sepertinya class_methods diperkenalkan pada 2014 github.com/rails/rails/commit/… )
Harish Shetty
70

Anda bisa memperpanjang kelas dan cukup menggunakan warisan.

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end
Toby Hede
sumber
Saya suka ide ini karena merupakan cara standar untuk melakukannya tetapi ... Saya mendapatkan tabel kesalahan 'moboolo_development.abstract_models' tidak ada: SHOW FIELDS FROM abstract_models. Di mana saya harus meletakkannya?
xpepermint
23
Tambahkan self.abstract_class = trueke AbstractModel. Rails sekarang akan mengenali model tersebut sebagai model abstrak.
Harish Shetty
Wow! Tidak mengira ini mungkin. Sudah mencoba sebelumnya dan menyerah ketika ActiveRecord tersedak mencari AbstractModeldi database. Siapa yang tahu penyetel sederhana akan membantu saya KERING! (Saya mulai merasa ngeri ... itu buruk). Terima kasih Toby dan Harish!
dooleyo
Dalam kasus saya ini jelas merupakan cara terbaik untuk melakukan ini: Saya tidak memperluas kemampuan model saya dengan metode asing di sini, tetapi refactoring metode commmon untuk objek berperilaku serupa dari aplikasi saya. Warisan jauh lebih masuk akal di sini. Tidak ada cara yang disukai selain 2 solusi tergantung pada apa yang ingin Anda capai!
Augustin Riedinger
Ini tidak berfungsi untuk saya di Rails4. Saya membuat abstract_model.rb dan dimasukkan ke dalam direktori model saya. di dalam model itu memiliki self.abstract_class = true Kemudian saya membuat dari model saya yang lain mewarisi ... Pengguna <AbstractModel . Di konsol saya mendapatkan: Pengguna (panggil 'User.connection' untuk membuat koneksi)
Joel Grannas
21

Anda juga dapat menggunakan ActiveSupport::Concerndan menjadi lebih idiom inti Rails seperti:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

[Sunting] mengikuti komentar dari @daniel

Maka semua model Anda akan foomemasukkan metode sebagai metode instance dan metode yang ClassMethodsdimasukkan sebagai metode kelas. Misalnya pada FooBar < ActiveRecord::BaseAnda akan memiliki: FooBar.bardanFooBar#foo

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

nikola
sumber
5
Catatan yang InstanceMethodssudah usang sejak Rails 3.2, cukup masukkan metode Anda ke dalam modul body.
Daniel Rikowski
Saya menempatkan ActiveRecord::Base.send(:include, MyExtension)inisialisasi dan kemudian ini bekerja untuk saya. Rails 4.1.9
6ft Dan
18

Dengan Rails 4, konsep menggunakan kekhawatiran untuk memodulasi dan KERING model Anda telah menjadi sorotan.

Kekhawatiran pada dasarnya memungkinkan Anda untuk mengelompokkan kode yang serupa dari suatu model atau melintasi beberapa model dalam satu modul dan kemudian menggunakan modul ini dalam model. Berikut ini sebuah contoh:

Pertimbangkan model Artikel, model Acara dan Model Komentar. Artikel atau acara memiliki banyak komentar. Komentar adalah milik artikel atau acara.

Secara tradisional, modelnya mungkin terlihat seperti ini:

Model komentar:

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

Model artikel:

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

Model Peristiwa

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

Seperti yang dapat kita perhatikan, ada potongan kode penting yang umum untuk Peristiwa dan Model Artikel. Dengan menggunakan kekhawatiran, kami dapat mengekstrak kode umum ini dalam modul yang terpisah, Commentable.

Untuk ini, buat file commentable.rb di 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

Dan sekarang model Anda terlihat seperti ini:

Model komentar:

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

Model artikel:

class Article < ActiveRecord::Base
    include Commentable
end

Model Peristiwa

class Event < ActiveRecord::Base    
    include Commentable
end

Satu hal yang saya ingin soroti saat menggunakan Kekhawatiran adalah bahwa Kekhawatiran harus digunakan untuk pengelompokan 'berbasis domain' daripada pengelompokan 'teknis'. Misalnya, pengelompokan domain seperti 'Commentable', 'Taggable' dll. Pengelompokan berbasis teknis akan seperti 'FinderMethods', 'ValidationMethods'.

Berikut ini tautan ke pos yang menurut saya sangat berguna untuk memahami masalah dalam Model.

Semoga Langgan membantu :)

Aaditi Jain
sumber
7

Langkah 1

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

Langkah 2

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

Langkah 3

There is no step 3 :)
Vitaly Kushner
sumber
1
Saya kira langkah 2 harus ditempatkan ke config / environment.rb. Ini tidak berfungsi untuk saya :(. Bisakah Anda menulis beberapa bantuan lagi? Thx.
xpepermint
5

Rails 5 menyediakan mekanisme bawaan untuk memperpanjang ActiveRecord::Base.

Ini dicapai dengan memberikan lapisan tambahan:

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

dan semua model mewarisi dari yang itu:

class Post < ApplicationRecord
end

Lihat misalnya blogpost ini .

Adobe
sumber
4

Hanya untuk menambah topik ini, saya menghabiskan waktu mencari cara untuk menguji ekstensi tersebut (saya pergi jalan ActiveSupport::Concern.)

Inilah cara saya menyiapkan model untuk menguji ekstensi saya.

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 Tomlin
sumber
4

Dengan Rails 5, semua model diwarisi dari ApplicationRecord & ini memberikan cara yang bagus untuk memasukkan atau memperluas perpustakaan ekstensi lainnya.

# 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

Misalkan modul metode khusus harus tersedia di semua model, sertakan dalam file application_record.rb. Jika kami ingin menerapkan ini untuk serangkaian model tertentu, maka sertakan dalam kelas model masing-masing.

# 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

Jika Anda ingin agar metode yang didefinisikan dalam modul sebagai metode kelas, perluas modul ke ApplicationRecord.

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

Semoga ini bisa membantu orang lain!

Ashik Salman
sumber
0

saya sudah

ActiveRecord::Base.extend Foo::Bar

dalam penginisialisasi

Untuk modul seperti di bawah ini

module Foo
  module Bar
  end
end
Ed Richards
sumber