Альтернативи спостерігача за рейками для 4.0


154

Оскільки спостерігачів офіційно видалено з Rails 4.0 , мені цікаво, що використовують інші розробники замість них. (За винятком використання видобутого дорогоцінного каменю.) Хоча спостерігачів, безумовно, зловживали і часом могли легко стати недоброзичливими, було багато випадків використання поза просто очищенням кешу, де вони принесли користь.

Візьмемо, наприклад, додаток, якому потрібно відстежувати зміни в моделі. Спостерігач міг легко спостерігати за змінами в моделі A і записувати ці зміни за допомогою моделі B в базі даних. Якщо ви хотіли спостерігати за змінами в декількох моделях, то з цим впорався б один спостерігач.

У Rails 4 мені цікаво, які стратегії використовують інші розробники замість спостерігачів, щоб відтворити цю функціональність.

Особисто я схиляюся до свого роду "жирового контролера", де ці зміни відслідковуються в методі створення / оновлення / видалення контролера кожної моделі. Хоча це трохи полегшує поведінку кожного контролера, це допомагає в читанні та розумінні, оскільки весь код знаходиться в одному місці. Мінусом є те, що зараз існує дуже схожий на кілька кодів код. Витяг цього коду в допоміжні методи - це варіант, але ви все ще залишаєтеся дзвінками до цих методів. Не кінець світу, але не зовсім в дусі "худих контролерів".

Відклики викликів ActiveRecord - це ще один можливий варіант, хоча один, який мені особисто не подобається, як це, як правило, поєднує дві різні моделі занадто тісно разом, на мій погляд.

Отож, у світі Rails 4, не спостерігачів, якщо вам довелося створити новий запис після створення / оновлення / знищення іншої записи, яку модель дизайну ви б використали? Контролери жиру, зворотні виклики ActiveRecord чи щось інше цілком?

Дякую.


4
Я дуже здивований, що на це запитання не розміщено більше відповідей. Вигляд розчарування.
Courtimas

Відповіді:


82

Погляньте на концерни

Створіть у папці своїх моделей папку під назвою проблеми. Додайте туди модуль:

module MyConcernModule
  extend ActiveSupport::Concern

  included do
    after_save :do_something
  end

  def do_something
     ...
  end
end

Далі, включіть це в моделі, в яких ви хочете запустити after_save в:

class MyModel < ActiveRecord::Base
  include MyConcernModule
end

Залежно від того, що ви робите, це може вас зблизити без спостерігачів.


20
Є проблеми з таким підходом. Зокрема, це не прибирає ваші моделі; включити копії методів з модуля назад у свій клас. Вилучення методів класу з модуля може групувати їх занепокоєнням, але клас все ще настільки ж роздутий.
Стівен Сорока

15
Заголовок: "Альтернативи спостерігачів Rails для 4.0", а не "Як мінімізувати набряк". Як це, що турботи не виконують роботу Стівена? І ні, припущення про те, що «порив» є причиною, чому це не спрацює замість спостерігачів, недостатньо добре. Вам доведеться запропонувати кращу пропозицію, щоб допомогти громаді або пояснити, чому проблеми не стануть заміною спостерігачів. Сподіваємось, ви
заявите

10
Порив - це завжди турбота. Кращою альтернативою є мудріший , який, якщо реалізувати належним чином, дозволяє усунути проблеми, витягнувши їх на окремі класи, не тісно пов'язані з моделями. Це також значно спрощує випробування в ізоляції
Стівен Сорока

4
Зробити модель bloat або Whole App, потягнувши дорогоцінний камінь, щоб зробити це - ми можемо це віддати індивідуально. Дякуємо за додаткову пропозицію.
UncleAdam

Це лише підірве автоматичне заповнення меню методу IDE, що має бути добре для багатьох людей.
lulalala

33

Вони зараз у плагіні .

Чи можу я також порекомендувати альтернативу, яка надасть вам контролери типу:

class PostsController < ApplicationController
  def create
    @post = Post.new(params[:post])

    @post.subscribe(PusherListener.new)
    @post.subscribe(ActivityListener.new)
    @post.subscribe(StatisticsListener.new)

    @post.on(:create_post_successful) { |post| redirect_to post }
    @post.on(:create_post_failed)     { |post| render :action => :new }

    @post.create
  end
end

Як щодо ActiveSupport :: Сповіщення?
свооп

@svoop ActiveSupport::Notificationsорієнтовані на інструментальні інструменти, а не загальні підрозділи / паби .
Кріс

@Kris - ти маєш рацію. Він використовується в першу чергу для інструментальних приладів, але мені цікаво, що заважає використовувати його як загальний метод для pub / sub? він забезпечує основні будівельні блоки, правда? Інакше кажучи, які порівняно з / у порівнянні зі швидкістю / недоліком ActiveSupport::Notifications?
пряник

