Rails ขยาย ActiveRecord :: ฐาน


160

ฉันได้อ่านเกี่ยวกับวิธีการขยาย ActiveRecord: คลาสพื้นฐานดังนั้นแบบจำลองของฉันจะมีวิธีพิเศษบางอย่าง วิธีง่าย ๆ ในการขยาย (การสอนทีละขั้นตอน) คืออะไร?


ส่วนขยายชนิดใด เราต้องการมากขึ้นเพื่อดำเนินการต่อไป
jonnii

คำตอบ:


336

มีหลายวิธี:

การใช้ ActiveSupport :: ข้อกังวล (ที่ต้องการ)

อ่านActiveSupport ::เอกสารที่เกี่ยวข้องสำหรับรายละเอียดเพิ่มเติม

สร้างไฟล์ที่เรียกว่าactive_record_extension.rbในlibไดเรกทอรี

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)

สร้างไฟล์ในconfig/initializersไดเรกทอรีชื่อextensions.rbและเพิ่มบรรทัดต่อไปนี้ในไฟล์:

require "active_record_extension"

มรดก (ที่ต้องการ)

อ้างถึงโทบี้คำตอบ

การปะของลิง (ควรหลีกเลี่ยง)

สร้างไฟล์ในส่วนไดเรกทอรีที่เรียกว่าconfig/initializersactive_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

คำพูดที่มีชื่อเสียงเกี่ยวกับการแสดงออกปกติโดยJamie Zawinskiสามารถนำมาใช้อีกครั้งเพื่อแสดงปัญหาที่เกี่ยวข้องกับการปะแก้ลิง

บางคนเมื่อเผชิญกับปัญหาคิดว่า“ ฉันรู้ว่าฉันจะใช้การปะลิง” ตอนนี้พวกเขามีสองปัญหา

การปะของลิงเป็นเรื่องง่ายและรวดเร็ว แต่เวลาและความพยายามที่บันทึกไว้จะถูกดึงกลับมาบางครั้งในอนาคต; ด้วยดอกเบี้ยทบต้น ทุกวันนี้ฉันได้ จำกัด การปะแก้ลิงเพื่อให้ต้นแบบโซลูชันได้อย่างรวดเร็วในคอนโซลทางรถไฟ


3
คุณต้องไฟล์ในตอนท้ายของrequire environment.rbฉันได้เพิ่มขั้นตอนพิเศษนี้ในคำตอบของฉัน
Harish Shetty

1
@HartleyBrody มันเป็นเพียงเรื่องของการตั้งค่า หากคุณใช้การสืบทอดคุณต้องแนะนำสิ่งใหม่ImprovedActiveRecordและการสืบทอดจากสิ่งนั้นเมื่อคุณใช้moduleคุณกำลังอัพเดตนิยามของคลาสที่เป็นปัญหา ฉันเคยใช้การสืบทอด (สาเหตุของประสบการณ์ Java / C ++) วันนี้ฉันส่วนใหญ่ใช้โมดูล
Harish Shetty

1
มันเป็นเรื่องน่าขันเล็กน้อยที่ลิงก์ของคุณเป็นบริบทจริงและชี้ให้เห็นว่าผู้คนเข้าใจผิดและอ้างข้อความมากเกินไป แต่ในความร้ายแรงทั้งหมดฉันมีปัญหาในการทำความเข้าใจว่าทำไม "การปะของลิง" จะไม่เป็นวิธีที่ดีที่สุดในกรณีนี้ ถ้าคุณต้องการที่จะเพิ่มลงในหลายคลาสดังนั้นโมดูลจะเป็นหนทางที่ชัดเจนในการไป แต่ถ้าเป้าหมายของคุณคือการขยายชั้นเรียนหนึ่งนั่นไม่ใช่เหตุผลว่าทำไม Ruby จึงทำการขยายชั้นเรียนด้วยวิธีนี้ง่ายมาก?
MCB

