Đường ray mở rộng ActiveRecord :: Base


160

Tôi đã đọc một số cách về cách mở rộng ActiveRecord: Lớp cơ sở để các mô hình của tôi có một số phương thức đặc biệt. Cách dễ dàng để mở rộng nó (hướng dẫn từng bước) là gì?


Những loại mở rộng? Chúng tôi thực sự cần nhiều hơn để đi về.
jonnii

Câu trả lời:


336

Có một số cách tiếp cận:

Sử dụng ActiveSupport :: Mối quan tâm (Ưu tiên)

Đọc tài liệu ActiveSupport :: Mối quan tâm để biết thêm chi tiết.

Tạo một tập tin được gọi active_record_extension.rbtrong libthư mục.

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)

Tạo một tệp trong config/initializersthư mục được gọi extensions.rbvà thêm dòng sau vào tệp:

require "active_record_extension"

Kế thừa (Ưu tiên)

Tham khảo câu trả lời của Toby .

Khỉ vá (Nên tránh)

Tạo một tập tin trong config/initializersthư mục được gọi 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

Câu nói nổi tiếng về các biểu thức chính quy của Jamie Zawinski có thể được tái sử dụng để minh họa các vấn đề liên quan đến vá khỉ.

Một số người, khi gặp vấn đề, nghĩ rằng tôi biết, tôi sẽ sử dụng phương pháp vá khỉ. Bây giờ họ có hai vấn đề.

Khỉ vá rất dễ dàng và nhanh chóng. Nhưng, thời gian và công sức tiết kiệm luôn được trích xuất trở lại vào lúc nào đó trong tương lai; với lãi kép. Những ngày này, tôi hạn chế vá khỉ để nhanh chóng tạo ra một giải pháp trong bảng điều khiển đường ray.


3
Bạn phải requiretập tin vào cuối environment.rb. Tôi đã thêm bước bổ sung này vào câu trả lời của tôi.
Harish Shetty

1
@HartleyBrody nó chỉ là vấn đề ưu tiên. Nếu bạn sử dụng tính kế thừa, bạn phải giới thiệu một tính mới ImprovedActiveRecordvà kế thừa từ đó, khi bạn đang sử dụng module, bạn đang cập nhật định nghĩa của lớp đang đề cập. Tôi đã từng sử dụng tính kế thừa (nguyên nhân của nhiều năm kinh nghiệm Java / C ++). Ngày nay tôi chủ yếu sử dụng các mô-đun.
Harish Shetty

1
Thật là mỉa mai khi liên kết của bạn thực sự được bối cảnh hóa và chỉ ra cách mọi người sử dụng sai và lạm dụng trích dẫn. Nhưng trong tất cả sự nghiêm túc, tôi gặp khó khăn trong việc hiểu tại sao "vá khỉ" sẽ không phải là cách tốt nhất trong trường hợp này. Nếu bạn muốn thêm vào nhiều lớp thì một mô-đun rõ ràng là cách để đi. Nhưng nếu mục tiêu của bạn là mở rộng một lớp thì đó không phải là lý do tại sao Ruby tạo ra các lớp mở rộng theo cách này dễ dàng như vậy?
MCB

1
@MCB, Mỗi dự án lớn đều có một vài câu chuyện về một lỗi khó xác định được giới thiệu do vá khỉ. Đây là một bài viết của Avdi về các tệ nạn vá lỗi: devblog.avdi.org/2008/02/23/ . Ruby 2.0 giới thiệu một tính năng mới được gọi là Refinementsgiải quyết hầu hết các vấn đề với việc vá khỉ ( yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice ). Đôi khi một tính năng là có để buộc bạn phải cám dỗ số phận. Và đôi khi bạn làm.
Harish Shetty

1
@TrantorLiu Có. Tôi đã cập nhật câu trả lời để phản ánh tài liệu mới nhất (có vẻ như class_methods đã được giới thiệu vào năm 2014 github.com/rails/rails/commit/ mẹo )
Harish Shetty

70

Bạn chỉ có thể mở rộng lớp và chỉ cần sử dụng kế thừa.

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end

Tôi thích ý tưởng này vì đây là một cách tiêu chuẩn để thực hiện nhưng ... tôi gặp lỗi 'Bảng moboolo_development.abauge_models' không tồn tại: HIỂN THỊ CÁC L FINH VỰC TỪ abstract_models. Tôi nên đặt nó ở đâu?
xpepermint

23
Thêm self.abstract_class = truevào của bạn AbstractModel. Bây giờ Rails sẽ nhận ra mô hình là một mô hình trừu tượng.
Harish Shetty

Ồ Không nghĩ rằng điều này là có thể. Đã thử nó trước đó và bỏ cuộc khi ActiveRecord nghẹn ngào tìm kiếm AbstractModeltrong cơ sở dữ liệu. Ai biết một setter đơn giản sẽ giúp tôi DRY mọi thứ lên! (Tôi đã bắt đầu co rúm ... thật tệ). Cảm ơn Toby và Harish!
dooleyo

Trong trường hợp của tôi, đây chắc chắn là cách tốt nhất để làm điều này: Tôi không mở rộng khả năng mô hình của mình bằng các phương thức nước ngoài ở đây, nhưng tái cấu trúc các phương thức commmon cho các đối tượng ứng xử tương tự trong ứng dụng của tôi. Kế thừa có ý nghĩa hơn nhiều ở đây. Không có cách nào thích hơn nhưng 2 giải pháp tùy thuộc vào những gì bạn muốn đạt được!
Augustin Riedinger