Я мало використовував, Notificationsале я б сказав, що він Wisperмає приємніший API та такі функції, як "глобальні підписники", "за префіксом" та "відображення подій", які Notificationsцього не роблять. Майбутній випуск Wisperтакож дозволить асинхронні публікації через SideKiq / Resque / Celluloid. Крім того, потенційно, у майбутніх випусках Rails API Notificationsможе змінитися, щоб бути більш орієнтованим на інструментарій.
Кріс

21

Моя пропозиція - прочитати допис у блозі Джеймса Голіка на веб-сайті http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html (спробуйте проігнорувати, як нескромні звуки заголовка).

Ще в той день це було все "жирна модель, худий контролер". Тоді жирові моделі стали гігантським головним болем, особливо під час тестування. Зовсім недавно поштовх ставився до худих моделей - ідея полягала в тому, що кожен клас повинен керуватися однією відповідальністю, а завдання моделі - зберігати ваші дані в базі даних. То де ж закінчується вся моя складна бізнес-логіка? У класах ділової логіки - класи, що представляють транзакції.

Такий підхід може перетворитися на трясовину (гігігіти), коли логіка починає ускладнюватися. Ця концепція є здоровою - замість того, щоб неявно запускати речі зворотними дзвінками або спостерігачами, які важко перевірити та відладкувати, явно запускайте речі в класі, який шарує логіку поверх вашої моделі.


4
Я робив щось подібне для проекту протягом останніх кількох місяців. У вас є багато невеликих послуг, але простота тестування та обслуговування, безумовно, переважає недоліки. Мої досить обширні характеристики цієї середньої величини все ще потребують 5 секунд для запуску :)
Luca Spiller

Також відомий як PORO (Plain Old Ruby Objects) або службові об'єкти
Кирило Дюшон-Доріс

13

Використання активних зворотних дзвінків запису просто усуває залежність вашої зв'язку. Наприклад, якщо у вас є modelAі CacheObserverякі спостерігають modelAрейки 3 стилю, ви можете видалити CacheObserverбез будь - яких проблем. Тепер замість цього потрібно сказати, Aщо потрібно вручну викликати CacheObserverпісля збереження, яке було б рейками 4. Ви просто перемістили свою залежність, щоб ви могли безпечно видалити, Aале ні CacheObserver.

Тепер від башти зі слонової кістки я вважаю за краще, щоб спостерігач залежав від моделі, яку він спостерігає. Мені достатньо байдуже, щоб захаращувати контролери? Для мене відповідь - ні.

Імовірно, ви задумалися, чому ви хочете / потребуєте спостерігача, і, таким чином, створити модель, залежну від його спостерігача, не є страшною трагедією.

У мене також є (розумно обґрунтована, я думаю) неприємність будь-якого спостерігача, залежного від дії контролера. Раптом вам доведеться ввести свого спостерігача в будь-яку дію контролера (або іншу модель), яка може оновити модель, яку ви хочете спостерігати. Якщо ви можете гарантувати, що ваш додаток буде змінювати екземпляри лише через дії контролера створення / оновлення, більше потужності для вас, але це не припущення, яке я б робив щодо програми рейкових (враховуйте вкладені форми, моделювання асоціацій оновлення бізнес-логіки тощо).


1
Дякую за коментарі @agmin. Я радий відмовитися від використання спостерігача, якщо там є кращий дизайн. Мене найбільше цікавить, як інші люди структурують свій код та залежності, щоб забезпечити подібну функціональність (виключаючи кешування). У моєму випадку я хотів би записати зміни в модель будь-коли, коли її атрибути оновлюються. Для цього я використовував Спостерігача. Тепер я намагаюся вибрати між жировим контролером, зворотним зв'язком AR або чим-небудь іншим, про що я не думав. Наразі це не здається елегантним.
kennyc

13

Wisper - чудове рішення. Мої особисті переваги щодо зворотних викликів - це те, що вони знімаються моделями, але події прослуховуються лише тоді, коли надходить запит, тобто я не хочу, щоб зворотні виклики знімалися під час налаштування моделей у тестах тощо. звільняється, коли задіяні контролери. Це дуже просто налаштувати за допомогою Wisper, оскільки ви можете сказати йому лише слухати події всередині блоку.

class ApplicationController < ActionController::Base
  around_filter :register_event_listeners

  def register_event_listeners(&around_listener_block)
    Wisper.with_listeners(UserListener.new) do
      around_listener_block.call
    end
  end        
end

class User
  include Wisper::Publisher
  after_create{ |user| publish(:user_registered, user) }
end

class UserListener
  def user_registered(user)
    Analytics.track("user:registered", user.analytics)
  end
end


4

Моя альтернатива спостерігачам Rails 3 - це вручну реалізація, яка використовує зворотний виклик, визначений у моделі, але встигає (як заявляє адміністратор у своїй відповіді вище) "перевернути залежність ... з'єднання".

Мої об'єкти успадковують від базового класу, який передбачає реєстрацію спостерігачів:

