rails-observers-0.1.5/0000755000004100000410000000000013301532025014661 5ustar www-datawww-datarails-observers-0.1.5/README.md0000644000004100000410000001004013301532025016133 0ustar www-datawww-data[![Build Status](https://secure.travis-ci.org/rails/rails-observers.png)](https://travis-ci.org/rails/rails-observers) # Rails::Observers Rails Observers (removed from core in Rails 4.0) ## Installation Add this line to your application's Gemfile: ```ruby gem 'rails-observers' ``` And then execute: $ bundle Or install it yourself as: $ gem install rails-observers ## Usage This gem contains two observers: * Active Record Observer * Action Controller Sweeper ### Active Record Observer 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. Observers are put in `app/models` (e.g. `app/models/comment_observer.rb`). Example: ```ruby class CommentObserver < ActiveRecord::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. ```ruby class ContactObserver < ActiveRecord::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. The convention is to name observers after the class they observe. If you absolutely need to override this, or want to use one observer for several classes, use `observe`: ```ruby class NotificationsObserver < ActiveRecord::Observer observe :comment, :like def after_create(record) # notifiy users of new comment or like end end ``` Please note that observers are called in the order that they are defined. This means that callbacks in an observer will always be called *after* callbacks defined in the model itself. Likewise, `has_one` and `has_many` use callbacks to enforce `dependent: :destroy`. Therefore, associated records will be destroyed before the observer's `before_destroy` is called. For an observer to be active, it must be registered first. This can be done by adding the following line into the `application.rb`: config.active_record.observers = :contact_observer Observers can also be registered on an environment-specific basis by simply using the corresponding environment's configuration file instead of `application.rb`. ### Action Controller Sweeper Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example: ```ruby class ListSweeper < ActionController::Caching::Sweeper observe List, Item def after_save(record) list = record.is_a?(List) ? record : record.list expire_page(controller: "lists", action: %w( show public feed ), id: list.id) expire_action(controller: "lists", action: "all") list.shares.each { |share| expire_page(controller: "lists", action: "show", id: share.url_key) } end end ``` The sweeper is assigned in the controllers that wish to have its job performed using the `cache_sweeper` class method: ```ruby class ListsController < ApplicationController caches_action :index, :show, :public, :feed cache_sweeper :list_sweeper, only: [ :edit, :destroy, :share ] end ``` In the example above, four actions are cached and three actions are responsible for expiring those caches. You can also name an explicit class in the declaration of a sweeper, which is needed if the sweeper is in a module: ```ruby class ListsController < ApplicationController caches_action :index, :show, :public, :feed cache_sweeper OpenBar::Sweeper, only: [ :edit, :destroy, :share ] end ``` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Added some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request rails-observers-0.1.5/rails-observers.gemspec0000644000004100000410000000751213301532025021355 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: rails-observers 0.1.5 ruby lib Gem::Specification.new do |s| s.name = "rails-observers".freeze s.version = "0.1.5" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Rafael Mendon\u{e7}a Fran\u{e7}a".freeze, "Steve Klabnik".freeze] s.date = "2017-07-16" s.description = "Rails observer (removed from core in Rails 4.0)".freeze s.email = ["rafaelmfranca@gmail.com".freeze, "steve@steveklabnik.com".freeze] s.files = ["LICENSE".freeze, "README.md".freeze, "lib/generators/active_record/observer/observer_generator.rb".freeze, "lib/generators/active_record/observer/templates/observer.rb".freeze, "lib/generators/rails/observer/USAGE".freeze, "lib/generators/rails/observer/observer_generator.rb".freeze, "lib/generators/test_unit/observer/observer_generator.rb".freeze, "lib/generators/test_unit/observer/templates/unit_test.rb".freeze, "lib/rails-observers.rb".freeze, "lib/rails/observers/action_controller/caching.rb".freeze, "lib/rails/observers/action_controller/caching/sweeper.rb".freeze, "lib/rails/observers/action_controller/caching/sweeping.rb".freeze, "lib/rails/observers/active_model.rb".freeze, "lib/rails/observers/active_model/active_model.rb".freeze, "lib/rails/observers/active_model/observer_array.rb".freeze, "lib/rails/observers/active_model/observing.rb".freeze, "lib/rails/observers/active_resource/observing.rb".freeze, "lib/rails/observers/activerecord/active_record.rb".freeze, "lib/rails/observers/activerecord/base.rb".freeze, "lib/rails/observers/activerecord/observer.rb".freeze, "lib/rails/observers/railtie.rb".freeze, "lib/rails/observers/version.rb".freeze] s.homepage = "https://github.com/rails/rails-observers".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.2.2".freeze) s.rubygems_version = "2.5.2.1".freeze s.summary = "ActiveModel::Observer, ActiveRecord::Observer and ActionController::Caching::Sweeper extracted from Rails.".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, [">= 4.0"]) s.add_development_dependency(%q.freeze, [">= 4.0"]) s.add_runtime_dependency(%q.freeze, [">= 4.0"]) s.add_development_dependency(%q.freeze, [">= 4.0"]) s.add_development_dependency(%q.freeze, [">= 4.0"]) s.add_development_dependency(%q.freeze, [">= 3"]) s.add_development_dependency(%q.freeze, [">= 4.0"]) s.add_development_dependency(%q.freeze, [">= 1.3"]) else s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 3"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 1.3"]) end else s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 3"]) s.add_dependency(%q.freeze, [">= 4.0"]) s.add_dependency(%q.freeze, [">= 1.3"]) end end rails-observers-0.1.5/LICENSE0000644000004100000410000000211513301532025015665 0ustar www-datawww-dataCopyright (c) 2012-2016 Steve Klabnik, Rafael Mendonça França MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rails-observers-0.1.5/lib/0000755000004100000410000000000013301532025015427 5ustar www-datawww-datarails-observers-0.1.5/lib/rails-observers.rb0000644000004100000410000000012613301532025021075 0ustar www-datawww-datarequire 'rails/observers/railtie' if defined? Rails require 'rails/observers/version' rails-observers-0.1.5/lib/generators/0000755000004100000410000000000013301532025017600 5ustar www-datawww-datarails-observers-0.1.5/lib/generators/test_unit/0000755000004100000410000000000013301532025021616 5ustar www-datawww-datarails-observers-0.1.5/lib/generators/test_unit/observer/0000755000004100000410000000000013301532025023445 5ustar www-datawww-datarails-observers-0.1.5/lib/generators/test_unit/observer/templates/0000755000004100000410000000000013301532025025443 5ustar www-datawww-datarails-observers-0.1.5/lib/generators/test_unit/observer/templates/unit_test.rb0000644000004100000410000000026313301532025030007 0ustar www-datawww-datarequire 'test_helper' <% module_namespacing do -%> class <%= class_name %>ObserverTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end <% end -%> rails-observers-0.1.5/lib/generators/test_unit/observer/observer_generator.rb0000644000004100000410000000057713301532025027700 0ustar www-datawww-datarequire 'rails/generators/test_unit' module TestUnit module Generators class ObserverGenerator < Base check_class_collision :suffix => "ObserverTest" source_root File.expand_path("../templates", __FILE__) def create_test_files template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_observer_test.rb") end end end end rails-observers-0.1.5/lib/generators/rails/0000755000004100000410000000000013301532025020712 5ustar www-datawww-datarails-observers-0.1.5/lib/generators/rails/observer/0000755000004100000410000000000013301532025022541 5ustar www-datawww-datarails-observers-0.1.5/lib/generators/rails/observer/observer_generator.rb0000644000004100000410000000022113301532025026756 0ustar www-datawww-datamodule Rails module Generators class ObserverGenerator < NamedBase #metagenerator hook_for :orm, :required => true end end end rails-observers-0.1.5/lib/generators/rails/observer/USAGE0000644000004100000410000000061413301532025023331 0ustar www-datawww-dataDescription: Stubs out a new observer. Pass the observer name, either CamelCased or under_scored, as an argument. This generator only invokes your ORM and test framework generators. Example: `rails generate observer Account` For ActiveRecord and TestUnit it creates: Observer: app/models/account_observer.rb TestUnit: test/unit/account_observer_test.rb rails-observers-0.1.5/lib/generators/active_record/0000755000004100000410000000000013301532025022411 5ustar www-datawww-datarails-observers-0.1.5/lib/generators/active_record/observer/0000755000004100000410000000000013301532025024240 5ustar www-datawww-datarails-observers-0.1.5/lib/generators/active_record/observer/templates/0000755000004100000410000000000013301532025026236 5ustar www-datawww-datarails-observers-0.1.5/lib/generators/active_record/observer/templates/observer.rb0000644000004100000410000000014513301532025030412 0ustar www-datawww-data<% module_namespacing do -%> class <%= class_name %>Observer < ActiveRecord::Observer end <% end -%> rails-observers-0.1.5/lib/generators/active_record/observer/observer_generator.rb0000644000004100000410000000064113301532025030463 0ustar www-datawww-datarequire 'rails/generators/active_record' module ActiveRecord module Generators class ObserverGenerator < Base check_class_collision :suffix => "Observer" source_root File.expand_path("../templates", __FILE__) def create_observer_file template 'observer.rb', File.join('app/models', class_path, "#{file_name}_observer.rb") end hook_for :test_framework end end end rails-observers-0.1.5/lib/rails/0000755000004100000410000000000013301532025016541 5ustar www-datawww-datarails-observers-0.1.5/lib/rails/observers/0000755000004100000410000000000013301532025020553 5ustar www-datawww-datarails-observers-0.1.5/lib/rails/observers/version.rb0000644000004100000410000000010013301532025022554 0ustar www-datawww-datamodule Rails module Observers VERSION = "0.1.5" end end rails-observers-0.1.5/lib/rails/observers/active_model.rb0000644000004100000410000000006413301532025023533 0ustar www-datawww-datarequire 'rails/observers/active_model/active_model' rails-observers-0.1.5/lib/rails/observers/railtie.rb0000644000004100000410000000430213301532025022530 0ustar www-datawww-datarequire 'rails/railtie' module Rails module Observers class Railtie < ::Rails::Railtie initializer "active_record.observer", :before => "active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do require "rails/observers/activerecord/active_record" observers = app.config.active_record.delete(:observers) self.observers = observers if observers end end initializer "action_controller.caching.sweepers" do ActiveSupport.on_load(:action_controller) do require "rails/observers/action_controller/caching" end end initializer "active_resource.observer" do |app| ActiveSupport.on_load(:active_resource) do require 'rails/observers/active_resource/observing' prepend ActiveResource::Observing end end config.after_initialize do |app| begin # Eager load `ActiveRecord::Base` to avoid circular references when # loading a constant for the first time. # # E.g. loading a `User` model that references `ActiveRecord::Base` # which calls `instantiate_observers` to instantiate a `UserObserver` # which eventually calls `observed_class` thus constantizing `"User"`, # the class we're loading. 💣💥 require "active_record/base" rescue LoadError end ActiveSupport.on_load(:active_record) do ActiveRecord::Base.instantiate_observers # Rails 5.1 forward-compat. AD::R is deprecated to AS::R in Rails 5. reloader = defined?(ActiveSupport::Reloader) ? ActiveSupport::Reloader : ActionDispatch::Reloader reloader.to_prepare do ActiveRecord::Base.instantiate_observers end end ActiveSupport.on_load(:active_resource) do self.instantiate_observers # Rails 5.1 forward-compat. AD::R is deprecated to AS::R in Rails 5. reloader = defined?(ActiveSupport::Reloader) ? ActiveSupport::Reloader : ActionDispatch::Reloader reloader.to_prepare do ActiveResource::Base.instantiate_observers end end end end end end rails-observers-0.1.5/lib/rails/observers/active_model/0000755000004100000410000000000013301532025023206 5ustar www-datawww-datarails-observers-0.1.5/lib/rails/observers/active_model/active_model.rb0000644000004100000410000000022613301532025026166 0ustar www-datawww-datamodule ActiveModel autoload :Observer, 'rails/observers/active_model/observing' autoload :Observing, 'rails/observers/active_model/observing' end rails-observers-0.1.5/lib/rails/observers/active_model/observing.rb0000644000004100000410000002721313301532025025536 0ustar www-datawww-datarequire 'singleton' require 'rails/observers/active_model/observer_array' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/enumerable' require 'active_support/core_ext/object/try' require 'active_support/descendants_tracker' module ActiveModel # == Active Model Observers Activation module Observing extend ActiveSupport::Concern included do extend ActiveSupport::DescendantsTracker end module ClassMethods # Activates the observers assigned. # # class ORM # include ActiveModel::Observing # end # # # Calls PersonObserver.instance # ORM.observers = :person_observer # # # Calls Cacher.instance and GarbageCollector.instance # ORM.observers = :cacher, :garbage_collector # # # Same as above, just using explicit class references # ORM.observers = Cacher, GarbageCollector # # Note: Setting this does not instantiate the observers yet. # instantiate_observers is called during startup, and before # each development request. def observers=(*values) observers.replace(values.flatten) end # Gets an array of observers observing this model. The array also provides # +enable+ and +disable+ methods that allow you to selectively enable and # disable observers (see ActiveModel::ObserverArray.enable and # ActiveModel::ObserverArray.disable for more on this). # # class ORM # include ActiveModel::Observing # end # # ORM.observers = :cacher, :garbage_collector # ORM.observers # => [:cacher, :garbage_collector] # ORM.observers.class # => ActiveModel::ObserverArray def observers @observers ||= ObserverArray.new(self) end # Returns the current observer instances. # # class Foo # include ActiveModel::Observing # # attr_accessor :status # end # # class FooObserver < ActiveModel::Observer # def on_spec(record, *args) # record.status = true # end # end # # Foo.observers = FooObserver # Foo.instantiate_observers # # Foo.observer_instances # => [#] def observer_instances @observer_instances ||= [] end # Instantiate the global observers. # # class Foo # include ActiveModel::Observing # # attr_accessor :status # end # # class FooObserver < ActiveModel::Observer # def on_spec(record, *args) # record.status = true # end # end # # Foo.observers = FooObserver # # foo = Foo.new # foo.status = false # foo.notify_observers(:on_spec) # foo.status # => false # # Foo.instantiate_observers # => [FooObserver] # # foo = Foo.new # foo.status = false # foo.notify_observers(:on_spec) # foo.status # => true def instantiate_observers observers.each { |o| instantiate_observer(o) } end # Add a new observer to the pool. The new observer needs to respond to # update, otherwise it raises an +ArgumentError+ exception. # # class Foo # include ActiveModel::Observing # end # # class FooObserver < ActiveModel::Observer # end # # Foo.add_observer(FooObserver.instance) # # Foo.observers_instance # # => [#] def add_observer(observer) unless observer.respond_to? :update raise ArgumentError, "observer needs to respond to 'update'" end observer_instances << observer end # Fires notifications to model's observers. # # def save # notify_observers(:before_save) # ... # notify_observers(:after_save) # end # # Custom notifications can be sent in a similar fashion: # # notify_observers(:custom_notification, :foo) # # This will call custom_notification, passing as arguments # the current object and :foo. def notify_observers(*args) observer_instances.each { |observer| observer.update(*args) } end # Returns the total number of instantiated observers. # # class Foo # include ActiveModel::Observing # # attr_accessor :status # end # # class FooObserver < ActiveModel::Observer # def on_spec(record, *args) # record.status = true # end # end # # Foo.observers = FooObserver # Foo.observers_count # => 0 # Foo.instantiate_observers # Foo.observers_count # => 1 def observers_count observer_instances.size end # count_observers is deprecated. Use #observers_count. def count_observers msg = "count_observers is deprecated in favor of observers_count" ActiveSupport::Deprecation.warn(msg) observers_count end protected def instantiate_observer(observer) #:nodoc: # string/symbol if observer.respond_to?(:to_sym) observer = observer.to_s.camelize.constantize end if observer.respond_to?(:instance) observer.instance else raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or " + "the class itself) responding to the method :instance. " + "Example: Person.observers = :big_brother # calls " + "BigBrother.instance" end end # Notify observers when the observed class is subclassed. def inherited(subclass) #:nodoc: super notify_observers :observed_class_inherited, subclass end end # Notify a change to the list of observers. # # class Foo # include ActiveModel::Observing # # attr_accessor :status # end # # class FooObserver < ActiveModel::Observer # def on_spec(record, *args) # record.status = true # end # end # # Foo.observers = FooObserver # Foo.instantiate_observers # => [FooObserver] # # foo = Foo.new # foo.status = false # foo.notify_observers(:on_spec) # foo.status # => true # # See ActiveModel::Observing::ClassMethods.notify_observers for more # information. def notify_observers(method, *extra_args) self.class.notify_observers(method, self, *extra_args) end end # == Active Model Observers # # 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. # # class CommentObserver < ActiveModel::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 < ActiveModel::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 < ActiveModel::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 < ActiveModel::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. # # If you're using an Observer in a Rails application with Active Record, be # sure to read about the necessary configuration in the documentation for # ActiveRecord::Observer. class Observer include Singleton extend ActiveSupport::DescendantsTracker class << self # Attaches the observer to the supplied model classes. # # class AuditObserver < ActiveModel::Observer # observe :account, :balance # end # # AuditObserver.observed_classes # => [Account, Balance] def observe(*models) models.flatten! models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model } singleton_class.redefine_method(:observed_classes) { models } end # Returns an array of Classes to observe. # # AccountObserver.observed_classes # => [Account] # # You can override this instead of using the +observe+ helper. # # class AuditObserver < ActiveModel::Observer # def self.observed_classes # [Account, Balance] # end # end def observed_classes Array(observed_class) end # Returns the class observed by default. It's inferred from the observer's # class name. # # PersonObserver.observed_class # => Person # AccountObserver.observed_class # => Account def observed_class name[/(.*)Observer/, 1].try :constantize end end # Start observing the declared classes and their subclasses. # Called automatically by the instance method. def initialize #:nodoc: observed_classes.each { |klass| add_observer!(klass) } end def observed_classes #:nodoc: self.class.observed_classes end # Send observed_method(object) if the method exists and # the observer is enabled for the given object's class. def update(observed_method, object, *extra_args, &block) #:nodoc: return if !respond_to?(observed_method) || disabled_for?(object) send(observed_method, object, *extra_args, &block) end # Special method sent by the observed class when it is inherited. # Passes the new subclass. def observed_class_inherited(subclass) #:nodoc: self.class.observe(observed_classes + [subclass]) add_observer!(subclass) end protected def add_observer!(klass) #:nodoc: klass.add_observer(self) end # Returns true if notifications are disabled for this object. def disabled_for?(object) #:nodoc: klass = object.class return false unless klass.respond_to?(:observers) klass.observers.disabled_for?(self) end end end rails-observers-0.1.5/lib/rails/observers/active_model/observer_array.rb0000644000004100000410000001101613301532025026557 0ustar www-datawww-datarequire 'set' module ActiveModel # Stores the enabled/disabled state of individual observers for # a particular model class. class ObserverArray < Array attr_reader :model_class def initialize(model_class, *args) #:nodoc: @model_class = model_class super(*args) end # Returns +true+ if the given observer is disabled for the model class, # +false+ otherwise. def disabled_for?(observer) #:nodoc: disabled_observers.include?(observer.class) end # Disables one or more observers. This supports multiple forms: # # ORM.observers.disable :all # # => disables all observers for all models subclassed from # # an ORM base class that includes ActiveModel::Observing # # e.g. ActiveRecord::Base # # ORM.observers.disable :user_observer # # => disables the UserObserver # # User.observers.disable AuditTrail # # => disables the AuditTrail observer for User notifications. # # Other models will still notify the AuditTrail observer. # # ORM.observers.disable :observer_1, :observer_2 # # => disables Observer1 and Observer2 for all models. # # User.observers.disable :all do # # all user observers are disabled for # # just the duration of the block # end def disable(*observers, &block) set_enablement(false, observers, &block) end # Enables one or more observers. This supports multiple forms: # # ORM.observers.enable :all # # => enables all observers for all models subclassed from # # an ORM base class that includes ActiveModel::Observing # # e.g. ActiveRecord::Base # # ORM.observers.enable :user_observer # # => enables the UserObserver # # User.observers.enable AuditTrail # # => enables the AuditTrail observer for User notifications. # # Other models will not be affected (i.e. they will not # # trigger notifications to AuditTrail if previously disabled) # # ORM.observers.enable :observer_1, :observer_2 # # => enables Observer1 and Observer2 for all models. # # User.observers.enable :all do # # all user observers are enabled for # # just the duration of the block # end # # Note: all observers are enabled by default. This method is only # useful when you have previously disabled one or more observers. def enable(*observers, &block) set_enablement(true, observers, &block) end protected def disabled_observers #:nodoc: @disabled_observers ||= Set.new end def observer_class_for(observer) #:nodoc: return observer if observer.is_a?(Class) if observer.respond_to?(:to_sym) # string/symbol observer.to_s.camelize.constantize else raise ArgumentError, "#{observer} was not a class or a " + "lowercase, underscored class name as expected." end end def start_transaction #:nodoc: disabled_observer_stack.push(disabled_observers.dup) each_subclass_array do |array| array.start_transaction end end def disabled_observer_stack #:nodoc: @disabled_observer_stack ||= [] end def end_transaction #:nodoc: @disabled_observers = disabled_observer_stack.pop each_subclass_array do |array| array.end_transaction end end def transaction #:nodoc: start_transaction begin yield ensure end_transaction end end def each_subclass_array #:nodoc: model_class.descendants.each do |subclass| yield subclass.observers end end def set_enablement(enabled, observers) #:nodoc: if block_given? transaction do set_enablement(enabled, observers) yield end else observers = ActiveModel::Observer.descendants if observers == [:all] observers.each do |obs| klass = observer_class_for(obs) unless klass < ActiveModel::Observer raise ArgumentError.new("#{obs} does not refer to a valid observer") end if enabled disabled_observers.delete(klass) else disabled_observers << klass end end each_subclass_array do |array| array.set_enablement(enabled, observers) end end end end end rails-observers-0.1.5/lib/rails/observers/active_resource/0000755000004100000410000000000013301532025023735 5ustar www-datawww-datarails-observers-0.1.5/lib/rails/observers/active_resource/observing.rb0000644000004100000410000000143313301532025026261 0ustar www-datawww-datarequire 'rails/observers/active_model/observing' module ActiveResource module Observing def self.prepended(context) context.include ActiveModel::Observing end def create(*) notify_observers(:before_create) if result = super notify_observers(:after_create) end result end def save(*) notify_observers(:before_save) if result = super notify_observers(:after_save) end result end def update(*) notify_observers(:before_update) if result = super notify_observers(:after_update) end result end def destroy(*) notify_observers(:before_destroy) if result = super notify_observers(:after_destroy) end result end end end rails-observers-0.1.5/lib/rails/observers/activerecord/0000755000004100000410000000000013301532025023225 5ustar www-datawww-datarails-observers-0.1.5/lib/rails/observers/activerecord/active_record.rb0000644000004100000410000000020313301532025026356 0ustar www-datawww-datarequire 'rails/observers/activerecord/base' module ActiveRecord autoload :Observer, 'rails/observers/activerecord/observer' end rails-observers-0.1.5/lib/rails/observers/activerecord/base.rb0000644000004100000410000000026313301532025024465 0ustar www-datawww-datarequire 'rails/observers/active_model/active_model' module ActiveRecord class Base extend ActiveModel::Observing::ClassMethods include ActiveModel::Observing end end rails-observers-0.1.5/lib/rails/observers/activerecord/observer.rb0000644000004100000410000001134313301532025025403 0ustar www-datawww-datamodule ActiveRecord # = Active Record Observer # # 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. Example: # # class CommentObserver < ActiveRecord::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 < ActiveRecord::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 < ActiveRecord::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 < ActiveRecord::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 # # The observer can implement callback methods for each of the methods described in the Callbacks module. # # == Storing Observers in Rails # # If you're using Active Record 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.active_record.observers configuration # setting in your config/application.rb file. # # config.active_record.observers = :comment_observer, :signup_observer # # Observers will not be invoked unless you define these in your application configuration. # # If you are using Active Record outside Rails, activate the observers explicitly in a configuration or # environment file: # # ActiveRecord::Base.add_observer CommentObserver.instance # ActiveRecord::Base.add_observer SignupObserver.instance # # == Loading # # Observers register themselves in the model class 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. # # Up to (and including) Rails 2.0.2 observers were instantiated between plugins and # application initializers. Now observers are loaded after application initializers, # so 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 protected def observed_classes klasses = super klasses + klasses.map { |klass| klass.descendants }.flatten end def add_observer!(klass) super define_callbacks klass end def define_callbacks(klass) observer = self observer_name = observer.class.name.underscore.gsub('/', '__') ActiveRecord::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.update(callback, self, &block) end klass.send(callback, callback_meth) end end end end end if defined?(ActionController) and defined?(ActionController::Caching::Sweeping) require 'rails/observers/action_controller/caching/sweeper' end rails-observers-0.1.5/lib/rails/observers/action_controller/0000755000004100000410000000000013301532025024273 5ustar www-datawww-datarails-observers-0.1.5/lib/rails/observers/action_controller/caching.rb0000644000004100000410000000055713301532025026223 0ustar www-datawww-datamodule ActionController #:nodoc: module Caching extend ActiveSupport::Autoload eager_autoload do autoload :Sweeper, 'rails/observers/action_controller/caching/sweeper' autoload :Sweeping, 'rails/observers/action_controller/caching/sweeping' end ActionController::Base.extend Sweeping::ClassMethods if defined?(ActiveRecord) end end rails-observers-0.1.5/lib/rails/observers/action_controller/caching/0000755000004100000410000000000013301532025025667 5ustar www-datawww-datarails-observers-0.1.5/lib/rails/observers/action_controller/caching/sweeper.rb0000644000004100000410000000337113301532025027672 0ustar www-datawww-datamodule ActionController #:nodoc: module Caching class Sweeper < ActiveRecord::Observer #:nodoc: attr_accessor :controller def initialize(*args) super @controller = nil end def before(controller) self.controller = controller callback(:before) if controller.perform_caching true # before method from sweeper should always return true end def after(controller) self.controller = controller callback(:after) if controller.perform_caching end def around(controller) before(controller) yield after(controller) ensure clean_up end protected # gets the action cache path for the given options. def action_path_for(options) Actions::ActionCachePath.new(controller, options).path end # Retrieve instance variables set in the controller. def assigns(key) controller.instance_variable_get("@#{key}") end private def clean_up # Clean up, so that the controller can be collected after this request self.controller = nil end def callback(timing) controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}" action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}" __send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true) __send__(action_callback_method_name) if respond_to?(action_callback_method_name, true) end def method_missing(method, *arguments, &block) return super unless @controller @controller.__send__(method, *arguments, &block) end end end end rails-observers-0.1.5/lib/rails/observers/action_controller/caching/sweeping.rb0000644000004100000410000000457413301532025030047 0ustar www-datawww-datamodule ActionController #:nodoc: module Caching # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example: # # class ListSweeper < ActionController::Caching::Sweeper # observe List, Item # # def after_save(record) # list = record.is_a?(List) ? record : record.list # expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id) # expire_action(:controller => "lists", :action => "all") # list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) } # end # end # # The sweeper is assigned in the controllers that wish to have its job performed using the cache_sweeper class method: # # class ListsController < ApplicationController # caches_action :index, :show, :public, :feed # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ] # end # # In the example above, four actions are cached and three actions are responsible for expiring those caches. # # You can also name an explicit class in the declaration of a sweeper, which is needed if the sweeper is in a module: # # class ListsController < ApplicationController # caches_action :index, :show, :public, :feed # cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ] # end module Sweeping module ClassMethods #:nodoc: def cache_sweeper(*sweepers) configuration = sweepers.extract_options! sweepers.each do |sweeper| ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base) sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.classify) : sweeper).instance if sweeper_instance.is_a?(Sweeper) around_action(sweeper_instance, :only => configuration[:only]) else after_action(sweeper_instance, :only => configuration[:only]) end end end end end if defined?(ActiveRecord) and defined?(ActiveRecord::Observer) require 'rails/observers/action_controller/caching/sweeper' end end end