1
@MCB ทุกโครงการใหญ่มีเรื่องราวไม่กี่ข้อเกี่ยวกับการหาข้อผิดพลาดที่แนะนำเนื่องจากการปะแก้ลิง นี่คือบทความโดย Avdi เกี่ยวกับความชั่วร้ายของการปะแก้: devblog.avdi.org/2008/02/23/ … Ruby 2.0 แนะนำคุณสมบัติใหม่ที่เรียกว่าRefinementsซึ่งแก้ไขปัญหาส่วนใหญ่ด้วยการแก้ไขลิง ( yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice ) บางครั้งคุณสมบัติก็มีไว้เพื่อบังคับให้คุณล่อลวงชะตากรรม และบางครั้งคุณก็ทำ
Harish Shetty

1
@TrantorLiu ใช่ ฉันได้อัปเดตคำตอบเพื่อให้สอดคล้องกับเอกสารล่าสุดแล้ว (ดูเหมือนว่า class_methods เปิดตัวในปี 2014 github.com/rails/rails/commit/… )
Harish Shetty

70

คุณสามารถขยายชั้นเรียนและใช้มรดก

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end

ผมชอบความคิดนี้เพราะเป็นวิธีมาตรฐานในการทำมัน แต่ ... ฉันจะได้รับตารางข้อผิดพลาด 'moboolo_development.abstract_models' ไม่มีอยู่: abstract_modelsแสดงเขตข้อมูลจาก ฉันควรใส่ไว้ที่ไหน?
xpepermint

23
เพิ่มที่คุณself.abstract_class = true AbstractModelRails จะรับรู้แบบจำลองเป็นแบบนามธรรม
Harish Shetty

ว้าว! ไม่คิดว่ามันเป็นไปได้ พยายามก่อนหน้านี้และเลิกเมื่อ ActiveRecord สำลักค้นหาAbstractModelในฐานข้อมูล ใครจะรู้ setter ง่าย ๆ จะช่วยให้ฉันแห้งขึ้น! (ฉันเริ่มที่จะประจบประแจง ... มันไม่ดี) ขอบคุณ Toby และ Harish!
dooleyo

ในกรณีของฉันมันเป็นวิธีที่ดีที่สุดในการทำสิ่งนี้: ฉันไม่ได้ขยายความสามารถของโมเดลของฉันด้วยวิธีการต่างประเทศที่นี่ การรับมรดกทำให้มันสมเหตุสมผลมากขึ้นที่นี่ ไม่มีวิธีที่ต้องการแต่มี 2 โซลูชั่นขึ้นอยู่กับสิ่งที่คุณต้องการบรรลุ!
Augustin Riedinger

สิ่งนี้ไม่ได้ผลสำหรับฉันใน Rails4 ฉันสร้างabstract_model.rbและใส่ในไดเรกทอรีรุ่นของฉัน ภายในรูปแบบมันมีself.abstract_class = true แล้วผมทำในรูปแบบอื่น ๆ ของฉันได้รับมรดก ... ผู้ใช้ <AbstractModel ในคอนโซลฉันได้รับ: ผู้ใช้ (โทร 'User.connection' เพื่อสร้างการเชื่อมต่อ)
Joel Grannas

21

คุณยังสามารถใช้ActiveSupport::Concernและเป็น Rails Core สำนวนที่ชอบมากขึ้น:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

[แก้ไข] ติดตามความคิดเห็นจาก @daniel

จากนั้นแบบจำลองทั้งหมดของคุณจะมีวิธีการfooรวมเป็นวิธีการอินสแตนซ์และวิธีการ ClassMethodsรวมเป็นวิธีการเรียน เช่นFooBar < ActiveRecord::Baseคุณจะมี: FooBar.barและFooBar#foo

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


5
โปรดทราบว่าInstanceMethodsเลิกใช้แล้วตั้งแต่ Rails 3.2 เพียงแค่ใส่วิธีการของคุณลงในส่วนของโมดูล
Daniel Rikowski

ฉันวางไว้ActiveRecord::Base.send(:include, MyExtension)ในเครื่องมือเริ่มต้นและจากนั้นก็ใช้งานได้สำหรับฉัน Rails 4.1.9
6 ฟุตจาก Dan

18

ด้วย Rails 4 แนวคิดของการใช้ข้อกังวลในการทำให้เป็นโมดูลและ DRY ในแบบจำลองของคุณเป็นไฮไลท์

ข้อกังวลช่วยให้คุณสามารถจัดกลุ่มโค้ดที่คล้ายกันของโมเดลหรือข้ามหลายโมเดลในโมดูลเดียวจากนั้นใช้โมดูลนี้ในโมเดล นี่คือตัวอย่าง:

