Какой самый чистый способ переопределить находку ActiveRecord для моделей и коллекций? - PullRequest
3 голосов
/ 24 ноября 2008

У меня есть библиотечный код, который переопределяет метод поиска Ар. Я также включил модуль для всех классов Ассоциации, чтобы MyModel.find и @ parent.my_models.find работали и применяли правильную область действия.

Я основал свой код на will_paginate's:

a = ActiveRecord::Associations
returning([ a::AssociationCollection ]) { |classes|
  # detect http://dev.rubyonrails.org/changeset/9230
  unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation
    classes << a::HasManyThroughAssociation
  end
}.each do |klass|
  klass.send :include, Finder::ClassMethods
  klass.class_eval { alias_method_chain :method_missing, :paginate }
end

Моя проблема в том, что я хочу переопределить искатели только для некоторых моделей. В настоящее время мне нужно расширить все классы коллекций ассоциаций, которые являются общими для всех моделей. Я знаю, что могу расширить ассоциации для каждой модели, передав модуль:

has_many :things, :extend => SomeCustomMethods

Но моя библиотека в основном представляет собой плагин ActiveRecord, поэтому я хотел бы иметь чистое соглашение для подключаемых расширений искателя, которые применяются как к модели, так и к областям областей, не затрагивая все модели в приложении.

Ответы [ 3 ]

8 голосов
/ 25 ноября 2008

Вы хотите переопределить find_every, который является методом AR, который в конечном итоге запустит find_by_sql с соответствующим запросом. Переопределение find не будет работать для настраиваемых искателей, оно просто более хрупкое.

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

module MyPlugin
  def self.included(base)
    class << base
      alias_method :find_every_without_my_plugin, :find_every
      def find_every(*args)
        # do whatever you need ...
        find_every_without_my_plugin(*args)
      end
    end
  end
end

ActiveRecord::Base.send :include, MyPlugin

Это включит ваш плагин для всех классов. Как вы хотите контролировать, какие модели включены? Может быть, стандартный плагин accessor?

class User < ActiveRecord::Base
  my_plugin
end

Для поддержки этого вам нужно переместить class << base в метод класса (таким образом, base должно быть self). Как:

module MyPlugin
  def self.included(base)
    class << base
      base.extend ClassMethods
    end
  end

  module ClassMethods
    def my_plugin
      class << self
        alias_method :find_every_without_my_plugin, :find_every
        # ...
      end
    end
  end
end
5 голосов
/ 24 ноября 2008

Прежде всего, убедитесь, что вы хорошо знаете структуру наследования вызовов методов Ruby , так как без этого вы можете остаться в темноте.

Самый простой способ сделать это внутри класса ActiveRecord:

def self.find(*args)
  super
end

Это относится и к ассоциациям, поскольку они сами используют базовый искатель. Теперь вам просто нужно сделать ваши настройки. Сложность этого может варьироваться в широких пределах, и я не знаю, что вы делаете, поэтому я не могу предложить какие-либо рекомендации.

Также это динамическое определение будет само по себе упражнением, но оно должно направить вас в правильном направлении.

0 голосов
/ 15 января 2010

'Педро отвечает правильно, но есть небольшая ошибка.

def self.included(base)
  class << base
    base.extend ClassMethods
  end
end

должно быть

def self.included(base)
  base.extend ClassMethods
end

Использование класса << base ... end приводит к вызову 'extends' для 'base' в области видимости метода класса, но в ActiveRecord :: Base нет метода 'base', поэтому возникает ошибка. Использование base.extend само по себе вызовет метод extended для ActiveRecord :: Base. </p>

...