rails-observers-0.1.2/0000755000004100000410000000000012250222654014664 5ustar www-datawww-datarails-observers-0.1.2/rails-observers.gemspec.erb0000644000004100000410000000225412250222654022125 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.expand_path('../lib/rails/observers/version', __FILE__) Gem::Specification.new do |s| s.name = "rails-observers" s.authors = ["Rafael Mendonça França", "Steve Klabnik"] s.email = ["rafaelmfranca@gmail.com", "steve@steveklabnik.com"] s.description = %q{Rails observer (removed from core in Rails 4.0)} s.summary = %q{ActiveModel::Observer, ActiveRecord::Observer and ActionController::Caching::Sweeper extracted from Rails.} s.homepage = "https://github.com/rails/rails-observers" s.version = Rails::Observers::VERSION s.files = [<%= files.map(&:inspect).join ',' %>] s.test_files = [<%= test_files.map(&:inspect).join ',' %>] s.executables = [<%= executables.map(&:inspect).join ',' %>] s.require_paths = ["lib"] s.add_dependency 'activemodel', '~> 4.0' s.add_development_dependency 'minitest', '>= 3' s.add_development_dependency 'railties', '~> 4.0' s.add_development_dependency 'activerecord', '~> 4.0' s.add_development_dependency 'actionmailer', '~> 4.0' s.add_development_dependency 'actionpack', '~> 4.0' s.add_development_dependency 'sqlite3', '~> 1.3' end rails-observers-0.1.2/Rakefile0000644000004100000410000000150712250222654016334 0ustar www-datawww-data#!/usr/bin/env rake require "bundler/gem_tasks" require 'rake/testtask' Rake::TestTask.new("test:regular") do |t| t.libs = ["test"] t.pattern = "test/*_test.rb" t.ruby_opts = ['-w'] end Rake::TestTask.new("test:generators") do |t| t.libs = ["test"] t.pattern = "test/generators/*_test.rb" t.ruby_opts = ['-w'] end task :default => :test task :test => ['test:regular', 'test:generators'] specname = "rails-observers.gemspec" deps = `git ls-files`.split("\n") - [specname] file specname => deps do files = `git ls-files`.split("\n") test_files = `git ls-files -- {test,spec,features}/*`.split("\n") executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } require 'erb' File.open specname, 'w:utf-8' do |f| f.write ERB.new(File.read("#{specname}.erb")).result(binding) end end rails-observers-0.1.2/Gemfile0000644000004100000410000000026012250222654016155 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in active_record-observers.gemspec gemspec gem 'activerecord-deprecated_finders' gem 'mocha', require: false rails-observers-0.1.2/.travis.yml0000644000004100000410000000035112250222654016774 0ustar www-datawww-datalanguage: ruby rvm: - 1.9.3 before_install: gem install bundler script: bundle exec rake test notifications: email: false irc: on_success: change on_failure: always channels: - "irc.freenode.org#rails-contrib" rails-observers-0.1.2/lib/0000755000004100000410000000000012250222654015432 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/0000755000004100000410000000000012250222654017603 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/test_unit/0000755000004100000410000000000012250222654021621 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/test_unit/observer/0000755000004100000410000000000012250222654023450 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/test_unit/observer/templates/0000755000004100000410000000000012250222654025446 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/test_unit/observer/templates/unit_test.rb0000644000004100000410000000026312250222654030012 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.2/lib/generators/test_unit/observer/observer_generator.rb0000644000004100000410000000057712250222654027703 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.2/lib/generators/active_record/0000755000004100000410000000000012250222654022414 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/active_record/observer/0000755000004100000410000000000012250222654024243 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/active_record/observer/templates/0000755000004100000410000000000012250222654026241 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/active_record/observer/templates/observer.rb0000644000004100000410000000014512250222654030415 0ustar www-datawww-data<% module_namespacing do -%> class <%= class_name %>Observer < ActiveRecord::Observer end <% end -%> rails-observers-0.1.2/lib/generators/active_record/observer/observer_generator.rb0000644000004100000410000000064112250222654030466 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.2/lib/generators/rails/0000755000004100000410000000000012250222654020715 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/rails/observer/0000755000004100000410000000000012250222654022544 5ustar www-datawww-datarails-observers-0.1.2/lib/generators/rails/observer/USAGE0000644000004100000410000000061412250222654023334 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.2/lib/generators/rails/observer/observer_generator.rb0000644000004100000410000000022112250222654026761 0ustar www-datawww-datamodule Rails module Generators class ObserverGenerator < NamedBase #metagenerator hook_for :orm, :required => true end end end rails-observers-0.1.2/lib/rails-observers.rb0000644000004100000410000000012612250222654021100 0ustar www-datawww-datarequire 'rails/observers/railtie' if defined? Rails require 'rails/observers/version' rails-observers-0.1.2/lib/rails/0000755000004100000410000000000012250222654016544 5ustar www-datawww-datarails-observers-0.1.2/lib/rails/observers/0000755000004100000410000000000012250222654020556 5ustar www-datawww-datarails-observers-0.1.2/lib/rails/observers/activerecord/0000755000004100000410000000000012250222654023230 5ustar www-datawww-datarails-observers-0.1.2/lib/rails/observers/activerecord/observer.rb0000644000004100000410000001112012250222654025377 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 rails-observers-0.1.2/lib/rails/observers/activerecord/active_record.rb0000644000004100000410000000020312250222654026361 0ustar www-datawww-datarequire 'rails/observers/activerecord/base' module ActiveRecord autoload :Observer, 'rails/observers/activerecord/observer' end rails-observers-0.1.2/lib/rails/observers/activerecord/base.rb0000644000004100000410000000026312250222654024470 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.2/lib/rails/observers/version.rb0000644000004100000410000000010012250222654022557 0ustar www-datawww-datamodule Rails module Observers VERSION = "0.1.2" end end rails-observers-0.1.2/lib/rails/observers/active_model.rb0000644000004100000410000000006412250222654023536 0ustar www-datawww-datarequire 'rails/observers/active_model/active_model' rails-observers-0.1.2/lib/rails/observers/action_controller/0000755000004100000410000000000012250222654024276 5ustar www-datawww-datarails-observers-0.1.2/lib/rails/observers/action_controller/caching.rb0000644000004100000410000000056012250222654026220 0ustar www-datawww-datamodule ActionController #:nodoc: module Caching extend ActiveSupport::Autoload eager_autoload do autoload :Sweeper, 'rails/observers/action_controller/caching/sweeping' autoload :Sweeping, 'rails/observers/action_controller/caching/sweeping' end ActionController::Base.extend Sweeping::ClassMethods if defined?(ActiveRecord) end end rails-observers-0.1.2/lib/rails/observers/action_controller/caching/0000755000004100000410000000000012250222654025672 5ustar www-datawww-datarails-observers-0.1.2/lib/rails/observers/action_controller/caching/sweeping.rb0000644000004100000410000001012512250222654030037 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_filter(sweeper_instance, :only => configuration[:only]) else after_filter(sweeper_instance, :only => configuration[:only]) end end end end end if defined?(ActiveRecord) and defined?(ActiveRecord::Observer) 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 end rails-observers-0.1.2/lib/rails/observers/active_model/0000755000004100000410000000000012250222654023211 5ustar www-datawww-datarails-observers-0.1.2/lib/rails/observers/active_model/observing.rb0000644000004100000410000002721312250222654025541 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.2/lib/rails/observers/active_model/observer_array.rb0000644000004100000410000001101612250222654026562 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.2/lib/rails/observers/active_model/active_model.rb0000644000004100000410000000022612250222654026171 0ustar www-datawww-datamodule ActiveModel autoload :Observer, 'rails/observers/active_model/observing' autoload :Observing, 'rails/observers/active_model/observing' end rails-observers-0.1.2/lib/rails/observers/railtie.rb0000644000004100000410000000172312250222654022537 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" if observers = app.config.respond_to?(:active_record) && app.config.active_record.delete(:observers) send :observers=, observers end end end initializer "action_controller.caching.sweepers" do ActiveSupport.on_load(:action_controller) do require "rails/observers/action_controller/caching" end end config.after_initialize do |app| ActiveSupport.on_load(:active_record) do ActiveRecord::Base.instantiate_observers ActionDispatch::Reloader.to_prepare do ActiveRecord::Base.instantiate_observers end end end end end end rails-observers-0.1.2/metadata.yml0000644000004100000410000001301112250222654017163 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: rails-observers version: !ruby/object:Gem::Version version: 0.1.2 platform: ruby authors: - Rafael Mendonça França - Steve Klabnik autorequire: bindir: bin cert_chain: [] date: 2013-07-19 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activemodel requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' - !ruby/object:Gem::Dependency name: minitest requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '3' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '3' - !ruby/object:Gem::Dependency name: railties requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' - !ruby/object:Gem::Dependency name: activerecord requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' - !ruby/object:Gem::Dependency name: actionmailer requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' - !ruby/object:Gem::Dependency name: actionpack requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '4.0' - !ruby/object:Gem::Dependency name: sqlite3 requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '1.3' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '1.3' description: Rails observer (removed from core in Rails 4.0) email: - rafaelmfranca@gmail.com - steve@steveklabnik.com executables: [] extensions: [] extra_rdoc_files: [] files: - .gitignore - .travis.yml - Gemfile - LICENSE - README.md - Rakefile - lib/generators/active_record/observer/observer_generator.rb - lib/generators/active_record/observer/templates/observer.rb - lib/generators/rails/observer/USAGE - lib/generators/rails/observer/observer_generator.rb - lib/generators/test_unit/observer/observer_generator.rb - lib/generators/test_unit/observer/templates/unit_test.rb - lib/rails-observers.rb - lib/rails/observers/action_controller/caching.rb - lib/rails/observers/action_controller/caching/sweeping.rb - lib/rails/observers/active_model.rb - lib/rails/observers/active_model/active_model.rb - lib/rails/observers/active_model/observer_array.rb - lib/rails/observers/active_model/observing.rb - lib/rails/observers/activerecord/active_record.rb - lib/rails/observers/activerecord/base.rb - lib/rails/observers/activerecord/observer.rb - lib/rails/observers/railtie.rb - lib/rails/observers/version.rb - rails-observers.gemspec - rails-observers.gemspec.erb - test/configuration_test.rb - test/console_test.rb - test/fixtures/developers.yml - test/fixtures/minimalistics.yml - test/fixtures/topics.yml - test/generators/generators_test_helper.rb - test/generators/namespaced_generators_test.rb - test/generators/observer_generator_test.rb - test/helper.rb - test/isolation/abstract_unit.rb - test/lifecycle_test.rb - test/models/observers.rb - test/observer_array_test.rb - test/observing_test.rb - test/rake_test.rb - test/sweeper_test.rb - test/transaction_callbacks_test.rb homepage: https://github.com/rails/rails-observers licenses: [] metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.0.2 signing_key: specification_version: 4 summary: ActiveModel::Observer, ActiveRecord::Observer and ActionController::Caching::Sweeper extracted from Rails. test_files: - test/configuration_test.rb - test/console_test.rb - test/fixtures/developers.yml - test/fixtures/minimalistics.yml - test/fixtures/topics.yml - test/generators/generators_test_helper.rb - test/generators/namespaced_generators_test.rb - test/generators/observer_generator_test.rb - test/helper.rb - test/isolation/abstract_unit.rb - test/lifecycle_test.rb - test/models/observers.rb - test/observer_array_test.rb - test/observing_test.rb - test/rake_test.rb - test/sweeper_test.rb - test/transaction_callbacks_test.rb rails-observers-0.1.2/test/0000755000004100000410000000000012250222654015643 5ustar www-datawww-datarails-observers-0.1.2/test/helper.rb0000644000004100000410000000320212250222654017444 0ustar www-datawww-datarequire 'minitest/autorun' require 'active_record' require 'rails/observers/activerecord/active_record' FIXTURES_ROOT = File.expand_path(File.dirname(__FILE__)) + "/fixtures" class ActiveSupport::TestCase include ActiveRecord::TestFixtures self.fixture_path = FIXTURES_ROOT self.use_instantiated_fixtures = false self.use_transactional_fixtures = true end ActiveRecord::Base.configurations = { "test" => { adapter: 'sqlite3', database: ':memory:' } } ActiveRecord::Base.establish_connection(:test) ActiveRecord::Schema.verbose = false ActiveRecord::Schema.define do create_table :topics do |t| t.string :title t.string :author_name t.string :author_email_address t.datetime :written_on t.time :bonus_time t.date :last_read t.text :content t.text :important t.boolean :approved, :default => true t.integer :replies_count, :default => 0 t.integer :parent_id t.string :parent_title t.string :type t.string :group t.timestamps end create_table :comments do |t| t.string :title end create_table :minimalistics do |t| end create_table :developers do |t| t.string :name t.integer :salary end end class Topic < ActiveRecord::Base has_many :replies, dependent: :destroy, foreign_key: "parent_id" end class Reply < Topic belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true end class Comment < ActiveRecord::Base def self.lol "lol" end end class Developer < ActiveRecord::Base end class Minimalistic < ActiveRecord::Base end ActiveSupport::Deprecation.silence do require 'active_record/test_case' end rails-observers-0.1.2/test/sweeper_test.rb0000644000004100000410000000410612250222654020702 0ustar www-datawww-datarequire 'minitest/autorun' require 'action_controller' require 'active_record' require 'rails/observers/activerecord/active_record' require 'rails/observers/action_controller/caching' SharedTestRoutes = ActionDispatch::Routing::RouteSet.new class AppSweeper < ActionController::Caching::Sweeper; end class SweeperTestController < ActionController::Base include SharedTestRoutes.url_helpers cache_sweeper :app_sweeper def show render text: 'hello world' end def error raise StandardError.new end end class SweeperTest < ActionController::TestCase def setup @routes = SharedTestRoutes @routes.draw do get ':controller(/:action)' end super end def test_sweeper_should_not_ignore_no_method_error sweeper = ActionController::Caching::Sweeper.send(:new) assert_raise NoMethodError do sweeper.send_not_defined end end def test_sweeper_should_not_block_rendering response = test_process(SweeperTestController) assert_equal 'hello world', response.body end def test_sweeper_should_clean_up_if_exception_is_raised assert_raise StandardError do test_process(SweeperTestController, 'error') end assert_nil AppSweeper.instance.controller end def test_before_method_of_sweeper_should_always_return_true sweeper = ActionController::Caching::Sweeper.send(:new) assert sweeper.before(SweeperTestController.new) end def test_after_method_of_sweeper_should_always_return_nil sweeper = ActionController::Caching::Sweeper.send(:new) assert_nil sweeper.after(SweeperTestController.new) end def test_sweeper_should_not_ignore_unknown_method_calls sweeper = ActionController::Caching::Sweeper.send(:new) assert_raise NameError do sweeper.instance_eval do some_method_that_doesnt_exist end end end private def test_process(controller, action = "show") @controller = controller.is_a?(Class) ? controller.new : controller @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new process(action) end end rails-observers-0.1.2/test/fixtures/0000755000004100000410000000000012250222654017514 5ustar www-datawww-datarails-observers-0.1.2/test/fixtures/minimalistics.yml0000644000004100000410000000001712250222654023102 0ustar www-datawww-datafirst: id: 1 rails-observers-0.1.2/test/fixtures/topics.yml0000644000004100000410000000153212250222654021541 0ustar www-datawww-datafirst: id: 1 title: The First Topic author_name: David author_email_address: david@loudthinking.com written_on: 2003-07-16t15:28:11.2233+01:00 last_read: 2004-04-15 bonus_time: 2005-01-30t15:28:00.00+01:00 content: Have a nice day approved: false replies_count: 1 second: id: 2 title: The Second Topic of the day author_name: Mary written_on: 2004-07-15t15:28:00.0099+01:00 content: Have a nice day approved: true replies_count: 0 parent_id: 1 type: Reply third: id: 3 title: The Third Topic of the day author_name: Carl written_on: 2012-08-12t20:24:22.129346+00:00 content: I'm a troll approved: true replies_count: 1 fourth: id: 4 title: The Fourth Topic of the day author_name: Carl written_on: 2006-07-15t15:28:00.0099+01:00 content: Why not? approved: true type: Reply parent_id: 3 rails-observers-0.1.2/test/fixtures/developers.yml0000644000004100000410000000005512250222654022407 0ustar www-datawww-datadavid: id: 1 name: David salary: 80000 rails-observers-0.1.2/test/lifecycle_test.rb0000644000004100000410000001425312250222654021173 0ustar www-datawww-datarequire 'helper' class SpecialDeveloper < Developer; end class DeveloperObserver < ActiveRecord::Observer def calls @calls ||= [] end def before_save(developer) calls << developer end end class SalaryChecker < ActiveRecord::Observer observe :special_developer attr_accessor :last_saved def before_save(developer) return developer.salary > 80000 end module Implementation def after_save(developer) self.last_saved = developer end end include Implementation end class TopicaAuditor < ActiveRecord::Observer observe :topic attr_reader :topic def after_find(topic) @topic = topic end end class TopicObserver < ActiveRecord::Observer attr_reader :topic def after_find(topic) @topic = topic end # Create an after_save callback, so a notify_observer hook is created # on :topic. def after_save(nothing) end end class MinimalisticObserver < ActiveRecord::Observer attr_reader :minimalistic def after_find(minimalistic) @minimalistic = minimalistic end end class MultiObserver < ActiveRecord::Observer attr_reader :record def self.observed_class() [ Topic, Developer ] end cattr_reader :last_inherited @@last_inherited = nil def observed_class_inherited_with_testing(subclass) observed_class_inherited_without_testing(subclass) @@last_inherited = subclass end alias_method_chain :observed_class_inherited, :testing def after_find(record) @record = record end end class ValidatedComment < Comment attr_accessor :callers before_validation :record_callers after_validation do record_callers end def record_callers callers << self.class if callers end end class ValidatedCommentObserver < ActiveRecord::Observer attr_accessor :callers def after_validation(model) callers << self.class if callers end end class AroundTopic < Topic end class AroundTopicObserver < ActiveRecord::Observer observe :around_topic def topic_ids @topic_ids ||= [] end def around_save(topic) topic_ids << topic.id yield(topic) topic_ids << topic.id end end class LifecycleTest < ActiveRecord::TestCase fixtures :topics, :developers, :minimalistics def test_before_destroy topic = Topic.find(1) assert_difference 'Topic.count', -(1 + topic.replies.size) do topic.destroy end end def test_auto_observer topic_observer = TopicaAuditor.instance assert_nil TopicaAuditor.observed_class assert_equal [Topic], TopicaAuditor.observed_classes.to_a topic = Topic.find(1) assert_equal topic.title, topic_observer.topic.title end def test_inferred_auto_observer topic_observer = TopicObserver.instance assert_equal Topic, TopicObserver.observed_class topic = Topic.find(1) assert_equal topic.title, topic_observer.topic.title end def test_observing_two_classes multi_observer = MultiObserver.instance topic = Topic.find(1) assert_equal topic.title, multi_observer.record.title developer = Developer.find(1) assert_equal developer.name, multi_observer.record.name end def test_observing_subclasses multi_observer = MultiObserver.instance developer = SpecialDeveloper.find(1) assert_equal developer.name, multi_observer.record.name klass = Class.new(Developer) assert_equal klass, multi_observer.last_inherited developer = klass.find(1) assert_equal developer.name, multi_observer.record.name end def test_after_find_can_be_observed_when_its_not_defined_on_the_model observer = MinimalisticObserver.instance assert_equal Minimalistic, MinimalisticObserver.observed_class minimalistic = Minimalistic.find(1) assert_equal minimalistic, observer.minimalistic end def test_after_find_can_be_observed_when_its_defined_on_the_model observer = TopicObserver.instance assert_equal Topic, TopicObserver.observed_class topic = Topic.find(1) assert_equal topic, observer.topic end def test_invalid_observer assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers } end test "model callbacks fire before observers are notified" do callers = [] comment = ValidatedComment.new comment.callers = ValidatedCommentObserver.instance.callers = callers comment.valid? assert_equal [ValidatedComment, ValidatedComment, ValidatedCommentObserver], callers, "model callbacks did not fire before observers were notified" end test "able to save developer" do SalaryChecker.instance # activate developer = SpecialDeveloper.new :name => 'Roger', :salary => 100000 assert developer.save, "developer with normal salary failed to save" end test "unable to save developer with low salary" do SalaryChecker.instance # activate developer = SpecialDeveloper.new :name => 'Rookie', :salary => 50000 assert !developer.save, "allowed to save a developer with too low salary" end test "able to call methods defined with included module" do # https://rails.lighthouseapp.com/projects/8994/tickets/6065-activerecordobserver-is-not-aware-of-method-added-by-including-modules SalaryChecker.instance # activate developer = SpecialDeveloper.create! :name => 'Roger', :salary => 100000 assert_equal developer, SalaryChecker.instance.last_saved end test "around filter from observer should accept block" do observer = AroundTopicObserver.instance topic = AroundTopic.new topic.save assert_nil observer.topic_ids.first assert_not_nil observer.topic_ids.last end test "able to disable observers" do observer = DeveloperObserver.instance # activate observer.calls.clear ActiveRecord::Base.observers.disable DeveloperObserver do Developer.create! :name => 'Ancestor', :salary => 100000 SpecialDeveloper.create! :name => 'Descendent', :salary => 100000 end assert_equal [], observer.calls end def test_observer_is_called_once observer = DeveloperObserver.instance # activate observer.calls.clear developer = Developer.create! :name => 'Ancestor', :salary => 100000 special_developer = SpecialDeveloper.create! :name => 'Descendent', :salary => 100000 assert_equal [developer, special_developer], observer.calls end end rails-observers-0.1.2/test/observer_array_test.rb0000644000004100000410000001700312250222654022255 0ustar www-datawww-datarequire 'minitest/autorun' require 'active_model' require 'rails/observers/active_model/active_model' require 'models/observers' class ObserverArrayTest < ActiveModel::TestCase def teardown ORM.observers.enable :all Budget.observers.enable :all Widget.observers.enable :all end def assert_observer_notified(model_class, observer_class) observer_class.instance.before_save_invocations.clear model_instance = model_class.new model_instance.save assert_equal [model_instance], observer_class.instance.before_save_invocations end def assert_observer_not_notified(model_class, observer_class) observer_class.instance.before_save_invocations.clear model_instance = model_class.new model_instance.save assert_equal [], observer_class.instance.before_save_invocations end test "all observers are enabled by default" do assert_observer_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can disable individual observers using a class constant" do ORM.observers.disable WidgetObserver assert_observer_not_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can enable individual observers using a class constant" do ORM.observers.disable :all ORM.observers.enable AuditTrail assert_observer_not_notified Widget, WidgetObserver assert_observer_not_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can disable individual observers using a symbol" do ORM.observers.disable :budget_observer assert_observer_notified Widget, WidgetObserver assert_observer_not_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can enable individual observers using a symbol" do ORM.observers.disable :all ORM.observers.enable :audit_trail assert_observer_not_notified Widget, WidgetObserver assert_observer_not_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can disable multiple observers at a time" do ORM.observers.disable :widget_observer, :budget_observer assert_observer_not_notified Widget, WidgetObserver assert_observer_not_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can enable multiple observers at a time" do ORM.observers.disable :all ORM.observers.enable :widget_observer, :budget_observer assert_observer_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_not_notified Widget, AuditTrail assert_observer_not_notified Budget, AuditTrail end test "can disable all observers using :all" do ORM.observers.disable :all assert_observer_not_notified Widget, WidgetObserver assert_observer_not_notified Budget, BudgetObserver assert_observer_not_notified Widget, AuditTrail assert_observer_not_notified Budget, AuditTrail end test "can enable all observers using :all" do ORM.observers.disable :all ORM.observers.enable :all assert_observer_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can disable observers on individual models without affecting those observers on other models" do Widget.observers.disable :all assert_observer_not_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_not_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can enable observers on individual models without affecting those observers on other models" do ORM.observers.disable :all Budget.observers.enable AuditTrail assert_observer_not_notified Widget, WidgetObserver assert_observer_not_notified Budget, BudgetObserver assert_observer_not_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can disable observers for the duration of a block" do yielded = false ORM.observers.disable :budget_observer do yielded = true assert_observer_notified Widget, WidgetObserver assert_observer_not_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end assert yielded assert_observer_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "can enable observers for the duration of a block" do yielded = false Widget.observers.disable :all Widget.observers.enable :all do yielded = true assert_observer_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end assert yielded assert_observer_not_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_not_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "raises an appropriate error when a developer accidentally enables or disables the wrong class (i.e. Widget instead of WidgetObserver)" do assert_raise ArgumentError do ORM.observers.enable :widget end assert_raise ArgumentError do ORM.observers.enable Widget end assert_raise ArgumentError do ORM.observers.disable :widget end assert_raise ArgumentError do ORM.observers.disable Widget end end test "allows #enable at the superclass level to override #disable at the subclass level when called last" do Widget.observers.disable :all ORM.observers.enable :all assert_observer_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end test "allows #disable at the superclass level to override #enable at the subclass level when called last" do Budget.observers.enable :audit_trail ORM.observers.disable :audit_trail assert_observer_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_not_notified Widget, AuditTrail assert_observer_not_notified Budget, AuditTrail end test "can use the block form at different levels of the hierarchy" do yielded = false Widget.observers.disable :all ORM.observers.enable :all do yielded = true assert_observer_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end assert yielded assert_observer_not_notified Widget, WidgetObserver assert_observer_notified Budget, BudgetObserver assert_observer_not_notified Widget, AuditTrail assert_observer_notified Budget, AuditTrail end end rails-observers-0.1.2/test/generators/0000755000004100000410000000000012250222654020014 5ustar www-datawww-datarails-observers-0.1.2/test/generators/observer_generator_test.rb0000644000004100000410000000202612250222654025275 0ustar www-datawww-datarequire 'generators/generators_test_helper' require 'generators/rails/observer/observer_generator' class ObserverGeneratorTest < Rails::Generators::TestCase tests Rails::Generators::ObserverGenerator destination File.expand_path("../../tmp", __FILE__) arguments %w(account) def setup super prepare_destination end def test_invokes_default_orm run_generator assert_file "app/models/account_observer.rb", /class AccountObserver < ActiveRecord::Observer/ end def test_invokes_default_orm_with_class_path run_generator ["admin/account"] assert_file "app/models/admin/account_observer.rb", /class Admin::AccountObserver < ActiveRecord::Observer/ end def test_invokes_default_test_framework run_generator assert_file "test/unit/account_observer_test.rb", /class AccountObserverTest < ActiveSupport::TestCase/ end def test_logs_if_the_test_framework_cannot_be_found content = run_generator ["account", "--test-framework=rspec"] assert_match(/rspec \[not found\]/, content) end end rails-observers-0.1.2/test/generators/namespaced_generators_test.rb0000644000004100000410000000206712250222654025736 0ustar www-datawww-datarequire 'generators/generators_test_helper' require 'generators/rails/observer/observer_generator' class NamespacedObserverGeneratorTest < Rails::Generators::TestCase tests Rails::Generators::ObserverGenerator arguments %w(account) destination File.expand_path("../../tmp", __FILE__) def setup super prepare_destination Rails::Generators.namespace = TestApp end def teardown super Rails::Generators.namespace = nil end def test_invokes_default_orm run_generator assert_file "app/models/test_app/account_observer.rb", /module TestApp/, / class AccountObserver < ActiveRecord::Observer/ end def test_invokes_default_orm_with_class_path run_generator ["admin/account"] assert_file "app/models/test_app/admin/account_observer.rb", /module TestApp/, / class Admin::AccountObserver < ActiveRecord::Observer/ end def test_invokes_default_test_framework run_generator assert_file "test/unit/test_app/account_observer_test.rb", /module TestApp/, / class AccountObserverTest < ActiveSupport::TestCase/ end end rails-observers-0.1.2/test/generators/generators_test_helper.rb0000644000004100000410000000052112250222654025106 0ustar www-datawww-datarequire 'minitest/autorun' require 'fileutils' require 'rails/all' require 'rails/generators' require 'rails/generators/test_case' module TestApp class Application < Rails::Application end end # Call configure to load the settings from # Rails.application.config.generators to Rails::Generators Rails.application.load_generators rails-observers-0.1.2/test/transaction_callbacks_test.rb0000644000004100000410000002316212250222654023557 0ustar www-datawww-datarequire "helper" class TransactionCallbacksTest < ActiveRecord::TestCase self.use_transactional_fixtures = false fixtures :topics class TopicWithCallbacks < ActiveRecord::Base self.table_name = :topics after_commit{|record| record.send(:do_after_commit, nil)} after_commit(:on => :create){|record| record.send(:do_after_commit, :create)} after_commit(:on => :update){|record| record.send(:do_after_commit, :update)} after_commit(:on => :destroy){|record| record.send(:do_after_commit, :destroy)} after_rollback{|record| record.send(:do_after_rollback, nil)} after_rollback(:on => :create){|record| record.send(:do_after_rollback, :create)} after_rollback(:on => :update){|record| record.send(:do_after_rollback, :update)} after_rollback(:on => :destroy){|record| record.send(:do_after_rollback, :destroy)} def history @history ||= [] end def after_commit_block(on = nil, &block) @after_commit ||= {} @after_commit[on] ||= [] @after_commit[on] << block end def after_rollback_block(on = nil, &block) @after_rollback ||= {} @after_rollback[on] ||= [] @after_rollback[on] << block end def do_after_commit(on) blocks = @after_commit[on] if defined?(@after_commit) blocks.each{|b| b.call(self)} if blocks end def do_after_rollback(on) blocks = @after_rollback[on] if defined?(@after_rollback) blocks.each{|b| b.call(self)} if blocks end end def setup @first, @second = TopicWithCallbacks.find(1, 3).sort_by { |t| t.id } end def test_call_after_commit_after_transaction_commits @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} @first.save! assert_equal [:after_commit], @first.history end def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record @first.after_commit_block(:create){|r| r.history << :commit_on_create} @first.after_commit_block(:update){|r| r.history << :commit_on_update} @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} @first.after_rollback_block(:create){|r| r.history << :rollback_on_create} @first.after_rollback_block(:update){|r| r.history << :rollback_on_update} @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} @first.save! assert_equal [:commit_on_update], @first.history end def test_only_call_after_commit_on_destroy_after_transaction_commits_for_destroyed_record @first.after_commit_block(:create){|r| r.history << :commit_on_create} @first.after_commit_block(:update){|r| r.history << :commit_on_update} @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} @first.after_rollback_block(:create){|r| r.history << :rollback_on_create} @first.after_rollback_block(:update){|r| r.history << :rollback_on_update} @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} @first.destroy assert_equal [:commit_on_destroy], @first.history end def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) @new_record.after_commit_block(:create){|r| r.history << :commit_on_create} @new_record.after_commit_block(:update){|r| r.history << :commit_on_update} @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create} @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update} @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} @new_record.save! assert_equal [:commit_on_create], @new_record.history end def test_call_after_rollback_after_transaction_rollsback @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} Topic.transaction do @first.save! raise ActiveRecord::Rollback end assert_equal [:after_rollback], @first.history end def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record @first.after_commit_block(:create){|r| r.history << :commit_on_create} @first.after_commit_block(:update){|r| r.history << :commit_on_update} @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} @first.after_rollback_block(:create){|r| r.history << :rollback_on_create} @first.after_rollback_block(:update){|r| r.history << :rollback_on_update} @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} Topic.transaction do @first.save! raise ActiveRecord::Rollback end assert_equal [:rollback_on_update], @first.history end def test_only_call_after_rollback_on_destroy_after_transaction_rollsback_for_destroyed_record @first.after_commit_block(:create){|r| r.history << :commit_on_create} @first.after_commit_block(:update){|r| r.history << :commit_on_update} @first.after_commit_block(:destroy){|r| r.history << :commit_on_update} @first.after_rollback_block(:create){|r| r.history << :rollback_on_create} @first.after_rollback_block(:update){|r| r.history << :rollback_on_update} @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} Topic.transaction do @first.destroy raise ActiveRecord::Rollback end assert_equal [:rollback_on_destroy], @first.history end def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_record @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today) @new_record.after_commit_block(:create){|r| r.history << :commit_on_create} @new_record.after_commit_block(:update){|r| r.history << :commit_on_update} @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy} @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create} @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update} @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy} Topic.transaction do @new_record.save! raise ActiveRecord::Rollback end assert_equal [:rollback_on_create], @new_record.history end def test_call_after_rollback_when_commit_fails TopicWithCallbacks.connection.class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction) begin TopicWithCallbacks.connection.class.class_eval do def commit_db_transaction; raise "boom!"; end end @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} assert !@first.save rescue nil assert_equal [:after_rollback], @first.history ensure TopicWithCallbacks.connection.class.send(:remove_method, :commit_db_transaction) TopicWithCallbacks.connection.class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction) end end def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end def @first.commits(i=0); @commits ||= 0; @commits += i if i; end @first.after_rollback_block{|r| r.rollbacks(1)} @first.after_commit_block{|r| r.commits(1)} def @second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end def @second.commits(i=0); @commits ||= 0; @commits += i if i; end @second.after_rollback_block{|r| r.rollbacks(1)} @second.after_commit_block{|r| r.commits(1)} Topic.transaction do @first.save! Topic.transaction(:requires_new => true) do @second.save! raise ActiveRecord::Rollback end end assert_equal 1, @first.commits assert_equal 0, @first.rollbacks assert_equal 0, @second.commits assert_equal 1, @second.rollbacks end def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint_when_release_savepoint_fails def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end def @first.commits(i=0); @commits ||= 0; @commits += i if i; end @first.after_rollback_block{|r| r.rollbacks(1)} @first.after_commit_block{|r| r.commits(1)} Topic.transaction do @first.save Topic.transaction(:requires_new => true) do @first.save! raise ActiveRecord::Rollback end Topic.transaction(:requires_new => true) do @first.save! raise ActiveRecord::Rollback end end assert_equal 1, @first.commits assert_equal 2, @first.rollbacks end def test_after_transaction_callbacks_should_prevent_callbacks_from_being_called def @first.last_after_transaction_error=(e); @last_transaction_error = e; end def @first.last_after_transaction_error; @last_transaction_error; end @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";} @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";} @second.after_commit_block{|r| r.history << :after_commit} @second.after_rollback_block{|r| r.history << :after_rollback} Topic.transaction do @first.save! @second.save! end assert_equal :commit, @first.last_after_transaction_error assert_equal [:after_commit], @second.history @second.history.clear Topic.transaction do @first.save! @second.save! raise ActiveRecord::Rollback end assert_equal :rollback, @first.last_after_transaction_error assert_equal [:after_rollback], @second.history end end rails-observers-0.1.2/test/configuration_test.rb0000644000004100000410000000137512250222654022104 0ustar www-datawww-datarequire 'isolation/abstract_unit' require 'rails-observers' class ConfigurationTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation def setup build_app boot_rails FileUtils.rm_rf("#{app_path}/config/environments") end def teardown teardown_app end test "config.active_record.observers" do add_to_config <<-RUBY config.active_record.observers = :foo_observer RUBY app_file 'app/models/foo.rb', <<-RUBY class Foo < ActiveRecord::Base end RUBY app_file 'app/models/foo_observer.rb', <<-RUBY class FooObserver < ActiveRecord::Observer end RUBY require "#{app_path}/config/environment" _ = ActiveRecord::Base assert defined?(FooObserver) end end rails-observers-0.1.2/test/isolation/0000755000004100000410000000000012250222654017644 5ustar www-datawww-datarails-observers-0.1.2/test/isolation/abstract_unit.rb0000644000004100000410000000614612250222654023042 0ustar www-datawww-data# Note: # It is important to keep this file as light as possible # the goal for tests that require this is to test booting up # rails from an empty state, so anything added here could # hide potential failures # # It is also good to know what is the bare minimum to get # Rails booted up. require 'fileutils' require 'bundler/setup' require 'minitest/autorun' require 'active_support/test_case' # These files do not require any others and are needed # to run the tests require "active_support/testing/isolation" require "active_support/core_ext/kernel/reporting" require 'tmpdir' module TestHelpers module Paths def app_template_path File.join Dir.tmpdir, 'app_template' end def tmp_path(*args) @tmp_path ||= File.realpath(Dir.mktmpdir) File.join(@tmp_path, *args) end def app_path(*args) tmp_path(*%w[app] + args) end def rails_root app_path end end module Generation # Build an application by invoking the generator and going through the whole stack. def build_app(options = {}) @prev_rails_env = ENV['RAILS_ENV'] ENV['RAILS_ENV'] = 'development' FileUtils.rm_rf(app_path) FileUtils.cp_r(app_template_path, app_path) # Delete the initializers unless requested unless options[:initializers] Dir["#{app_path}/config/initializers/*.rb"].each do |initializer| File.delete(initializer) end end gemfile_path = "#{app_path}/Gemfile" if options[:gemfile].blank? && File.exist?(gemfile_path) File.delete gemfile_path end routes = File.read("#{app_path}/config/routes.rb") if routes =~ /(\n\s*end\s*)\Z/ File.open("#{app_path}/config/routes.rb", 'w') do |f| f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)', :via => :all\n" + $1 end end add_to_config <<-RUBY config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" config.session_store :cookie_store, :key => "_myapp_session" config.active_support.deprecation = :log config.action_controller.allow_forgery_protection = false config.eager_load = false RUBY end def teardown_app ENV['RAILS_ENV'] = @prev_rails_env if @prev_rails_env end def add_to_config(str) environment = File.read("#{app_path}/config/application.rb") if environment =~ /(\n\s*end\s*end\s*)\Z/ File.open("#{app_path}/config/application.rb", 'w') do |f| f.puts $` + "\n#{str}\n" + $1 end end end def app_file(path, contents) FileUtils.mkdir_p File.dirname("#{app_path}/#{path}") File.open("#{app_path}/#{path}", 'w') do |f| f.puts contents end end def boot_rails require 'rubygems' unless defined? Gem require 'bundler' Bundler.setup end end end class ActiveSupport::TestCase include TestHelpers::Paths include TestHelpers::Generation end Module.new do extend TestHelpers::Paths # Build a rails app FileUtils.rm_rf(app_template_path) FileUtils.mkdir(app_template_path) `rails new #{app_template_path} --skip-gemfile` end rails-observers-0.1.2/test/console_test.rb0000644000004100000410000000147312250222654020676 0ustar www-datawww-datarequire 'isolation/abstract_unit' require 'rails-observers' class ConsoleTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation def setup build_app boot_rails end def teardown teardown_app end def load_environment require "#{rails_root}/config/environment" Rails.application.sandbox = false Rails.application.load_console end def test_active_record_does_not_panic_when_referencing_an_observed_constant add_to_config "config.active_record.observers = :user_observer" app_file "app/models/user.rb", <<-MODEL class User < ActiveRecord::Base end MODEL app_file "app/models/user_observer.rb", <<-MODEL class UserObserver < ActiveRecord::Observer end MODEL load_environment assert_nothing_raised { User } end end rails-observers-0.1.2/test/observing_test.rb0000644000004100000410000001203512250222654021226 0ustar www-datawww-datarequire 'minitest/autorun' require 'active_model' require 'rails/observers/active_model/active_model' class ObservedModel include ActiveModel::Observing class Observer end end class FooObserver < ActiveModel::Observer class << self public :new end attr_accessor :stub def on_spec(record, *args) stub.event_with(record, *args) if stub end def around_save(record) yield :in_around_save end end class Foo include ActiveModel::Observing end class ObservingTest < ActiveModel::TestCase def setup ObservedModel.observers.clear end test "initializes model with no cached observers" do assert ObservedModel.observers.empty?, "Not empty: #{ObservedModel.observers.inspect}" end test "stores cached observers in an array" do ObservedModel.observers << :foo assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}" end test "flattens array of assigned cached observers" do ObservedModel.observers = [[:foo], :bar] assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}" assert ObservedModel.observers.include?(:bar), ":bar not in #{ObservedModel.observers.inspect}" end test "uses an ObserverArray so observers can be disabled" do ObservedModel.observers = [:foo, :bar] assert ObservedModel.observers.is_a?(ActiveModel::ObserverArray) end test "instantiates observer names passed as strings" do ObservedModel.observers << 'foo_observer' FooObserver.expects(:instance) ObservedModel.instantiate_observers end test "instantiates observer names passed as symbols" do ObservedModel.observers << :foo_observer FooObserver.expects(:instance) ObservedModel.instantiate_observers end test "instantiates observer classes" do ObservedModel.observers << ObservedModel::Observer ObservedModel::Observer.expects(:instance) ObservedModel.instantiate_observers end test "raises an appropriate error when a developer accidentally adds the wrong class (i.e. Widget instead of WidgetObserver)" do assert_raise ArgumentError do ObservedModel.observers = ['string'] ObservedModel.instantiate_observers end assert_raise ArgumentError do ObservedModel.observers = [:string] ObservedModel.instantiate_observers end assert_raise ArgumentError do ObservedModel.observers = [String] ObservedModel.instantiate_observers end end test "passes observers to subclasses" do FooObserver.instance bar = Class.new(Foo) assert_equal Foo.observers_count, bar.observers_count end end class ObserverTest < ActiveModel::TestCase def setup ObservedModel.observers = :foo_observer FooObserver.singleton_class.instance_eval do alias_method :original_observed_classes, :observed_classes end end def teardown FooObserver.singleton_class.instance_eval do undef_method :observed_classes alias_method :observed_classes, :original_observed_classes end end test "guesses implicit observable model name" do assert_equal Foo, FooObserver.observed_class end test "tracks implicit observable models" do instance = FooObserver.new assert_equal [Foo], instance.observed_classes end test "tracks explicit observed model class" do FooObserver.observe ObservedModel instance = FooObserver.new assert_equal [ObservedModel], instance.observed_classes end test "tracks explicit observed model as string" do FooObserver.observe 'observed_model' instance = FooObserver.new assert_equal [ObservedModel], instance.observed_classes end test "tracks explicit observed model as symbol" do FooObserver.observe :observed_model instance = FooObserver.new assert_equal [ObservedModel], instance.observed_classes end test "calls existing observer event" do foo = Foo.new FooObserver.instance.stub = stub FooObserver.instance.stub.expects(:event_with).with(foo) Foo.notify_observers(:on_spec, foo) end test "calls existing observer event from the instance" do foo = Foo.new FooObserver.instance.stub = stub FooObserver.instance.stub.expects(:event_with).with(foo) foo.notify_observers(:on_spec) end test "passes extra arguments" do foo = Foo.new FooObserver.instance.stub = stub FooObserver.instance.stub.expects(:event_with).with(foo, :bar) Foo.send(:notify_observers, :on_spec, foo, :bar) end test "skips nonexistent observer event" do foo = Foo.new Foo.notify_observers(:whatever, foo) end test "update passes a block on to the observer" do yielded_value = nil FooObserver.instance.update(:around_save, Foo.new) do |val| yielded_value = val end assert_equal :in_around_save, yielded_value end test "observe redefines observed_classes class method" do class BarObserver < ActiveModel::Observer observe :foo end assert_equal [Foo], BarObserver.observed_classes BarObserver.observe(ObservedModel) assert_equal [ObservedModel], BarObserver.observed_classes end end rails-observers-0.1.2/test/models/0000755000004100000410000000000012250222654017126 5ustar www-datawww-datarails-observers-0.1.2/test/models/observers.rb0000644000004100000410000000101612250222654021463 0ustar www-datawww-dataclass ORM include ActiveModel::Observing def save notify_observers :before_save end class Observer < ActiveModel::Observer def before_save_invocations @before_save_invocations ||= [] end def before_save(record) before_save_invocations << record end end end class Widget < ORM; end class Budget < ORM; end class WidgetObserver < ORM::Observer; end class BudgetObserver < ORM::Observer; end class AuditTrail < ORM::Observer observe :widget, :budget end ORM.instantiate_observers rails-observers-0.1.2/test/rake_test.rb0000644000004100000410000000172512250222654020156 0ustar www-datawww-datarequire 'isolation/abstract_unit' require 'rails-observers' module ApplicationTests class RakeTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation def setup build_app boot_rails FileUtils.rm_rf("#{app_path}/config/environments") end def teardown teardown_app end def test_load_activerecord_base_when_we_use_observers Dir.chdir(app_path) do `bundle exec rails g model user; bundle exec rake db:migrate; bundle exec rails g observer user;` add_to_config "config.active_record.observers = :user_observer" assert_equal "0", `bundle exec rails r "puts User.count"`.strip app_file "lib/tasks/count_user.rake", <<-RUBY namespace :user do task :count => :environment do puts User.count end end RUBY assert_equal "0", `bundle exec rake user:count`.strip end end end end rails-observers-0.1.2/.gitignore0000644000004100000410000000025012250222654016651 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp .ruby-version rails-observers-0.1.2/LICENSE0000644000004100000410000000211012250222654015663 0ustar www-datawww-dataCopyright (c) 2012 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.2/rails-observers.gemspec0000644000004100000410000000604212250222654021355 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.expand_path('../lib/rails/observers/version', __FILE__) Gem::Specification.new do |s| s.name = "rails-observers" s.authors = ["Rafael Mendonça França", "Steve Klabnik"] s.email = ["rafaelmfranca@gmail.com", "steve@steveklabnik.com"] s.description = %q{Rails observer (removed from core in Rails 4.0)} s.summary = %q{ActiveModel::Observer, ActiveRecord::Observer and ActionController::Caching::Sweeper extracted from Rails.} s.homepage = "https://github.com/rails/rails-observers" s.version = Rails::Observers::VERSION s.files = [".gitignore",".travis.yml","Gemfile","LICENSE","README.md","Rakefile","lib/generators/active_record/observer/observer_generator.rb","lib/generators/active_record/observer/templates/observer.rb","lib/generators/rails/observer/USAGE","lib/generators/rails/observer/observer_generator.rb","lib/generators/test_unit/observer/observer_generator.rb","lib/generators/test_unit/observer/templates/unit_test.rb","lib/rails-observers.rb","lib/rails/observers/action_controller/caching.rb","lib/rails/observers/action_controller/caching/sweeping.rb","lib/rails/observers/active_model.rb","lib/rails/observers/active_model/active_model.rb","lib/rails/observers/active_model/observer_array.rb","lib/rails/observers/active_model/observing.rb","lib/rails/observers/activerecord/active_record.rb","lib/rails/observers/activerecord/base.rb","lib/rails/observers/activerecord/observer.rb","lib/rails/observers/railtie.rb","lib/rails/observers/version.rb","rails-observers.gemspec","rails-observers.gemspec.erb","test/configuration_test.rb","test/console_test.rb","test/fixtures/developers.yml","test/fixtures/minimalistics.yml","test/fixtures/topics.yml","test/generators/generators_test_helper.rb","test/generators/namespaced_generators_test.rb","test/generators/observer_generator_test.rb","test/helper.rb","test/isolation/abstract_unit.rb","test/lifecycle_test.rb","test/models/observers.rb","test/observer_array_test.rb","test/observing_test.rb","test/rake_test.rb","test/sweeper_test.rb","test/transaction_callbacks_test.rb"] s.test_files = ["test/configuration_test.rb","test/console_test.rb","test/fixtures/developers.yml","test/fixtures/minimalistics.yml","test/fixtures/topics.yml","test/generators/generators_test_helper.rb","test/generators/namespaced_generators_test.rb","test/generators/observer_generator_test.rb","test/helper.rb","test/isolation/abstract_unit.rb","test/lifecycle_test.rb","test/models/observers.rb","test/observer_array_test.rb","test/observing_test.rb","test/rake_test.rb","test/sweeper_test.rb","test/transaction_callbacks_test.rb"] s.executables = [] s.require_paths = ["lib"] s.add_dependency 'activemodel', '~> 4.0' s.add_development_dependency 'minitest', '>= 3' s.add_development_dependency 'railties', '~> 4.0' s.add_development_dependency 'activerecord', '~> 4.0' s.add_development_dependency 'actionmailer', '~> 4.0' s.add_development_dependency 'actionpack', '~> 4.0' s.add_development_dependency 'sqlite3', '~> 1.3' end rails-observers-0.1.2/checksums.yaml.gz0000444000004100000410000000041412250222654020151 0ustar www-datawww-dataɳQe;R0 D"c}mIq(aԨvr~}rdžjG\€ĸG.I*<=WAfd1A€eS7tH87 UƮ(0=Ǝ3snVAsS*s3Tqrails-observers-0.1.2/README.md0000644000004100000410000000722712250222654016153 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: 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. 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. Please note that observers are called in the order that they are defined. That 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_destory` 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 Obeservers 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