module Neo4j module Rails # Observer classes respond to life cycle callbacks to implement trigger-like # behavior outside the original class. This is a great way to reduce the # clutter that normally comes when the model class is burdened with # functionality that doesn't pertain to the core responsibility of the # class. Neo4j's observers work similar to ActiveRecord's. Example: # # class CommentObserver < Neo4j::Rails::Observer # def after_save(comment) # Notifications.comment( # "admin@do.com", "New comment was posted", comment # ).deliver # end # end # # This Observer sends an email when a Comment#save is finished. # # class ContactObserver < Neo4j::Rails::Observer # def after_create(contact) # contact.logger.info('New contact added!') # end # # def after_destroy(contact) # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!") # end # end # # This Observer uses logger to log when specific callbacks are triggered. # # == Observing a class that can't be inferred # # Observers will by default be mapped to the class with which they share a # name. So CommentObserver will be tied to observing Comment, # ProductManagerObserver to ProductManager, and so on. If you want to # name your observer differently than the class you're interested in # observing, you can use the Observer.observe class method which takes # either the concrete class (Product) or a symbol for that class (:product): # # class AuditObserver < Neo4j::Rails::Observer # observe :account # # def after_update(account) # AuditTrail.new(account, "UPDATED") # end # end # # If the audit observer needs to watch more than one kind of object, # this can be specified with multiple arguments: # # class AuditObserver < Neo4j::Rails::Observer # observe :account, :balance # # def after_update(record) # AuditTrail.new(record, "UPDATED") # end # end # # The AuditObserver will now act on both updates to Account and Balance # by treating them both as records. # # == Available callback methods # # * before_validation # * after_validation # * before_create # * around_create # * after_create # * before_update # * around_update # * after_update # * before_save # * around_save # * after_save # * before_destroy # * around_destroy # * after_destroy # # == Storing Observers in Rails # # If you're using Neo4j within Rails, observer classes are usually stored # in +app/models+ with the naming convention of +app/models/audit_observer.rb+. # # == Configuration # # In order to activate an observer, list it in the +config.neo4j.observers+ # configuration setting in your +config/application.rb+ file. # # config.neo4j.observers = [:comment_observer, :signup_observer] # # Observers will not be invoked unless you define them in your # application configuration. # # During testing you may want (and probably should) to disable all the observers. # Most of the time you don't want any kind of emails to be sent when creating objects. # This should improve the speed of your tests and isolate the models and observer logic. # # For example, the following will disable the observers in RSpec: # # config.before(:each) { Neo4j::Rails::Observer.disable_observers } # # But if you do want to run a particular observer(s) as part of the test, # you can temporarily enable it: # # Neo4j::Rails::Observer.with_observers(:user_recorder, :account_observer) do # # Any code here will work with observers enabled # end # # == Loading # # Observers register themselves with the model class that they observe, # since it is the class that notifies them of events when they occur. # As a side-effect, when an observer is loaded, its corresponding model # class is loaded. # # Observers are loaded after the application initializers, so that # observed models can make use of extensions. If by any chance you are # using observed models in the initialization, you can # still load their observers by calling +ModelObserver.instance+ before. # Observers are singletons and that call instantiates and registers them. class Observer < ActiveModel::Observer # Instantiate the new observer. Will add all child observers as well. # # @example Instantiate the observer. # Neo4j::Rails::Observer.new def initialize super and observed_descendants.each { |klass| add_observer!(klass) } end cattr_accessor :default_observers_enabled, :observers_enabled # TODO: Add docs class << self # Enables all observers (default behavior) def enable_observers self.default_observers_enabled = true end # Disables all observers def disable_observers self.default_observers_enabled = false end # Run a block with a specific set of observers enabled def with_observers(*observer_syms) self.observers_enabled = Array(observer_syms).map do |o| o.respond_to?(:instance) ? o.instance : o.to_s.classify.constantize.instance end yield ensure self.observers_enabled = [] end # Determines whether an observer is enabled. Either: # - All observers are enabled OR # - The observer is in the whitelist def observer_enabled?(observer) default_observers_enabled or self.observers_enabled.include?(observer) end end # Determines whether this observer should be run def observer_enabled? self.class.observer_enabled?(self) end # By default, enable all observers enable_observers self.observers_enabled = [] protected # Get all the child observers. # # @example Get the children. # observer.observed_descendants # # @return [ Array<Class> ] The children. def observed_descendants observed_classes.inject([]) { |all, klass| all += klass.descendants } end # Adds the specified observer to the class. # # @example Add the observer. # observer.add_observer!(Document) # # @param [ Class ] klass The child observer to add. def add_observer!(klass) super and define_callbacks(klass) end # Defines all the callbacks for each observer of the model. # # @example Define all the callbacks. # observer.define_callbacks(Document) # # @param [ Class ] klass The model to define them on. def define_callbacks(klass) tap do |observer| observer_name = observer.class.name.underscore.gsub('/', '__') Neo4j::Rails::Callbacks::CALLBACKS.each do |callback| next unless respond_to?(callback) callback_meth = :"_notify_#{observer_name}_for_#{callback}" unless klass.respond_to?(callback_meth) klass.send(:define_method, callback_meth) do |&block| observer.send(callback, self, &block) if observer.observer_enabled? end klass.send(callback, callback_meth) end end end end end end end