class Party411BaseModel

  self.abstract_class = true
  class_attribute :observers

  def self.add_observer(observer)
    observers << observer
    logger.debug("Observer #{observer.name} added to #{self.name}")
  end

  def notify_observers(obj, event_name, *args)
    observers && observers.each do |observer|
    if observer.respond_to?(event_name)
        begin
          observer.public_send(event_name, obj, *args)
        rescue Exception => e
          logger.error("Error notifying observer #{observer.name}")
          logger.error e.message
          logger.error e.backtrace.join("\n")
        end
    end
  end

end

(Зрозуміло, у дусі композиції над успадкуванням, зазначений вище код може бути розміщений у модулі та змішаний у кожній моделі.)

Ініціалізатор реєструє спостерігачів:

User.add_observer(NotificationSender)
User.add_observer(ProfilePictureCreator)

Кожна модель може потім визначити власні спостережувані події, що перевищують основні зворотні виклики ActiveRecord. Наприклад, моя модель користувача виставляє 2 події:

class User < Party411BaseModel

  self.observers ||= []

  after_commit :notify_observers, :on => :create

  def signed_up_via_lunchwalla
    self.account_source == ACCOUNT_SOURCES['LunchWalla']
  end

  def notify_observers
    notify_observers(self, :new_user_created)
    notify_observers(self, :new_lunchwalla_user_created) if self.signed_up_via_lunchwalla
  end
end

Будь-який спостерігач, який бажає отримувати сповіщення про ці події, просто повинен (1) зареєструватися у моделі, що викриває подію, та (2) створити метод, ім'я якого відповідає події. Як можна було очікувати, кілька спостерігачів можуть зареєструватися на одну і ту ж подію, і (посилаючись на другий абзац оригінального питання) спостерігач може спостерігати за подіями в декількох моделях.

Нижче класи класів спостережень NotificationSender та ProfilePictureCreator визначають методи подій, що піддаються різним моделям:

NotificationSender
  def new_user_created(user_id)
    ...
  end

  def new_invitation_created(invitation_id)
    ...
  end

  def new_event_created(event_id)
    ...
  end
end

class ProfilePictureCreator
  def new_lunchwalla_user_created(user_id)
    ...
  end

  def new_twitter_user_created(user_id)
    ...
  end
end

Одним із застережень є те, що назви всіх подій, які виставляються на всіх моделях, повинні бути унікальними.


3

Я думаю, що проблема спостереження спостерігачів не в тому, щоб спостерігачі були погані самі по собі, а в тому, що їх зловживають.

Я б застеріг від додавання занадто багато логіки у зворотній зв'язок або просто переміщення коду, щоб імітувати поведінку спостерігача, коли вже є надійне рішення цієї проблеми, модель спостерігача.

Якщо є сенс використовувати спостерігачів, то будь-якими способами користуйтеся спостерігачами. Просто зрозумійте, що вам потрібно переконатися, що ваша логіка спостерігача дотримується методів кодування звуку, наприклад, SOLID.

Дорогоцінний камінь для спостерігачів доступний на рубігемах, якщо ви хочете додати його до свого проекту https://github.com/rails/rails-observers

дивіться цю коротку тему, хоча не повне всебічне обговорення, я думаю, що основний аргумент справедливий. https://github.com/rails/rails-observers/isissue/2



2

Як щодо використання PORO?

Логіка цього полягає в тому, що ваші «зайві дії щодо економії», ймовірно, будуть бізнес-логікою. Це я люблю тримати окремо від моделей AR (які повинні бути максимально простими) та контролерів (які турбують належним чином для тестування)

class LoggedUpdater

  def self.save!(record)
    record.save!
    #log the change here
  end

end

І просто назвіть це як таке:

LoggedUpdater.save!(user)

Ви навіть можете розширити його, ввівши додаткові об'єкти дій після збереження

LoggedUpdater.save(user, [EmailLogger.new, MongoLogger.new])

І, щоб навести приклад "статей". Можливо, ви хочете трохи підняти їх:

class EmailLogger
  def call(msg)
    #send email with msg
  end
end

Якщо вам подобається такий підхід, рекомендую прочитати публікацію в блозі Брайана Гельмкампса 7 Patterns .

EDIT: Я також повинен зазначити, що вищевказане рішення дозволяє додавати логіку транзакцій також, коли це необхідно. Наприклад, з ActiveRecord та підтримуваною базою даних:

class LoggedUpdater

  def self.save!([records])
    ActiveRecord::Base.transaction do
      records.each(&:save!)
      #log the changes here
    end
  end

end


-2

У мене такий же probjem! Я знаходжу рішення ActiveModel :: Брудне, щоб ви могли відстежувати зміни вашої моделі!

include ActiveModel::Dirty
before_save :notify_categories if :data_changed? 


def notify_categories
  self.categories.map!{|c| c.update_results(self.data)}
end

http://api.rubyonrails.org/classes/ActiveModel/Dirty.html

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.