Рельсы, расширяющие ActiveRecord :: Base


160

Я немного читал о том, как расширить ActiveRecord: базовый класс, чтобы в моих моделях были специальные методы. Каков простой способ его расширения (пошаговое руководство)?


Какие расширения? Нам действительно нужно больше, чтобы продолжить.
Джонни

Ответы:


336

Есть несколько подходов:

Использование ActiveSupport :: Concern (предпочтительно)

Прочитайте документацию 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

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

Некоторые люди, сталкиваясь с проблемой, думают: «Я знаю, я использую исправления обезьян». Теперь у них две проблемы.

Исправление обезьян легко и быстро. Но сэкономленные время и усилия всегда извлекаются в будущем; с сложным интересом. В эти дни я ограничиваю исправление обезьян, чтобы быстро создать прототип решения в консоли rails.


3
Вы должны requireфайл в конце environment.rb. Я добавил этот дополнительный шаг в мой ответ.
Хариш Шетти

1
@HartleyBrody это просто вопрос предпочтений. Если вы используете наследование, вы должны ввести новое ImprovedActiveRecordи наследовать от него, когда вы используете module, вы обновляете определение рассматриваемого класса. Раньше я использовал наследование (причина многолетнего опыта работы с Java / C ++). В эти дни я в основном использую модули.
Хариш Шетти

1
Немного иронично, что ваша ссылка на самом деле контекстуализировала и указывала на то, как люди неправильно используют цитату. Но со всей серьезностью мне трудно понять, почему «исправление обезьян» не будет лучшим способом в этом случае. Если вы хотите добавить несколько классов, то, очевидно, модуль - это путь. Но если ваша цель состоит в том, чтобы расширить один класс, то не поэтому ли Ruby сделал расширение классов таким простым способом?
MCB

1
@MCB, В каждом большом проекте есть несколько историй о трудно обнаруживаемой ошибке, возникшей из-за исправлений обезьян. Вот статья Авди о пороках исправлений: devblog.avdi.org/2008/02/23/… . В Ruby 2.0 появилась новая функция под названием, Refinementsкоторая решает большинство проблем с исправлениями обезьян ( yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice ). Иногда есть особенность, чтобы заставить вас испытать судьбу. И иногда ты делаешь.
Хариш Шетти

1
@TrantorLiu Да. Я обновил ответ, чтобы отразить последнюю документацию (похоже, class_methods был представлен в 2014 г. github.com/rails/rails/commit/… )
Хариш Шетти,

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к своему AbstractModel. Rails теперь распознает модель как абстрактную модель.
Хариш Шетти

Вот Это Да! Не думал, что это возможно. Пробовал ранее и сдался, когда ActiveRecord задохнулся, ища AbstractModelв базе данных. Кто знал, что простой сеттер поможет мне высушить вещи! (Я начинал съеживаться ... это было плохо). Спасибо Тоби и Хариш!
dooleyo

В моем случае это, безусловно, лучший способ сделать это: я не расширяю свои возможности по модели с помощью сторонних методов, а перерабатываю общие методы для похожих объектов в моем приложении. Наследование имеет гораздо больше смысла здесь. Там нет предпочтительного пути, но 2 решения в зависимости от того, что вы хотите достичь!
Августин Ридингер

Это не работает для меня в Rails4. Я создал abstract_model.rb и вставил в мой каталог моделей. внутри модели он имел self.abstract_class = true. Затем я сделал наследование от других моих моделей ... User <AbstractModel . В консоли я получаю: Пользователь (вызов "User.connection", чтобы установить соединение)
Джоэл Граннас

21

Вы также можете использовать ActiveSupport::Concernядро Rails, например:

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
футов Дан

18

В Rails 4 концепция использования проблем для модульности и сушки ваших моделей была в центре внимания.

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

Рассмотрим модель Article, модель Event и модель Comment. Статья или событие имеет много комментариев. Комментарий относится либо к статье, либо к событию.

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

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

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.

Для этого создайте файл 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

Одна вещь, которую я хотел бы подчеркнуть при использовании проблем, заключается в том, что проблемы следует использовать для групповой, а не для технической группировки. Например, группировка доменов похожа на «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. Это не работает для меня :(. Можете ли вы написать еще немного помощи? Спасибо.
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.