Skip to content

Instantly share code, notes, and snippets.

@kpumuk
Created June 15, 2011 22:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kpumuk/1028273 to your computer and use it in GitHub Desktop.
Save kpumuk/1028273 to your computer and use it in GitHub Desktop.
Contains the FiresEvent module and extensions to ActiveRecord::Base for adding event-related after_save hooks to models.
# Contains the FiresEvent module and extensions to ActiveRecord::Base for
# adding event-related after_save hooks to models.
# Adds methods to ActiveRecord::Base for working with events.
class ActiveRecord::Base
class_attribute :skip_events, :instance_writer => false
class << self
# Runs a block of code without firing any events.
def without_events(&block)
self.skip_events = true
ret = Thread.exclusive(&block)
self.skip_events = false
ret
end
end
# Returns true if events are being suppressed.
def skip_events?
@skip_events || self.class.skip_events
end
# Stops firing events until resume_events! is called.
def skip_events!
@skip_events = true
end
# Resumes firing events after a call to skip_events!
def resume_events!
@skip_events = nil
end
end
# Contains the fires_event method.
module FiresEvent
# Gives an ActiveRecord::Base subclass the ability to create event objects (or
# really, any object) via any of the ActiveRecord::Base callbacks. You provide
# a class (or class name string) and optionally a callback (by default
# +after_create+). A new instance of the given class, along with the record
# itself, is yielded to the given block as part of the callback.
#
# In the block, you can configure the object as you see fit. It is saved after
# the block finishes. Example:
#
# class Post < ActiveRecord::Base
# fires_event Event::PostCreated do |event, post|
# event.post_id = post.id
# end
# end
#
# This will create an +after_create+ hook that initializes an
# <tt>Event::PostCreated</tt> object, configures it according to the block,
# and then saves it. Any validation errors will be raised as exceptions.
#
# If you want to abort the process and suppress the event, you can call
# <tt>cancel_event!</tt> in the block:
#
# fires_event Event::PostCreated do |event, post|
# cancel_event! if post.private?
# event.post_id = post.id
# end
def fires_event(event_class, callback=:after_create, &block)
send(callback) do |object|
next if object.skip_events?
event = (event_class.kind_of?(String) ? event_class.constantize : event_class).new
begin
block.call(event, object)
rescue CancelException
else
event.save!
@after_firing_event.each { |hook| hook.call(event, object) } if @after_firing_event
end
end
end
# Aborts the process of creating an event. Should only be called within a
# fires_event block.
def cancel_event!
raise CancelException
end
# Saves a block that will be executed after the event is saved and has a
# database ID. The block will be given the saved event and the host object:
#
# fires_event Event::PostCreated do |event, post|
# event.post_id = post.id
# after_firing_event { |saved_event, post| post.update_attribute_with_validation :event_id, saved_event.id }
# end
#
# The block
def after_firing_event(&block)
@after_firing_event ||= []
@after_firing_event << block
end
# :nodoc:
class CancelException < StandardError; end
end
ActiveRecord::Base.extend FiresEvent
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment