Снова откройте модель 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 в вашей конфигурации, вам не нужно это делать.
- Источник
- RoR ведет о конфигурации
- Это говорит о том, что он устарел, и вы должны использовать
ActionDispatch::Reloader
обратные вызовы вместо этого? Еще не пробовал.
Не могли бы вы попробовать что-то вроде этого:
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 имеет некоторые новые функции и улучшения.