Рейки, що розширюються ActiveRecord :: Base


160

Я читав про те, як розширити ActiveRecord: Base class, щоб мої моделі мали деякі спеціальні методи. Який простий спосіб розширити його (покроковий посібник)?


Які розширення? Нам дійсно потрібно більше, щоб продовжувати.
jonnii

Відповіді:


336

Існує кілька підходів:

Використання ActiveSupport :: Концерн (кращий)

Прочитайте документацію щодо ActiveSupport :: Concern для отримання більш детальної інформації.

Створіть файл, який називається 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/initializersкаталозі під назвою 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

Знамениту цитату про регулярні вирази Джеймі Завінського можна переосмислити, щоб проілюструвати проблеми, пов’язані з виправленням мавп.

Деякі люди, стикаючись з проблемою, думають: «Я знаю, я буду використовувати патч-мавпи». Зараз у них дві проблеми.

Виправлення мавп легко і швидко. Але заощаджений час та зусилля завжди повертаються десь у майбутньому; зі складним відсотком. Ці дні я обмежую виправлення мавп для швидкого прототипу рішення в консолі рейок.


3
Ви повинні мати requireфайл у кінці environment.rb. Я додав цей додатковий крок до своєї відповіді.
Харіш Шетті

1
@HartleyBrody - це лише питання переваги. Якщо ви використовуєте спадщину, вам потрібно ввести нове ImprovedActiveRecordі успадкувати від цього, коли ви використовуєте module, ви оновлюєте визначення відповідного класу. Раніше я користувався спадщиною (причина років досвіду Java / C ++). У ці дні я в основному використовую модулі.
Harish Shetty

1
Трохи іронічно, що ваше посилання насправді контекстуалізувало і вказувало на те, як люди неправильно і зловживають цитатою. Але в усьому серйозності у мене виникають проблеми з розумінням того, чому «патч мавпи» не був би найкращим способом у цьому випадку. Якщо ви хочете додати до декількох класів, то модуль, очевидно, шлях. Але якщо ваша мета - розширити один клас, то хіба не так, чому Рубі зробив розширення класів таким чином таким простим?
MCB

1
@MCB, У кожному великому проекті є кілька історій про важко знайти помилку, введену через виправлення мавп. Ось стаття AVDI про пороках латок: devblog.avdi.org/2008/02/23 / ... . Ruby 2.0 представляє нову функцію під назвою, Refinementsяка вирішує більшість проблем з виправленням мавп ( yehudakatz.com/2010/11/30/ruby-2-0-refinances-in-practice ). Іноді особливість є просто для того, щоб примусити вас спокусити долю. А іноді й робиш.
Harish Shetty

1
@TrantorLiu Так. Я оновив відповідь, щоб відобразити останню документацію (схоже, що class_methods була представлена ​​у 2014 році github.com/rails/rails/commit/… )
Harish

70

Ви можете просто розширити клас і просто використовувати спадщину.

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end

Мені подобається ця ідея, тому що це стандартний спосіб її виконання, але ... я отримую таблицю помилок "moboolo_development.ab абстракт_models" не існує: ПОКАЖИТИ ПОЛІ ВІД abstract_models. Куди мені його поставити?
xpepermint

23
Додайте self.abstract_class = trueдо свого AbstractModel. Рейки тепер визнають модель абстрактною моделлю.
Harish Shetty

Оце Так! Не думав, що це можливо. Спробував це раніше і здався, коли ActiveRecord задихнувся від пошуку AbstractModelв базі даних. Хто знав, що простий сетер допоможе мені СУХІ речі! (Я починав плакати ... було погано). Дякую Тобі та Харішу!
dooleyo

У моєму випадку це, безумовно, найкращий спосіб зробити це: я не розширюю свої здібності до моделей за допомогою іноземних методів, але переробляю загальні методи для подібних поведінкових об'єктів мого додатка. Спадщина має тут набагато більше сенсу. Немає кращого способу, але два рішення залежно від того, чого ви хочете досягти!
Августин Рідінгер,

Це не працює для мене в Rails4. Я створив abstract_model.rb і помістив у свій каталог моделей. всередині моделі він мав self.abstract_class = істинно Тоді я зробив на моїх інших моделей успадковують ... User <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, просто введіть свої методи в корпус модуля.
Даніель Ріковський

Я помістив ActiveRecord::Base.send(:include, MyExtension)в ініціалізатор, і тоді це працювало для мене. Рейки 4.1.9
6 футів. Дан

18

Що стосується Rails 4, то концепція використання занепокоєнь для модуляції та висушування ваших моделей була висвітлена.

Побоювання в основному дозволяють згрупувати подібний код моделі або по декількох моделях в одному модулі, а потім використовувати цей модуль у моделях. Ось приклад:

Розглянемо модель статті, модель події та модель коментаря. До статті чи події є багато коментарів. Коментар належить до будь-якої статті чи події.

Традиційно моделі можуть виглядати так:

Модель коментаря:

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

Як ми можемо помітити, є значна частина коду, спільна як для події, так і для статті моделі. Використовуючи проблеми, ми можемо витягти цей загальний код в окремому модулі Commentable.

Для цього створіть файл 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

Один момент, який я хотів би підкреслити під час використання Concerns, полягає в тому, що стурбованість слід використовувати для групування на основі домена, а не для "технічного" групування. Наприклад, групування доменів схоже на "Commentable", "Taggable" тощо. Технічне групування буде на зразок "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. Це не працює для мене :(. Чи можете ви, будь ласка, написати ще додаткову допомогу? Thx.
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

Сподіваюся, це допоможе іншим!


0

У мене є

ActiveRecord::Base.extend Foo::Bar

в ініціалізаторі

Для модуля, як показано нижче

module Foo
  module Bar
  end
end
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.