ActionPubsub
In process, concurrent observers, loosely modeled after rabbitmq
Example
Lets say we have a blog app, and our posts have comments enabled on a case by case basis. When someone leaves a new comment, we want to blast out an email to everyone who has subscribed to recieve new posts
In our comments model:
module Blogger
class Comment < ::ActiveRecord::Base
include ::ActionPubsub::ActiveRecord::Publishable
publish_as "blogger/comment"
publishable_actions :created, :updated
end
end
Our subscriber:
module Blogger
class CommentSubscriber < ::ActionPubsub::ActiveRecord::Subscriber
#this is the "exchange" we want all of the events we are watching for, to be scoped to
#i.e. blogger/comment/created will end up being the fully qualified path for on :create
subscribe_to "blogger/comment"
self.concurrency = 5
on :created, :if => lambda{ |record| record.post.has_comments_enabled? } do
#on initialize right now subscriber instance will get a resource instance variable
#populated for free pertaining to the record in focus, i.e. a comment record
resource.post.commenters.by_new_comment_notifications_enabled.each do |commenter|
NewCommentNotificationMailer.deliver(resource, commenter)
end
end
end
end
What is the advantage of this pattern?
The advantage is it makes your app incredibly reactive. Rather than have to fatten up your controller with logic that does not belong, or have some service object that does 20 things in sequence, it allows everything to be decoupled, only subscribe to the things that are relevant. It also enforces the single responsibility principle, by allowing these subscribers to exist in potentially different engines, or areas of your application, and do nothing but react to the events occurring within the system.
Callbacks
Sure, we could use callbacks, but do we care about any of that if the record has not been commited to the database? (No we should not). Unless you use after_commit :on => :create, then your callbacks will attempt to run even if record hasn't been committed, unless at some point an error or false was returned.
So as a best practice, we only broadcast the creation after it's been commited.
This also allows for complex chains of events to occur, with no knowledge of each other, but that do their one job, and do it well. If that subscriber happens to create a new record, We can then subscribe to that models creation somewhere else, settings up the building blocks of a pipeline.
Installation
Add this line to your application's Gemfile:
gem 'action_pubsub'
And then execute:
$ bundle
Or install it yourself as:
$ gem install action_pubsub
Usage
TODO: Work in progress
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake rspec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/action_pubsub.
License
The gem is available as open source under the terms of the MIT License.