Created
August 15, 2012 12:31
-
-
Save tatey/3359753 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # I've noticed we want to do two different things when something about a model | |
| # changes. Typically you would use ActiveModel::Observers, but the callbacks you | |
| # get only have limited context forcing us to save more state to get it back later. | |
| # | |
| # The idea of this is that we can craft our own callbacks using a pub/sub pattern | |
| # and pass in as much transient context as we want. We also want to bring in our | |
| # analytics (or kiss metrics) on some of these callbacks. | |
| # | |
| # We can make our tests faster because we only care that our controller published | |
| # the notification. Other tests will catch who is subscribed and what they should | |
| # do when they subscribe. | |
| # | |
| # We can also trigger notifications from the CLI, or opt-in and out of them at any time. | |
| # | |
| # Scroll to the comments to see a visual graph. | |
| # Notification queue and registry. There is a default queue that lives in the | |
| # main thread. Just like ActiveSupport::Notifications, it blocks. | |
| module TBD | |
| module Notifications | |
| include Forwardable | |
| def_delegators :notifier, :publish, :subscribe | |
| class Notifier | |
| attr_accessor :subscribers | |
| def initialize | |
| @subscribers = SubscriberMap.new | |
| end | |
| def publish name, *args | |
| subscribers[name].each do |subscriber | |
| subscribe.call *args | |
| end | |
| end | |
| def subscribe name, subscriber | |
| subscribers[name] << subscriber | |
| end | |
| end | |
| class SubscriberMap | |
| attr_accessor :map | |
| def initialize | |
| @map = {} | |
| end | |
| def [] name | |
| map[name] = [] unless map.key? name | |
| map[name] | |
| end | |
| end | |
| def self.notifier | |
| @notifier ||= Notifier.new | |
| end | |
| end | |
| end | |
| # Default notifier is injected into the base controller. We can swap this out | |
| # in testing. | |
| class ApplicationController | |
| attr_writer :notifier | |
| protected | |
| def notifier | |
| @notifier ||= TBD::Notifications.notifier | |
| end | |
| end | |
| # An initializer sets up the subscriptions. | |
| TBD::Notifications.subscribe 'plan.finalize', Subscriber::FeedItem.new | |
| TBD::Notifications.subscribe 'plan.finalize', Subscriber::Member.new | |
| # Trigger the notification from a controller. | |
| class PlansController < ApplicationController | |
| def finalize | |
| notifier.publish 'plan.finalized', plan: {id: @plan.id} | |
| end | |
| end | |
| # Subscribers are a proxy that pass the notification onto the correct | |
| # object. | |
| class Subscriber::FeedItem | |
| def call name, payload | |
| public_send name.gsub('.', '_'), payload # plan.finalized -> plan_finalized | |
| end | |
| def plan_finalized payload | |
| plan = Plan.find payload[:plan][:id] | |
| FeedItem.create notification_type: 'plan_finalized', target: plan | |
| end | |
| end | |
| class Subscriber::Member | |
| def call name, payload | |
| public_send name.gsub('.', '_'), payload | |
| end | |
| def plan_finalized payload | |
| plan = Plan.find payload[:plan][:id] | |
| Service::Member::PlanFinalized.new(plan).deliver | |
| end | |
| end | |
| # Service takes the plan and sends it to the members who have opt-in | |
| # to receive these types of notifications. | |
| class Service::Member::PlanFinalized | |
| attr_accessor :plan | |
| def initialize plan | |
| @plan = plan | |
| end | |
| def deliver | |
| plan.members.each do |member| | |
| next unless member.preferences.email.plan_finalized? | |
| MemberMailer.plan_finalized.deliver | |
| end | |
| end | |
| end | |
| class FeedItem < ActiveRecord::Base | |
| end | |
| class MemberMailer < ActionMailer::Base | |
| end | |
| # Notes | |
| # | |
| # * Notifications do not support patterns. Eg: plan.* versus plan.create | |
| # * Passing an id in the payload and loading the object again could be unnecessary overhead. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Totally overkill when compared to? | |
| class PostsController | |
| def finalize | |
| # ... | |
| Service::MemberMailer::PlanFinalize.new(@plan).deliver | |
| Service::FeedItem.new(@plan).finalize_plan | |
| # ... | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

