Снова откройте модель ActiveRecord, предоставленную гемом.

Я пытаюсь расширить модель ActiveRecord (Vote), который гем ( https://github.com/peteonrails/vote_fu) предоставляет моему приложению. (То есть нет vote.rb в app/models)

Мой первый подход состоял в том, чтобы создать файл с именем lib/extend_vote.rb который содержит код:

Vote.class_eval do
  after_create :create_activity_stream_event
  has_one :activity_stream_event

  def create_activity_stream_event
    # something..
  end
end

Это работает, когда создается первый голос, но когда я пытаюсь создать каждый последующий голос, я получаю ошибку TypeError (can't dup NilClass),

Я думаю, что эта ошибка вызвана тем, что Vote класс перезагружается автоматически после каждого запроса, но код в lib/extend_vote.rb загружается только один раз, когда сервер запускается, и это вызывает has_one :activity_stream_event ассоциация вести себя странно. (Кроме того, проблема исчезнет, ​​если я установлю config.cache_classes = true в development.rb)

Чтобы решить эту проблему, я попытался перезагрузить расширения голосов при каждом запросе, добавив to_prepare заблокировать мой development.rb:

config.to_prepare do
  load 'extend_vote.rb'
end

Это решает (can't dup NilClass) проблема, но теперь, когда я создаю новый голос, create_activity_stream_event обратный вызов вызывается в дополнительное время. То есть первый голос называет это один раз, второй - дважды, и т. Д., И т. Д. to_prepare Блок перезагружает расширение слишком агрессивно и добавляет дубликаты обратных вызовов.

Какой лучший способ добавить методы и обратные вызовы к этому Vote модель?

6 ответов

[ОБНОВЛЕНИЕ: должно быть правильным решением, чтобы модуль не включался несколько раз в один класс]

Я полагаю, что вы можете использовать ActiveSupport::Concern для предотвращения включения модуля несколько раз, что приводит к обратному вызову, вызываемому несколько раз. Смотрите пример ниже:

module VotePatch
  extend ActiveSupport::Concern

  included do
    after_create :create_activity_stream_event
    has_one :activity_stream_event
  end

  module InstanceMethods
    def create_activity_stream_event
      #your code here
    end  
  end

end

Vote.send(:include, VotePatch)

Предупреждение: это очень старый гем (последний коммит 3 года) и, судя по всему, не будет работать с rails 3.x как есть. В Rails 3.x движки упрощают подобные вещи.

Насколько я понимаю, проблема в первом случае не в том, что модель голосования перезагружается (не должно), а в том, что activity_stream_event модель перезагружена. Поскольку модель голосования не перезагружена, ассоциация остается висеть на версии activity_stream_event класс от до перезагрузки. Поскольку рельсы выводят классы до того, как они перезагружаются, это вызывает проблемы.

С этим в моем, попробуйте это взломать:

#in config/initializers/abstract_vote.rb
AbstractVote = Vote
AbstractVote.abstract_class = true
Object.send :remove_const, :Vote

#in app/models/vote.rb

class Vote < AbstractVote
  after_create :create_activity_stream_event
  has_one :activity_stream_event

  def create_activity_stream_event
  end
end

Это позволяет вам иметь собственный класс для голосования, который наследуется от класса в жемчужине.

Но, опять же, я призываю вас найти что-то более современное или свернуть свое собственное (драгоценный камень всего ~250 строк рубина)

Я бы попробовал то, что предложил agmcleod в комментариях, но вместо того, чтобы поместить его в lib, поместите его в config/initializers/voice.rb:

 class Vote
   after_create :create_activity_stream_event
   has_one :activity_stream_event

   def create_activity_stream_event
   # something..
   end
 end

Конечно, вы можете разветвлять гем, вносить изменения и ссылаться на свою разветвленную версию в своем Gemfile (это мои предпочтения).

Адриен Кокио имеет правильную идею с ActiveSupport::Concerns, которые являются способом расширения моделей Rails. Его код будет работать, и вы должны его использовать.

Однако это не будет работать все время в разработке, потому что, когда Rails перезагружает ваши классы при изменении файла, он не будет повторно оценивать #send линия. Единственное решение, которое я мог найти, было прикрепление к ActionDispatch обратный вызов в производстве, чтобы гарантировать, что файл повторно требуется после каждой загрузки страницы:

if Rails.env.development?
  ActionDispatch::Callbacks.to_prepare do
    require_dependency "../../lib/vote_fu_extensions"
  end
end

В производстве, или если вы установите cache_classes true в вашей конфигурации, вам не нужно это делать.

Не могли бы вы попробовать что-то вроде этого:

class Vote
    after_create :create_activity_stream_event
    has_one :activity_stream_event

    def create_activity_stream_event
        # something..
    end
end

Я думаю, что это добавит вашу функцию и вызовет функции "after_create" и "has_hone".

Ваша проблема может быть связана с тем, что вы исправляете класс. Когда rails пытается перезагрузить константы, он не учитывает ваш файл.

Попробуйте использовать методику модуля, как указано ниже.

Добавить файл с именем lib/vote_fu_extension.rb

module VoteFuExtension
  def self.included(base)
    base.has_one :activity_stream_event
    base.after_create :create_activity_stream_event
  end
  def create_activity_stream_event
    # something..
  end  
end
Vote.send(:include, VoteFuExtension)

Добавьте инициализатор с именем config/initializers/vote_fu.rb

require "vote_fu_extension"

Заметка

Если вы хотите добавить методы класса к Vote модель относится к этому ответу.

Бесстыдная вилка: моя вилка vote_fu Gem имеет некоторые новые функции и улучшения.

Другие вопросы по тегам