พิจารณาโมเดลบทความโมเดลเหตุการณ์และโมเดลข้อคิดเห็น บทความหรือเหตุการณ์มีความคิดเห็นมากมาย ความคิดเห็นเป็นของบทความหรือเหตุการณ์

ตามเนื้อผ้าโมเดลอาจมีลักษณะเช่นนี้:

รูปแบบความคิดเห็น:

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

รูปแบบบทความ:

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

รูปแบบกิจกรรม

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

ดังที่เราสามารถสังเกตเห็นได้ว่ามีรหัสที่สำคัญอย่างหนึ่งที่พบได้ทั่วไปสำหรับทั้ง Event และ Article Model ด้วยความกังวลเราสามารถแยกรหัสทั่วไปนี้ในโมดูลที่แยกต่างหากแสดงความคิดเห็นได้

สำหรับสิ่งนี้ให้สร้างไฟล์ commentable.rb ในแอพ / รุ่น / ข้อกังวล

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

และตอนนี้แบบจำลองของคุณมีลักษณะเช่นนี้:

รูปแบบความคิดเห็น:

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

รูปแบบบทความ:

class Article < ActiveRecord::Base
    include Commentable
end

รูปแบบกิจกรรม

class Event < ActiveRecord::Base    
    include Commentable
end

ประเด็นหนึ่งที่ฉันอยากจะเน้นในขณะที่ใช้ความกังวลก็คือควรใช้ความกังวลสำหรับการจัดกลุ่ม 'โดเมนที่ใช้' แทนที่จะใช้การจัดกลุ่มแบบ 'เทคนิค' ตัวอย่างเช่นการจัดกลุ่มโดเมนมีลักษณะเช่น 'แสดงความคิดเห็น', 'แท็กเกเบิล' เป็นต้นการจัดกลุ่มตามเทคนิคจะเหมือนกับ 'FinderMethods', 'ValidationMethods'

นี่คือลิงค์ไปยังโพสต์ที่ฉันพบว่ามีประโยชน์มากสำหรับการทำความเข้าใจข้อกังวลในแบบจำลอง

หวังว่าการเขียนช่วยได้ :)


7

ขั้นตอนที่ 1

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

ขั้นตอนที่ 2

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

ขั้นตอนที่ 3

There is no step 3 :)

1
ฉันเดาว่าต้องวางขั้นตอนที่ 2 ไว้ใน config / environment.rb มันใช้งานไม่ได้สำหรับฉัน :( กรุณาช่วยเขียนเพิ่มอีกหน่อยได้ไหมขอบคุณ
xpepermint

5

ราง 5 ActiveRecord::Baseให้ในตัวกลไกสำหรับการขยาย

นี่คือความสำเร็จโดยการให้ชั้นเพิ่มเติม:

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

และทุกรุ่นสืบทอดจากที่หนึ่ง:

class Post < ApplicationRecord
end

ดูเช่นบล็อกนี้


4

เพียงเพิ่มในหัวข้อนี้ฉันใช้เวลาสักครู่เพื่อหาวิธีทดสอบส่วนขยายดังกล่าว (ฉันลงไปตามActiveSupport::Concernเส้นทาง)

นี่คือวิธีการตั้งค่าแบบจำลองสำหรับทดสอบส่วนขยายของฉัน

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

ด้วย Rails 5 ทุกรุ่นได้รับการสืบทอดจาก ApplicationRecord & เป็นวิธีที่ดีในการรวมหรือขยายไลบรารีส่วนขยายอื่น ๆ

# 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

สมมติว่าโมดูลเมธอดพิเศษต้องพร้อมใช้งานในทุกรุ่นรวมไว้ในไฟล์ application_record.rb หากเราต้องการนำสิ่งนี้ไปใช้กับชุดของโมเดลที่เฉพาะเจาะจงให้รวมไว้ในคลาสโมเดลที่เกี่ยวข้อง

# 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

หากคุณต้องการมีวิธีการที่กำหนดไว้ในโมดูลเป็นวิธีการเรียนขยายโมดูลเพื่อ ApplicationRecord

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

หวังว่ามันจะช่วยให้ผู้อื่น!


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.