Điều này không làm việc cho tôi trong Rails4. Tôi đã tạo trừu tượng_model.rb và đưa vào thư mục mô hình của tôi. bên trong mô hình, nó có self.abab_group = true Sau đó, tôi đã tạo ra các mô hình khác của mình kế thừa ... Người dùng <AbstractModel . Trong bảng điều khiển tôi nhận được: Người dùng (gọi 'User.connection' để thiết lập kết nối)
Joel Grannas

21

Bạn cũng có thể sử dụng ActiveSupport::Concernvà có nhiều thành ngữ cốt lõi Rails như:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

[Chỉnh sửa] sau nhận xét từ @daniel

Sau đó, tất cả các mô hình của bạn sẽ có phương thức được foobao gồm như một phương thức cá thể và các phương thức được ClassMethodsbao gồm dưới dạng các phương thức lớp. Ví dụ: FooBar < ActiveRecord::Basebạn sẽ có: FooBar.barFooBar#foo

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


5
Lưu ý rằng InstanceMethodskhông được chấp nhận kể từ Rails 3.2, chỉ cần đặt các phương thức của bạn vào thân mô-đun.
Daniel Rikowski

Tôi đặt ActiveRecord::Base.send(:include, MyExtension)trong một bộ khởi tạo và sau đó điều này làm việc cho tôi. Đường ray 4.1.9
6ft Dan

18

Với Rails 4, khái niệm sử dụng các mối quan tâm để mô đun hóa và DRY lên các mô hình của bạn đã được nêu bật.

Về cơ bản, mối quan tâm cho phép bạn nhóm mã tương tự của một mô hình hoặc trên nhiều mô hình trong một mô-đun và sau đó sử dụng mô-đun này trong các mô hình. Đây là một ví dụ:

Xem xét mô hình Bài viết, mô hình Sự kiện và Mô hình Nhận xét. Một bài viết hoặc một sự kiện có nhiều ý kiến. Một bình luận thuộc về một trong hai bài viết hoặc sự kiện.

Theo truyền thống, các mô hình có thể trông như thế này:

Mô hình bình luận:

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

Mô hình bài viết:

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

Mô hình sự kiện

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

Như chúng ta có thể nhận thấy, có một đoạn mã quan trọng phổ biến cho cả Mô hình sự kiện và Mô hình bài viết. Sử dụng mối quan tâm, chúng tôi có thể trích xuất mã phổ biến này trong một mô-đun riêng biệt Có thể nhận xét.

Đối với điều này, hãy tạo một tệp commentable.rb trong ứng dụng / mô hình / mối quan tâm.

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

Và bây giờ mô hình của bạn trông như thế này:

Mô hình bình luận:

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

Mô hình bài viết:

class Article < ActiveRecord::Base
    include Commentable
end

Mô hình sự kiện

class Event < ActiveRecord::Base    
    include Commentable
end

Một điểm tôi muốn nêu bật trong khi sử dụng Mối quan tâm là Mối quan tâm nên được sử dụng cho nhóm 'dựa trên miền' thay vì nhóm 'kỹ thuật'. Ví dụ: một nhóm tên miền giống như 'Có thể nhận xét', 'Có thể gắn thẻ', v.v ... Một nhóm dựa trên kỹ thuật sẽ giống như 'FinderMethods', 'ValidationMethods'.

Đây là một liên kết đến một bài đăng mà tôi thấy rất hữu ích để hiểu mối quan tâm trong Mô hình.

Hy vọng các bài viết giúp :)


7

Bước 1

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

Bước 2

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

Bước 3

There is no step 3 :)

1
Tôi đoán bước 2 phải được đặt vào config / môi trường.rb. Nó không hoạt động với tôi :(. Bạn có thể vui lòng viết thêm một số trợ giúp không? Thx.
xpepermint

5

Rails 5 cung cấp một cơ chế tích hợp để mở rộng ActiveRecord::Base.

Điều này đạt được bằng cách cung cấp lớp bổ sung:

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

và tất cả các mô hình kế thừa từ đó:

class Post < ApplicationRecord
end

Xem ví dụ blogpost này .


4

Chỉ để thêm vào chủ đề này, tôi đã dành một lúc để tìm cách kiểm tra các tiện ích mở rộng như vậy (tôi đã đi theo ActiveSupport::Concerntuyến đường.)

Đây là cách tôi thiết lập một mô hình để kiểm tra các tiện ích mở rộng của mình.

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

Với Rails 5, tất cả các mô hình được kế thừa từ ApplicationRecord và nó mang lại cách hay để bao gồm hoặc mở rộng các thư viện mở rộng khác.

# 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

Giả sử mô-đun phương thức đặc biệt cần có sẵn trên tất cả các mô hình, bao gồm nó trong tệp application_record.rb. Nếu chúng ta muốn áp dụng điều này cho một tập các mô hình cụ thể, thì hãy đưa nó vào các lớp mô hình tương ứng.

# 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

Nếu bạn muốn có các phương thức được định nghĩa trong mô-đun như các phương thức lớp, hãy mở rộng mô-đun sang ApplicationRecord.

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

Hy vọng nó sẽ giúp người khác!


0

Tôi có

ActiveRecord::Base.extend Foo::Bar

trong một bộ khởi tạo

Đối với một mô-đun như dưới đây

module Foo
  module Bar
  end
end
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.