Rails расширяет ActiveRecord :: Base

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


person xpepermint    schedule 24.02.2010    source источник
comment
Какие расширения? Нам действительно нужно что-то большее.   -  person jonnii    schedule 24.02.2010


Ответы (9)


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

Использование 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

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

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

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

person Harish Shetty    schedule 24.02.2010
comment
Хм ... во втором примере, когда я запускаю ./scripts/console, я получаю сообщение об ошибке `include ': TypeError: неправильный тип аргумента Class (ожидаемый модуль). - person xpepermint; 25.02.2010
comment
@xpepermint Похоже, вы начали с class MyActiveRecordExtensions вместо module MyActiveRecordExtensions. - person Jimmy; 25.02.2010
comment
Вы должны require файл в конце environment.rb. Я добавил этот дополнительный шаг к своему ответу. - person Harish Shetty; 25.02.2010
comment
Почему использование озабоченностей считается более идиоматичным? Я новичок в Rails, но, исходя из других языков / фреймворков, кажется, что простое наследование классов было бы правильным путем. - person Hartley Brody; 06.11.2013
comment
@HartleyBrody, это просто вопрос предпочтений. Если вы используете наследование, вы должны ввести новый ImprovedActiveRecord и наследовать от него, когда вы используете module, вы обновляете определение рассматриваемого класса. Раньше я использовал наследование (из-за многолетнего опыта работы с Java / C ++). Сейчас я в основном использую модули. - person Harish Shetty; 06.11.2013
comment
Немного иронично, что ваша ссылка на самом деле контекстуализирует и указывает на то, как люди неправильно и злоупотребляют цитатой. Но если серьезно, мне трудно понять, почему исправление обезьян не лучший способ в этом случае. Если вы хотите добавить к нескольким классам, очевидно, что модуль - это то, что вам нужно. Но если ваша цель - расширить один класс, разве не поэтому Ruby так упростил расширение классов? - person MCB; 25.11.2013
comment
@MCB, В каждом большом проекте есть несколько историй о трудно обнаруживаемой ошибке, появившейся из-за установки патчей обезьяны. Вот статья Авди о пороках установки исправлений: devblog.avdi.org/2008/02/23/. Ruby 2.0 представляет новую функцию под названием Refinements, которая решает большинство проблем с исправлением обезьян (yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice). Иногда особенность нужна только для того, чтобы заставить вас искушать судьбу. И иногда да. - person Harish Shetty; 25.11.2013
comment
@ Деннис, да, будет. - person Harish Shetty; 15.03.2014
comment
Обычно абстрактный класс используется при изменении соединения с базой данных из-за проблем с пулом соединений, если вы этого не сделаете. Было ли это решено, чтобы Концерн действительно стал предпочтительным методом? Или те, кто меняет соединение, в конечном итоге убьют себя по дороге, когда обнаружат проблему пула соединений? - person Altonymous; 26.03.2014
comment
Rails 4.1 имеет Module # about, который можно использовать вместо определения модуля inline, расширяя его с помощью ActiveSupport::Concern, а затем смешивая с классом. Может, здесь можно это использовать? - person Dennis; 10.04.2014
comment
Как использовать управление запросами в модуле? Я хочу написать метод list, который получает 10 строк из базы данных в модуле, тогда мне не нужно копировать его в каждый подкласс. - person worldask; 12.09.2014
comment
@worldask, я изменил пример, добавив метод top_ten. Вы можете связать этот метод с другими методами AR, например: Order.where('qty > ?', 10).top_ten - person Harish Shetty; 12.09.2014
comment
@HarishShetty Как насчет того, чтобы метод экземпляра уже определен? Я хочу исключить некоторые поля из модели, изменив метод serializable_hash, будет ли проблема работать в этом случае? - person fuyi; 05.03.2015
comment
@xiaopang, если вы хотите исключить некоторые поля из serializable_hash, вы должны использовать параметр exclude функции. Если вы пытаетесь настроить таргетинг только на один класс, возможно, лучше переопределить метод в этом классе. Вот пример: robots.oughttbot.com/better-serialization-less-as -json - person Harish Shetty; 05.03.2015
comment
@HarishShetty очень четко объяснил с примерами. Здесь я хотел бы добавить еще один способ через гем под названием github.com/AndyObtiva/super_module. - person Selvamani; 30.03.2015
comment
Вы также не должны использовать наследование, вы должны использовать модуль mix-in. - person Mike Bethany; 24.04.2015
comment
Я нашел эту статью полезной для дополнения этого ответа: fakingfantastic.com/2010/09/20/. - person Donato; 01.05.2015
comment
@HarishShetty Согласно предоставленному вами документу, это должно быть class_methods do вместо module ClassMethods, верно? - person Trantor Liu; 11.07.2016
comment
@TrantorLiu Да. Я обновил ответ, чтобы отразить последнюю версию документации (похоже, class_methods был представлен в 2014 году github.com/ рельсы / рельсы / коммит /) - person Harish Shetty; 12.07.2016
comment
Чтобы первый ответ сработал, мне пришлось добавить require 'active_support/concern' к lib/active_record_extension.rb файлу. См. документацию по ActiveSupport Concern. - person a.barbieri; 29.08.2017
comment
Пытался расширить ActiveRecord :: Result первым методом, но ничего не вышло. - person fgblomqvist; 26.09.2018
comment
@HarishShetty без ошибок, он просто пожаловался, что моего метода не существует (когда я пытался его использовать). Как в, вроде не загружалось расширение. Я использовал точный код, указанный выше, за исключением того, что я изменил ActiveRecord :: Base на ActiveRecord :: Result в последней строке. - person fgblomqvist; 27.09.2018
comment
Этому ответу 10 лет, все еще рекомендуется? - person fatfrog; 30.01.2021
comment
По крайней мере, в соответствии с официальным документом rails: api.rubyonrails.org/classes/ActiveSupport/Concern .html - person Harish Shetty; 02.02.2021

Вы можете просто расширить класс и просто использовать наследование.

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end
person Toby Hede    schedule 24.02.2010
comment
Мне нравится эта идея, потому что это стандартный способ сделать это, но ... Я получаю сообщение об ошибке Таблица «moboolo_development.abstract_models» не существует: ПОКАЗАТЬ ПОЛЯ ИЗ abstract_models. Куда мне его поставить? - person xpepermint; 25.02.2010
comment
Добавьте self.abstract_class = true в свой AbstractModel. Rails теперь распознает модель как абстрактную. - person Harish Shetty; 25.02.2010
comment
Ух ты! Не думал, что это возможно. Пробовал раньше и отказался, когда ActiveRecord захлебнулся при поиске AbstractModel в базе данных. Кто знал, что простой сеттер поможет мне СУХОСТЬ! (Я начала передергиваться ... было плохо). Спасибо Тоби и Харишу! - person dooleyo; 28.06.2013
comment
В моем случае это определенно лучший способ сделать это: здесь я не расширяю возможности своей модели с помощью внешних методов, а реорганизую общие методы для аналогичных объектов моего приложения. Наследование имеет здесь гораздо больше смысла. Не существует предпочтительного способа, кроме двух решений в зависимости от того, чего вы хотите достичь! - person Augustin Riedinger; 26.09.2014
comment
У меня это не работает в Rails4. Я создал abstract_model.rb и поместил в каталог моих моделей. внутри модели у него было self.abstract_class = true Затем я сделал из других моих моделей наследование ... User ‹AbstractModel. В консоли я получаю: Пользователь (вызовите User.connection, чтобы установить соединение) - person Joel Grannas; 21.01.2015
comment
Наследование снижает производительность, потому что вы получаете класс со многими предками, и это увеличивает время поиска метода. Лучший способ расширить ActiveRecord - использовать модули. - person Lev Lukomsky; 20.04.2016

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

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

[Edit] после комментария @daniel

Тогда все ваши модели будут иметь метод foo, включенный как метод экземпляра, и методы в ClassMethods, включенные как методы класса. Например. на FooBar < ActiveRecord::Base у вас будет: FooBar.bar и FooBar#foo

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

person nikola    schedule 08.02.2012
comment
Обратите внимание, что InstanceMethods устарел, начиная с Rails 3.2, просто поместите свои методы в тело модуля. - person Daniel Rikowski; 12.12.2012
comment
Я поместил ActiveRecord::Base.send(:include, MyExtension) в инициализатор, и это сработало для меня. Рельсы 4.1.9 - person 6ft Dan; 20.01.2015

В 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

Один момент, который я хотел бы выделить при использовании проблем, заключается в том, что проблемы следует использовать для группировки на основе домена, а не для «технической» группировки. Например, группировка доменов похожа на «Комментарии», «С тегами» 'и т. д. Группировка на основе технической информации будет похожа на' FinderMethods ',' ValidationMethods '.

Вот ссылка на сообщение, которое я нашел очень полезным для понимания проблем, связанных с моделями.

Надеюсь, что запись поможет :)

person Aaditi Jain    schedule 15.09.2014

Шаг 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 :)
person Vitaly Kushner    schedule 24.02.2010
comment
Думаю, шаг 2 нужно поместить в config / environment.rb. У меня не работает :(. Не могли бы вы написать еще помощь? Спасибо. - person xpepermint; 24.02.2010

Rails 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

См., Например, этот блог.

person Adobe    schedule 24.01.2017

Чтобы добавить к этой теме, я потратил некоторое время на разработку того, как тестировать такие расширения (я пошел по пути 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
person Will Tomlins    schedule 31.10.2012

В 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

Надеюсь, это поможет другим!

person Ashik Salman    schedule 02.03.2018

у меня есть

ActiveRecord::Base.extend Foo::Bar

в инициализаторе

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

module Foo
  module Bar
  end
end
person Ed Richards    schedule 01.03.2013