├── .rspec ├── .ruby-version ├── .ruby-gemset ├── spec ├── app │ └── models │ │ ├── actress.rb │ │ ├── phone_observer.rb │ │ ├── phone.rb │ │ ├── actor.rb │ │ ├── actor_observer.rb │ │ ├── callback_recorder.rb │ │ ├── record.rb │ │ └── person.rb ├── config │ ├── mongoid_4.yml │ ├── mongoid_5.yml │ └── mongoid_6.yml ├── generators │ └── mongoid │ │ └── observer │ │ └── observer_generator_spec.rb ├── spec_helper.rb └── mongoid │ └── observer_spec.rb ├── .coveralls.yml ├── lib ├── mongoid │ ├── observers │ │ ├── version.rb │ │ ├── config.rb │ │ ├── railtie.rb │ │ └── interceptable.rb │ └── observer.rb ├── generators │ └── mongoid │ │ └── observer │ │ ├── templates │ │ └── observer.rb.tt │ │ └── observer_generator.rb └── mongoid-observers.rb ├── .gitignore ├── .travis.yml ├── gemfiles ├── mongoid_4.gemfile ├── mongoid_5.gemfile ├── mongoid_6.gemfile ├── mongoid_6.gemfile.lock ├── mongoid_5.gemfile.lock └── mongoid_4.gemfile.lock ├── Gemfile ├── Rakefile ├── mongoid-observers.gemspec ├── LICENSE.txt ├── Guardfile └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --colour -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.3.1 2 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | mongoid-observers -------------------------------------------------------------------------------- /spec/app/models/actress.rb: -------------------------------------------------------------------------------- 1 | class Actress < Actor 2 | end -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | repo_token: rr717xFYVzxOS32gtsbWXR5QaxG4CBpIr -------------------------------------------------------------------------------- /lib/mongoid/observers/version.rb: -------------------------------------------------------------------------------- 1 | module Mongoid 2 | module Observers 3 | VERSION = "0.3.0" 4 | end 5 | end -------------------------------------------------------------------------------- /lib/mongoid/observers/config.rb: -------------------------------------------------------------------------------- 1 | module Mongoid 2 | module Config 3 | include ActiveModel::Observing 4 | end 5 | end -------------------------------------------------------------------------------- /spec/config/mongoid_4.yml: -------------------------------------------------------------------------------- 1 | test: 2 | sessions: 3 | default: 4 | database: mongoid_observers_test 5 | hosts: 6 | - localhost:27017 -------------------------------------------------------------------------------- /spec/config/mongoid_5.yml: -------------------------------------------------------------------------------- 1 | test: 2 | clients: 3 | default: 4 | database: mongoid_observers_test 5 | hosts: 6 | - localhost:27017 -------------------------------------------------------------------------------- /spec/config/mongoid_6.yml: -------------------------------------------------------------------------------- 1 | test: 2 | clients: 3 | default: 4 | database: mongoid_observers_test 5 | hosts: 6 | - localhost:27017 7 | -------------------------------------------------------------------------------- /lib/generators/mongoid/observer/templates/observer.rb.tt: -------------------------------------------------------------------------------- 1 | <% module_namespacing do -%> 2 | class <%= class_name %>Observer < Mongoid::Observer 3 | end 4 | <% end -%> -------------------------------------------------------------------------------- /spec/app/models/phone_observer.rb: -------------------------------------------------------------------------------- 1 | class PhoneObserver < Mongoid::Observer 2 | 3 | def after_save(phone) 4 | phone.number_in_observer = phone.number 5 | end 6 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | spec/tmp 16 | test/tmp 17 | test/version_tmp 18 | tmp -------------------------------------------------------------------------------- /spec/app/models/phone.rb: -------------------------------------------------------------------------------- 1 | class Phone 2 | include Mongoid::Document 3 | 4 | attr_accessor :number_in_observer 5 | 6 | field :_id, type: String, default: ->{ number } 7 | 8 | field :number 9 | embeds_one :country_code 10 | embedded_in :person 11 | end -------------------------------------------------------------------------------- /spec/app/models/actor.rb: -------------------------------------------------------------------------------- 1 | class Actor 2 | include Mongoid::Document 3 | field :name 4 | field :after_custom_count, type: Integer, default: 0 5 | 6 | define_model_callbacks :custom 7 | observable :custom 8 | 9 | def do_something 10 | run_callbacks(:custom) do 11 | self.name = "custom" 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | services: mongodb 2 | language: ruby 3 | script: "bundle exec rake spec:mongoid_6" 4 | rvm: 5 | - 2.3.0 6 | env: 7 | - CODECLIMATE_REPO_TOKEN=f506e465f1ed0571aeaa5e1cb9c14214c9f7567a92d1af70903f3d0bc8015ed6 8 | notifications: 9 | email: false 10 | addons: 11 | code_climate: 12 | repo_token: f506e465f1ed0571aeaa5e1cb9c14214c9f7567a92d1af70903f3d0bc8015ed6 13 | -------------------------------------------------------------------------------- /spec/app/models/actor_observer.rb: -------------------------------------------------------------------------------- 1 | class ActorObserver < Mongoid::Observer 2 | attr_reader :last_after_create_record 3 | 4 | def after_create(record) 5 | @last_after_create_record = record 6 | end 7 | 8 | def after_custom(record) 9 | record.after_custom_count += 1 10 | end 11 | 12 | def before_custom(record) 13 | @after_custom_called = true 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/mongoid-observers.rb: -------------------------------------------------------------------------------- 1 | require "active_support" 2 | require "rails/observers/active_model" 3 | 4 | require "mongoid" 5 | require "mongoid/observers/config" 6 | require "mongoid/observers/interceptable" 7 | require "mongoid/observers/railtie" if defined? Rails 8 | require "mongoid/observer" 9 | 10 | module Mongoid 11 | include ActiveModel::Observing 12 | 13 | delegate(*ActiveModel::Observing::ClassMethods.public_instance_methods(false) << 14 | { to: Config }) 15 | end -------------------------------------------------------------------------------- /gemfiles/mongoid_4.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'mongoid', '~> 4.0.2' 4 | gem 'rails-observers', '~> 0.1.2' 5 | gem 'rake', '~> 10.4.2' 6 | gem 'rspec', '~> 3.1.0' 7 | gem 'guard-rspec', '~> 4.6.4' 8 | gem 'pry', '~> 0.9.12.6' 9 | gem 'simplecov', '~> 0.12.0' 10 | gem 'coveralls', '~> 0.8.15', require: false 11 | gem 'railties', '~> 4.2.6' 12 | gem 'ammeter', '~> 1.1.4' 13 | gem 'bundler' 14 | 15 | gemspec :path => '../' -------------------------------------------------------------------------------- /gemfiles/mongoid_5.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'mongoid', '~> 5.1.2' 4 | gem 'rails-observers', '~> 0.1.2' 5 | gem 'rake', '~> 10.4.2' 6 | gem 'rspec', '~> 3.1.0' 7 | gem 'guard-rspec', '~> 4.6.4' 8 | gem 'pry', '~> 0.9.12.6' 9 | gem 'simplecov', '~> 0.12.0' 10 | gem 'coveralls', '~> 0.8.15', require: false 11 | gem 'railties', '~> 4.2.6' 12 | gem 'ammeter', '~> 1.1.4' 13 | gem 'bundler' 14 | 15 | gemspec :path => '../' -------------------------------------------------------------------------------- /gemfiles/mongoid_6.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'mongoid', '~> 6.0.2' 4 | gem 'rails-observers', github: 'rails/rails-observers' 5 | gem 'rake', '~> 10.4.2' 6 | gem 'rspec', '~> 3.5.0' 7 | gem 'rspec-rails', '~> 3.5.2' 8 | gem 'guard-rspec', '~> 4.7.3' 9 | gem 'pry', '~> 0.9.12.6' 10 | gem 'simplecov', '~> 0.12.0' 11 | gem 'coveralls', '~> 0.8.15', require: false 12 | gem 'railties', '~> 5.0.0.1' 13 | gem 'ammeter', '~> 1.1.4' 14 | gem 'bundler' 15 | 16 | gemspec :path => '../' -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'mongoid', '~> 6.0.2' 4 | gem 'rails-observers', github: 'rails/rails-observers' 5 | gem 'rake', '~> 10.4.2' 6 | gem 'rspec', '~> 3.5.0' 7 | gem 'rspec-rails', '~> 3.5.2' 8 | gem 'guard-rspec', '~> 4.7.3' 9 | gem 'pry', '~> 0.9.12.6' 10 | gem 'simplecov', '~> 0.12.0' 11 | gem 'coveralls', '~> 0.8.15', require: false 12 | gem 'railties', '~> 5.0.0.1' 13 | gem 'ammeter', '~> 1.1.4' 14 | gem 'bundler' 15 | 16 | # Specify your gem's dependencies in mongoid-observers.gemspec 17 | gemspec -------------------------------------------------------------------------------- /lib/generators/mongoid/observer/observer_generator.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "rails/generators" 3 | require "rails/generators/mongoid_generator" 4 | 5 | module Mongoid 6 | module Generators 7 | class ObserverGenerator < Base 8 | check_class_collision suffix: "Observer" 9 | 10 | def self.source_root 11 | File.expand_path("../templates", __FILE__) 12 | end 13 | 14 | def create_observer_file 15 | template "observer.rb.tt", File.join("app/models", class_path, "#{file_name}_observer.rb") 16 | end 17 | 18 | hook_for :test_framework 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /spec/app/models/callback_recorder.rb: -------------------------------------------------------------------------------- 1 | class CallbackRecorder < Mongoid::Observer 2 | observe :actor 3 | 4 | attr_reader :last_callback, :call_count, :last_record 5 | 6 | def initialize 7 | reset 8 | super 9 | end 10 | 11 | def reset 12 | @last_callback = nil 13 | @call_count = Hash.new(0) 14 | @last_record = {} 15 | end 16 | 17 | Mongoid::Interceptable.observables.each do |callback| 18 | define_method(callback) do |record, &block| 19 | @last_callback = callback 20 | @call_count[callback] += 1 21 | @last_record[callback] = record 22 | block ? block.call : true 23 | end 24 | end 25 | end -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | require "rspec/core" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) do |spec| 7 | spec.pattern = FileList["spec/**/*_spec.rb"] 8 | end 9 | 10 | task :default => "spec:all" 11 | 12 | namespace :spec do 13 | %w(mongoid_4 mongoid_5 mongoid_6).each do |gemfile| 14 | desc "Run Tests against #{gemfile}" 15 | task gemfile do 16 | sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle install --quiet" 17 | sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle exec rake spec" 18 | end 19 | end 20 | 21 | desc "Run Tests against rails versions" 22 | task :all do 23 | %w(mongoid_4 mongoid_5 mongoid_6).each do |gemfile| 24 | sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle install --quiet" 25 | sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle exec rake spec" 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /mongoid-observers.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'mongoid/observers/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "mongoid-observers" 8 | spec.version = Mongoid::Observers::VERSION 9 | spec.authors = ["Chamnap Chhorn"] 10 | spec.email = ["chamnapchhorn@gmail.com"] 11 | spec.summary = %q{Mongoid observer (removed in Mongoid 4.0)} 12 | spec.description = %q{Mongoid::Observer removed from Mongoid 4.0} 13 | spec.homepage = "https://github.com/chamnap/mongoid-observers" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency "mongoid", ">= 4.0.0" 22 | end 23 | -------------------------------------------------------------------------------- /spec/generators/mongoid/observer/observer_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | # Generators are not automatically loaded by Rails 4 | require "generators/mongoid/observer/observer_generator" 5 | 6 | describe Mongoid::Generators::ObserverGenerator do 7 | # Tell the generator where to put its output (what it thinks of as Rails.root) 8 | destination File.expand_path("../../../../../spec/tmp", __FILE__) 9 | 10 | before do 11 | prepare_destination 12 | end 13 | 14 | it "observer without namespace" do 15 | run_generator %w(product) 16 | expect(file("app/models/product_observer.rb")).to exist 17 | expect(file("app/models/product_observer.rb")).to contain(/class ProductObserver < Mongoid::Observer/) 18 | end 19 | 20 | it "observer with namespace" do 21 | run_generator %w(admin/account) 22 | expect(file("app/models/admin/account_observer.rb")).to exist 23 | expect(file("app/models/admin/account_observer.rb")).to contain(/class Admin::AccountObserver < Mongoid::Observer/) 24 | end 25 | end -------------------------------------------------------------------------------- /lib/mongoid/observers/railtie.rb: -------------------------------------------------------------------------------- 1 | require "rails/railtie" 2 | 3 | module Mongoid 4 | module Observers 5 | class Railtie < ::Rails::Railtie 6 | initializer "mongoid.observer" do |app| 7 | ActiveSupport.on_load(:mongoid) do 8 | if app.config.respond_to?(:mongoid) 9 | ::Mongoid.observers = app.config.mongoid.observers 10 | end 11 | end 12 | end 13 | 14 | # Instantitate any registered observers after Rails initialization and 15 | # instantiate them after being reloaded in the development environment 16 | config.after_initialize do |app| 17 | ActiveSupport.on_load(:mongoid) do 18 | ::Mongoid::instantiate_observers 19 | 20 | # DEPRECATION WARNING: to_prepare on ActionDispatch::Reloader is deprecated and will be removed from Rails 5.1 21 | constant = defined?(ActiveSupport::Reloader) ? ActiveSupport::Reloader : ActionDispatch::Reloader 22 | constant.to_prepare do 23 | ::Mongoid.instantiate_observers 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Chamnap Chhorn 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /spec/app/models/record.rb: -------------------------------------------------------------------------------- 1 | class Record 2 | include Mongoid::Document 3 | field :name, type: String 4 | 5 | field :before_create_called, type: Boolean, default: false 6 | field :before_save_called, type: Boolean, default: false 7 | field :before_update_called, type: Boolean, default: false 8 | field :before_validation_called, type: Boolean, default: false 9 | field :before_destroy_called, type: Boolean, default: false 10 | 11 | before_create :before_create_stub 12 | before_save :before_save_stub 13 | before_update :before_update_stub 14 | before_validation :before_validation_stub 15 | before_destroy :before_destroy_stub 16 | 17 | after_destroy :access_band 18 | 19 | def before_create_stub 20 | self.before_create_called = true 21 | end 22 | 23 | def before_save_stub 24 | self.before_save_called = true 25 | end 26 | 27 | def before_update_stub 28 | self.before_update_called = true 29 | end 30 | 31 | def before_validation_stub 32 | self.before_validation_called = true 33 | end 34 | 35 | def before_destroy_stub 36 | self.before_destroy_called = true 37 | end 38 | 39 | def access_band 40 | band.name 41 | end 42 | end -------------------------------------------------------------------------------- /spec/app/models/person.rb: -------------------------------------------------------------------------------- 1 | class Person 2 | include Mongoid::Document 3 | 4 | field :username, default: -> { "arthurnn#{rand(0..10)}" } 5 | field :title 6 | field :terms, type: Boolean 7 | field :pets, type: Boolean, default: false 8 | field :age, type: Integer, default: "100" 9 | field :dob, type: Date 10 | field :employer_id 11 | field :lunch_time, type: Time 12 | field :aliases, type: Array 13 | field :map, type: Hash 14 | field :map_with_default, type: Hash, default: {} 15 | field :score, type: Integer 16 | field :blood_alcohol_content, type: Float, default: ->{ 0.0 } 17 | field :last_drink_taken_at, type: Date, default: ->{ 1.day.ago.in_time_zone("Alaska") } 18 | field :ssn 19 | field :owner_id, type: Integer 20 | field :security_code 21 | field :reading, type: Object 22 | field :bson_id, type: BSON::ObjectId 23 | field :pattern, type: Regexp 24 | field :override_me, type: Integer 25 | field :at, as: :aliased_timestamp, type: Time 26 | field :t, as: :test, type: String 27 | field :i, as: :inte, type: Integer 28 | field :a, as: :array, type: Array 29 | field :desc, localize: true 30 | 31 | 32 | embeds_many :phone_numbers, class_name: "Phone", validate: false 33 | 34 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "simplecov" 2 | require "coveralls" 3 | 4 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ 5 | Coveralls::SimpleCov::Formatter, 6 | SimpleCov::Formatter::HTMLFormatter 7 | ]) 8 | 9 | SimpleCov.start do 10 | add_filter "/spec/" 11 | end 12 | 13 | MODELS = File.join(File.dirname(__FILE__), "app/models") 14 | $LOAD_PATH.unshift(MODELS) 15 | 16 | require "pry" 17 | require "rails/all" 18 | require "mongoid-observers" 19 | require "ammeter/init" 20 | 21 | # mongoid connection 22 | Mongoid.logger.level = Logger::INFO 23 | 24 | if Mongoid::VERSION.start_with?('4.') 25 | Mongoid.load! File.dirname(__FILE__) + "/config/mongoid_4.yml", :test 26 | elsif Mongoid::VERSION.start_with?('5.') 27 | Mongoid.load! File.dirname(__FILE__) + "/config/mongoid_5.yml", :test 28 | elsif Mongoid::VERSION.start_with?('6.') 29 | Mongoid.load! File.dirname(__FILE__) + "/config/mongoid_6.yml", :test 30 | end 31 | 32 | # Autoload every model for the test suite that sits in spec/app/models. 33 | Dir[ File.join(MODELS, "*.rb") ].sort.each do |file| 34 | name = File.basename(file, ".rb") 35 | autoload name.camelize.to_sym, name 36 | end 37 | 38 | RSpec.configure do |config| 39 | config.filter_run focus: true 40 | config.run_all_when_everything_filtered = true 41 | 42 | config.before(:each) do 43 | Mongoid.purge! 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/mongoid/observers/interceptable.rb: -------------------------------------------------------------------------------- 1 | module Mongoid 2 | module Interceptable 3 | include ActiveModel::Observing 4 | 5 | class << self 6 | 7 | # Get all callbacks that can be observed. 8 | # 9 | # @example Get the observables. 10 | # Interceptable.observables 11 | # 12 | # @return [ Array ] The names of the observables. 13 | def observables 14 | CALLBACKS + registered_observables 15 | end 16 | 17 | # Get all registered callbacks that can be observed, not included in 18 | # Mongoid's defaults. 19 | # 20 | # @example Get the observables. 21 | # Interceptable.registered_observables 22 | # 23 | # @return [ Array ] The names of the registered observables. 24 | def registered_observables 25 | @registered_observables ||= [] 26 | end 27 | end 28 | 29 | module ClassMethods 30 | 31 | # Set a custom callback as able to be observed. 32 | # 33 | # @example Set a custom callback as observable. 34 | # class Band 35 | # include Mongoid::Document 36 | # 37 | # define_model_callbacks :notification 38 | # observable :notification 39 | # end 40 | # 41 | # @param [ Array ] args The names of the observable callbacks. 42 | # 43 | # @since 3.0.1 44 | def observable(*args) 45 | observables = args.flat_map do |name| 46 | [ :"before_#{name}", :"after_#{name}", :"around_#{name}" ] 47 | end 48 | Interceptable.registered_observables.concat(observables).uniq 49 | end 50 | end 51 | end 52 | end -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | ## Uncomment and set this to only include directories you want to watch 5 | # directories %w(app lib config test spec feature) 6 | 7 | ## Uncomment to clear the screen before every task 8 | # clearing :on 9 | 10 | ## Guard internally checks for changes in the Guardfile and exits. 11 | ## If you want Guard to automatically start up again, run guard in a 12 | ## shell loop, e.g.: 13 | ## 14 | ## $ while bundle exec guard; do echo "Restarting Guard..."; done 15 | ## 16 | ## Note: if you are using the `directories` clause above and you are not 17 | ## watching the project directory ('.'), the you will want to move the Guardfile 18 | ## to a watched dir and symlink it back, e.g. 19 | # 20 | # $ mkdir config 21 | # $ mv Guardfile config/ 22 | # $ ln -s config/Guardfile . 23 | # 24 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 25 | 26 | # Note: The cmd option is now required due to the increasing number of ways 27 | # rspec may be run, below are examples of the most common uses. 28 | # * bundler: 'bundle exec rspec' 29 | # * bundler binstubs: 'bin/rspec' 30 | # * spring: 'bin/rspec' (This will use spring if running and you have 31 | # installed the spring binstubs per the docs) 32 | # * zeus: 'zeus rspec' (requires the server to be started separately) 33 | # * 'just' rspec: 'rspec' 34 | 35 | guard :rspec, cmd: "bundle exec rspec" do 36 | require "guard/rspec/dsl" 37 | dsl = Guard::RSpec::Dsl.new(self) 38 | 39 | # Feel free to open issues for suggestions and improvements 40 | 41 | # RSpec files 42 | rspec = dsl.rspec 43 | watch(rspec.spec_helper) { rspec.spec_dir } 44 | watch(rspec.spec_support) { rspec.spec_dir } 45 | watch(rspec.spec_files) 46 | 47 | # Ruby files 48 | ruby = dsl.ruby 49 | dsl.watch_spec_files_for(ruby.lib_files) 50 | 51 | # Rails files 52 | rails = dsl.rails(view_extensions: %w(erb haml slim)) 53 | dsl.watch_spec_files_for(rails.app_files) 54 | dsl.watch_spec_files_for(rails.views) 55 | 56 | watch(rails.controllers) do |m| 57 | [ 58 | rspec.spec.("routing/#{m[1]}_routing"), 59 | rspec.spec.("controllers/#{m[1]}_controller"), 60 | rspec.spec.("acceptance/#{m[1]}") 61 | ] 62 | end 63 | 64 | # Rails config changes 65 | watch(rails.spec_helper) { rspec.spec_dir } 66 | watch(rails.routes) { "#{rspec.spec_dir}/routing" } 67 | watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } 68 | 69 | # Capybara features specs 70 | watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") } 71 | 72 | # Turnip features and steps 73 | watch(%r{^spec/acceptance/(.+)\.feature$}) 74 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m| 75 | Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance" 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /gemfiles/mongoid_6.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/rails/rails-observers.git 3 | revision: 3fe157d6cbb5b5e767ded248009fc59443d63fa1 4 | specs: 5 | rails-observers (0.1.3.alpha) 6 | activemodel (>= 4.0, < 5.1) 7 | 8 | PATH 9 | remote: ../ 10 | specs: 11 | mongoid-observers (0.3.0) 12 | mongoid (>= 4.0.0) 13 | 14 | GEM 15 | remote: https://rubygems.org/ 16 | specs: 17 | actionpack (5.0.0.1) 18 | actionview (= 5.0.0.1) 19 | activesupport (= 5.0.0.1) 20 | rack (~> 2.0) 21 | rack-test (~> 0.6.3) 22 | rails-dom-testing (~> 2.0) 23 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 24 | actionview (5.0.0.1) 25 | activesupport (= 5.0.0.1) 26 | builder (~> 3.1) 27 | erubis (~> 2.7.0) 28 | rails-dom-testing (~> 2.0) 29 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 30 | activemodel (5.0.0.1) 31 | activesupport (= 5.0.0.1) 32 | activesupport (5.0.0.1) 33 | concurrent-ruby (~> 1.0, >= 1.0.2) 34 | i18n (~> 0.7) 35 | minitest (~> 5.1) 36 | tzinfo (~> 1.1) 37 | ammeter (1.1.4) 38 | activesupport (>= 3.0) 39 | railties (>= 3.0) 40 | rspec-rails (>= 2.2) 41 | bson (4.1.1) 42 | builder (3.2.2) 43 | coderay (1.1.1) 44 | concurrent-ruby (1.0.2) 45 | coveralls (0.8.15) 46 | json (>= 1.8, < 3) 47 | simplecov (~> 0.12.0) 48 | term-ansicolor (~> 1.3) 49 | thor (~> 0.19.1) 50 | tins (>= 1.6.0, < 2) 51 | diff-lcs (1.2.5) 52 | docile (1.1.5) 53 | erubis (2.7.0) 54 | ffi (1.9.14) 55 | formatador (0.2.5) 56 | guard (2.14.0) 57 | formatador (>= 0.2.4) 58 | listen (>= 2.7, < 4.0) 59 | lumberjack (~> 1.0) 60 | nenv (~> 0.1) 61 | notiffany (~> 0.0) 62 | pry (>= 0.9.12) 63 | shellany (~> 0.0) 64 | thor (>= 0.18.1) 65 | guard-compat (1.2.1) 66 | guard-rspec (4.7.3) 67 | guard (~> 2.1) 68 | guard-compat (~> 1.1) 69 | rspec (>= 2.99.0, < 4.0) 70 | i18n (0.7.0) 71 | json (2.0.2) 72 | listen (3.1.5) 73 | rb-fsevent (~> 0.9, >= 0.9.4) 74 | rb-inotify (~> 0.9, >= 0.9.7) 75 | ruby_dep (~> 1.2) 76 | loofah (2.0.3) 77 | nokogiri (>= 1.5.9) 78 | lumberjack (1.0.10) 79 | method_source (0.8.2) 80 | mini_portile2 (2.1.0) 81 | minitest (5.9.1) 82 | mongo (2.3.0) 83 | bson (~> 4.1) 84 | mongoid (6.0.2) 85 | activemodel (~> 5.0) 86 | mongo (~> 2.3) 87 | nenv (0.3.0) 88 | nokogiri (1.6.8.1) 89 | mini_portile2 (~> 2.1.0) 90 | notiffany (0.1.1) 91 | nenv (~> 0.1) 92 | shellany (~> 0.0) 93 | pry (0.9.12.6) 94 | coderay (~> 1.0) 95 | method_source (~> 0.8) 96 | slop (~> 3.4) 97 | rack (2.0.1) 98 | rack-test (0.6.3) 99 | rack (>= 1.0) 100 | rails-dom-testing (2.0.1) 101 | activesupport (>= 4.2.0, < 6.0) 102 | nokogiri (~> 1.6.0) 103 | rails-html-sanitizer (1.0.3) 104 | loofah (~> 2.0) 105 | railties (5.0.0.1) 106 | actionpack (= 5.0.0.1) 107 | activesupport (= 5.0.0.1) 108 | method_source 109 | rake (>= 0.8.7) 110 | thor (>= 0.18.1, < 2.0) 111 | rake (10.4.2) 112 | rb-fsevent (0.9.8) 113 | rb-inotify (0.9.7) 114 | ffi (>= 0.5.0) 115 | rspec (3.5.0) 116 | rspec-core (~> 3.5.0) 117 | rspec-expectations (~> 3.5.0) 118 | rspec-mocks (~> 3.5.0) 119 | rspec-core (3.5.4) 120 | rspec-support (~> 3.5.0) 121 | rspec-expectations (3.5.0) 122 | diff-lcs (>= 1.2.0, < 2.0) 123 | rspec-support (~> 3.5.0) 124 | rspec-mocks (3.5.0) 125 | diff-lcs (>= 1.2.0, < 2.0) 126 | rspec-support (~> 3.5.0) 127 | rspec-rails (3.5.2) 128 | actionpack (>= 3.0) 129 | activesupport (>= 3.0) 130 | railties (>= 3.0) 131 | rspec-core (~> 3.5.0) 132 | rspec-expectations (~> 3.5.0) 133 | rspec-mocks (~> 3.5.0) 134 | rspec-support (~> 3.5.0) 135 | rspec-support (3.5.0) 136 | ruby_dep (1.5.0) 137 | shellany (0.0.1) 138 | simplecov (0.12.0) 139 | docile (~> 1.1.0) 140 | json (>= 1.8, < 3) 141 | simplecov-html (~> 0.10.0) 142 | simplecov-html (0.10.0) 143 | slop (3.6.0) 144 | term-ansicolor (1.4.0) 145 | tins (~> 1.0) 146 | thor (0.19.1) 147 | thread_safe (0.3.5) 148 | tins (1.12.0) 149 | tzinfo (1.2.2) 150 | thread_safe (~> 0.1) 151 | 152 | PLATFORMS 153 | ruby 154 | 155 | DEPENDENCIES 156 | ammeter (~> 1.1.4) 157 | bundler 158 | coveralls (~> 0.8.15) 159 | guard-rspec (~> 4.7.3) 160 | mongoid (~> 6.0.2) 161 | mongoid-observers! 162 | pry (~> 0.9.12.6) 163 | rails-observers! 164 | railties (~> 5.0.0.1) 165 | rake (~> 10.4.2) 166 | rspec (~> 3.5.0) 167 | rspec-rails (~> 3.5.2) 168 | simplecov (~> 0.12.0) 169 | 170 | BUNDLED WITH 171 | 1.13.6 172 | -------------------------------------------------------------------------------- /gemfiles/mongoid_5.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../ 3 | specs: 4 | mongoid-observers (0.3.0) 5 | mongoid (>= 4.0.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actionpack (4.2.7.1) 11 | actionview (= 4.2.7.1) 12 | activesupport (= 4.2.7.1) 13 | rack (~> 1.6) 14 | rack-test (~> 0.6.2) 15 | rails-dom-testing (~> 1.0, >= 1.0.5) 16 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 17 | actionview (4.2.7.1) 18 | activesupport (= 4.2.7.1) 19 | builder (~> 3.1) 20 | erubis (~> 2.7.0) 21 | rails-dom-testing (~> 1.0, >= 1.0.5) 22 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 23 | activemodel (4.2.7.1) 24 | activesupport (= 4.2.7.1) 25 | builder (~> 3.1) 26 | activesupport (4.2.7.1) 27 | i18n (~> 0.7) 28 | json (~> 1.7, >= 1.7.7) 29 | minitest (~> 5.1) 30 | thread_safe (~> 0.3, >= 0.3.4) 31 | tzinfo (~> 1.1) 32 | ammeter (1.1.4) 33 | activesupport (>= 3.0) 34 | railties (>= 3.0) 35 | rspec-rails (>= 2.2) 36 | bson (4.1.1) 37 | builder (3.2.2) 38 | coderay (1.1.1) 39 | coveralls (0.8.15) 40 | json (>= 1.8, < 3) 41 | simplecov (~> 0.12.0) 42 | term-ansicolor (~> 1.3) 43 | thor (~> 0.19.1) 44 | tins (>= 1.6.0, < 2) 45 | diff-lcs (1.2.5) 46 | docile (1.1.5) 47 | erubis (2.7.0) 48 | ffi (1.9.14) 49 | formatador (0.2.5) 50 | guard (2.14.0) 51 | formatador (>= 0.2.4) 52 | listen (>= 2.7, < 4.0) 53 | lumberjack (~> 1.0) 54 | nenv (~> 0.1) 55 | notiffany (~> 0.0) 56 | pry (>= 0.9.12) 57 | shellany (~> 0.0) 58 | thor (>= 0.18.1) 59 | guard-compat (1.2.1) 60 | guard-rspec (4.6.5) 61 | guard (~> 2.1) 62 | guard-compat (~> 1.1) 63 | rspec (>= 2.99.0, < 4.0) 64 | i18n (0.7.0) 65 | json (1.8.3) 66 | listen (3.1.5) 67 | rb-fsevent (~> 0.9, >= 0.9.4) 68 | rb-inotify (~> 0.9, >= 0.9.7) 69 | ruby_dep (~> 1.2) 70 | loofah (2.0.3) 71 | nokogiri (>= 1.5.9) 72 | lumberjack (1.0.10) 73 | method_source (0.8.2) 74 | mini_portile2 (2.1.0) 75 | minitest (5.9.1) 76 | mongo (2.3.0) 77 | bson (~> 4.1) 78 | mongoid (5.1.5) 79 | activemodel (~> 4.0) 80 | mongo (~> 2.1) 81 | origin (~> 2.2) 82 | tzinfo (>= 0.3.37) 83 | nenv (0.3.0) 84 | nokogiri (1.6.8.1) 85 | mini_portile2 (~> 2.1.0) 86 | notiffany (0.1.1) 87 | nenv (~> 0.1) 88 | shellany (~> 0.0) 89 | origin (2.2.2) 90 | pry (0.9.12.6) 91 | coderay (~> 1.0) 92 | method_source (~> 0.8) 93 | slop (~> 3.4) 94 | rack (1.6.4) 95 | rack-test (0.6.3) 96 | rack (>= 1.0) 97 | rails-deprecated_sanitizer (1.0.3) 98 | activesupport (>= 4.2.0.alpha) 99 | rails-dom-testing (1.0.7) 100 | activesupport (>= 4.2.0.beta, < 5.0) 101 | nokogiri (~> 1.6.0) 102 | rails-deprecated_sanitizer (>= 1.0.1) 103 | rails-html-sanitizer (1.0.3) 104 | loofah (~> 2.0) 105 | rails-observers (0.1.2) 106 | activemodel (~> 4.0) 107 | railties (4.2.7.1) 108 | actionpack (= 4.2.7.1) 109 | activesupport (= 4.2.7.1) 110 | rake (>= 0.8.7) 111 | thor (>= 0.18.1, < 2.0) 112 | rake (10.4.2) 113 | rb-fsevent (0.9.8) 114 | rb-inotify (0.9.7) 115 | ffi (>= 0.5.0) 116 | rspec (3.1.0) 117 | rspec-core (~> 3.1.0) 118 | rspec-expectations (~> 3.1.0) 119 | rspec-mocks (~> 3.1.0) 120 | rspec-core (3.1.7) 121 | rspec-support (~> 3.1.0) 122 | rspec-expectations (3.1.2) 123 | diff-lcs (>= 1.2.0, < 2.0) 124 | rspec-support (~> 3.1.0) 125 | rspec-mocks (3.1.3) 126 | rspec-support (~> 3.1.0) 127 | rspec-rails (3.1.0) 128 | actionpack (>= 3.0) 129 | activesupport (>= 3.0) 130 | railties (>= 3.0) 131 | rspec-core (~> 3.1.0) 132 | rspec-expectations (~> 3.1.0) 133 | rspec-mocks (~> 3.1.0) 134 | rspec-support (~> 3.1.0) 135 | rspec-support (3.1.2) 136 | ruby_dep (1.5.0) 137 | shellany (0.0.1) 138 | simplecov (0.12.0) 139 | docile (~> 1.1.0) 140 | json (>= 1.8, < 3) 141 | simplecov-html (~> 0.10.0) 142 | simplecov-html (0.10.0) 143 | slop (3.6.0) 144 | term-ansicolor (1.4.0) 145 | tins (~> 1.0) 146 | thor (0.19.1) 147 | thread_safe (0.3.5) 148 | tins (1.12.0) 149 | tzinfo (1.2.2) 150 | thread_safe (~> 0.1) 151 | 152 | PLATFORMS 153 | ruby 154 | 155 | DEPENDENCIES 156 | ammeter (~> 1.1.4) 157 | bundler 158 | coveralls (~> 0.8.15) 159 | guard-rspec (~> 4.6.4) 160 | mongoid (~> 5.1.2) 161 | mongoid-observers! 162 | pry (~> 0.9.12.6) 163 | rails-observers (~> 0.1.2) 164 | railties (~> 4.2.6) 165 | rake (~> 10.4.2) 166 | rspec (~> 3.1.0) 167 | simplecov (~> 0.12.0) 168 | 169 | BUNDLED WITH 170 | 1.13.6 171 | -------------------------------------------------------------------------------- /gemfiles/mongoid_4.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../ 3 | specs: 4 | mongoid-observers (0.3.0) 5 | mongoid (>= 4.0.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actionpack (4.2.7.1) 11 | actionview (= 4.2.7.1) 12 | activesupport (= 4.2.7.1) 13 | rack (~> 1.6) 14 | rack-test (~> 0.6.2) 15 | rails-dom-testing (~> 1.0, >= 1.0.5) 16 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 17 | actionview (4.2.7.1) 18 | activesupport (= 4.2.7.1) 19 | builder (~> 3.1) 20 | erubis (~> 2.7.0) 21 | rails-dom-testing (~> 1.0, >= 1.0.5) 22 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 23 | activemodel (4.2.7.1) 24 | activesupport (= 4.2.7.1) 25 | builder (~> 3.1) 26 | activesupport (4.2.7.1) 27 | i18n (~> 0.7) 28 | json (~> 1.7, >= 1.7.7) 29 | minitest (~> 5.1) 30 | thread_safe (~> 0.3, >= 0.3.4) 31 | tzinfo (~> 1.1) 32 | ammeter (1.1.4) 33 | activesupport (>= 3.0) 34 | railties (>= 3.0) 35 | rspec-rails (>= 2.2) 36 | bson (3.2.6) 37 | builder (3.2.2) 38 | coderay (1.1.1) 39 | connection_pool (2.2.0) 40 | coveralls (0.8.15) 41 | json (>= 1.8, < 3) 42 | simplecov (~> 0.12.0) 43 | term-ansicolor (~> 1.3) 44 | thor (~> 0.19.1) 45 | tins (>= 1.6.0, < 2) 46 | diff-lcs (1.2.5) 47 | docile (1.1.5) 48 | erubis (2.7.0) 49 | ffi (1.9.14) 50 | formatador (0.2.5) 51 | guard (2.14.0) 52 | formatador (>= 0.2.4) 53 | listen (>= 2.7, < 4.0) 54 | lumberjack (~> 1.0) 55 | nenv (~> 0.1) 56 | notiffany (~> 0.0) 57 | pry (>= 0.9.12) 58 | shellany (~> 0.0) 59 | thor (>= 0.18.1) 60 | guard-compat (1.2.1) 61 | guard-rspec (4.6.5) 62 | guard (~> 2.1) 63 | guard-compat (~> 1.1) 64 | rspec (>= 2.99.0, < 4.0) 65 | i18n (0.7.0) 66 | json (1.8.3) 67 | listen (3.1.5) 68 | rb-fsevent (~> 0.9, >= 0.9.4) 69 | rb-inotify (~> 0.9, >= 0.9.7) 70 | ruby_dep (~> 1.2) 71 | loofah (2.0.3) 72 | nokogiri (>= 1.5.9) 73 | lumberjack (1.0.10) 74 | method_source (0.8.2) 75 | mini_portile2 (2.1.0) 76 | minitest (5.9.1) 77 | mongoid (4.0.2) 78 | activemodel (~> 4.0) 79 | moped (~> 2.0.0) 80 | origin (~> 2.1) 81 | tzinfo (>= 0.3.37) 82 | moped (2.0.7) 83 | bson (~> 3.0) 84 | connection_pool (~> 2.0) 85 | optionable (~> 0.2.0) 86 | nenv (0.3.0) 87 | nokogiri (1.6.8.1) 88 | mini_portile2 (~> 2.1.0) 89 | notiffany (0.1.1) 90 | nenv (~> 0.1) 91 | shellany (~> 0.0) 92 | optionable (0.2.0) 93 | origin (2.2.2) 94 | pry (0.9.12.6) 95 | coderay (~> 1.0) 96 | method_source (~> 0.8) 97 | slop (~> 3.4) 98 | rack (1.6.4) 99 | rack-test (0.6.3) 100 | rack (>= 1.0) 101 | rails-deprecated_sanitizer (1.0.3) 102 | activesupport (>= 4.2.0.alpha) 103 | rails-dom-testing (1.0.7) 104 | activesupport (>= 4.2.0.beta, < 5.0) 105 | nokogiri (~> 1.6.0) 106 | rails-deprecated_sanitizer (>= 1.0.1) 107 | rails-html-sanitizer (1.0.3) 108 | loofah (~> 2.0) 109 | rails-observers (0.1.2) 110 | activemodel (~> 4.0) 111 | railties (4.2.7.1) 112 | actionpack (= 4.2.7.1) 113 | activesupport (= 4.2.7.1) 114 | rake (>= 0.8.7) 115 | thor (>= 0.18.1, < 2.0) 116 | rake (10.4.2) 117 | rb-fsevent (0.9.8) 118 | rb-inotify (0.9.7) 119 | ffi (>= 0.5.0) 120 | rspec (3.1.0) 121 | rspec-core (~> 3.1.0) 122 | rspec-expectations (~> 3.1.0) 123 | rspec-mocks (~> 3.1.0) 124 | rspec-core (3.1.7) 125 | rspec-support (~> 3.1.0) 126 | rspec-expectations (3.1.2) 127 | diff-lcs (>= 1.2.0, < 2.0) 128 | rspec-support (~> 3.1.0) 129 | rspec-mocks (3.1.3) 130 | rspec-support (~> 3.1.0) 131 | rspec-rails (3.1.0) 132 | actionpack (>= 3.0) 133 | activesupport (>= 3.0) 134 | railties (>= 3.0) 135 | rspec-core (~> 3.1.0) 136 | rspec-expectations (~> 3.1.0) 137 | rspec-mocks (~> 3.1.0) 138 | rspec-support (~> 3.1.0) 139 | rspec-support (3.1.2) 140 | ruby_dep (1.5.0) 141 | shellany (0.0.1) 142 | simplecov (0.12.0) 143 | docile (~> 1.1.0) 144 | json (>= 1.8, < 3) 145 | simplecov-html (~> 0.10.0) 146 | simplecov-html (0.10.0) 147 | slop (3.6.0) 148 | term-ansicolor (1.4.0) 149 | tins (~> 1.0) 150 | thor (0.19.1) 151 | thread_safe (0.3.5) 152 | tins (1.12.0) 153 | tzinfo (1.2.2) 154 | thread_safe (~> 0.1) 155 | 156 | PLATFORMS 157 | ruby 158 | 159 | DEPENDENCIES 160 | ammeter (~> 1.1.4) 161 | bundler 162 | coveralls (~> 0.8.15) 163 | guard-rspec (~> 4.6.4) 164 | mongoid (~> 4.0.2) 165 | mongoid-observers! 166 | pry (~> 0.9.12.6) 167 | rails-observers (~> 0.1.2) 168 | railties (~> 4.2.6) 169 | rake (~> 10.4.2) 170 | rspec (~> 3.1.0) 171 | simplecov (~> 0.12.0) 172 | 173 | BUNDLED WITH 174 | 1.13.6 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mongoid::Observers [![Build Status](https://travis-ci.org/chamnap/mongoid-observers.svg?branch=master)](https://travis-ci.org/chamnap/mongoid-observers)[![Code Climate](https://codeclimate.com/github/chamnap/mongoid-observers.png)](https://codeclimate.com/github/chamnap/mongoid-observers)[![Coverage Status](https://coveralls.io/repos/chamnap/mongoid-observers/badge.png?branch=master)](https://coveralls.io/r/chamnap/mongoid-observers?branch=master)[![Dependency Status](https://gemnasium.com/chamnap/mongoid-observers.svg)](https://gemnasium.com/chamnap/mongoid-observers) 2 | 3 | Mongoid Observers (removed from core in Mongoid 4.0). Because this gem doesn't exist and I need to use it very often. Therefore, I extract the code from mongoid on my own. It's basically the same code from mongoid before it's removed. 4 | 5 | ## Installation 6 | 7 | For Rails 5+, Add this line to your application's Gemfile: 8 | 9 | gem 'mongoid-observers', '~> 0.3.0' 10 | gem 'rails-observers', github: 'rails/rails-observers' 11 | 12 | **NOTE:** `mongoid-observers` depends on `rails-observers` mostly, but it is not yet ready for a new release on Rails 5 yet, https://github.com/rails/rails-observers/issues/53. 13 | 14 | For Rails 4 and below, Add this line to your application's Gemfile: 15 | 16 | gem 'mongoid-observers', '~> 0.2.0' 17 | 18 | And then execute: 19 | 20 | $ bundle 21 | 22 | ## Usage 23 | 24 | Observer classes respond to life cycle callbacks to implement trigger-like 25 | behavior outside the original class. This is a great way to reduce the 26 | clutter that normally comes when the model class is burdened with 27 | functionality that doesn't pertain to the core responsibility of the 28 | class. Mongoid's observers work similar to ActiveRecord's. Example: 29 | 30 | ```ruby 31 | class CommentObserver < Mongoid::Observer 32 | def after_save(comment) 33 | Notifications.comment( 34 | "admin@do.com", "New comment was posted", comment 35 | ).deliver 36 | end 37 | end 38 | ``` 39 | 40 | This Observer sends an email when a Comment#save is finished. 41 | 42 | ```ruby 43 | class ContactObserver < Mongoid::Observer 44 | def after_create(contact) 45 | contact.logger.info('New contact added!') 46 | end 47 | 48 | def after_destroy(contact) 49 | contact.logger.warn("Contact with an id of #{contact.id} was destroyed!") 50 | end 51 | end 52 | ``` 53 | 54 | This Observer uses logger to log when specific callbacks are triggered. 55 | 56 | #### Observing a class that can't be inferred 57 | 58 | Observers will by default be mapped to the class with which they share a 59 | name. So CommentObserver will be tied to observing Comment, 60 | ProductManagerObserver to ProductManager, and so on. If you want to 61 | name your observer differently than the class you're interested in 62 | observing, you can use the Observer.observe class method which takes 63 | either the concrete class (Product) or a symbol for that class (:product): 64 | 65 | ```ruby 66 | class AuditObserver < Mongoid::Observer 67 | observe :account 68 | 69 | def after_update(account) 70 | AuditTrail.new(account, "UPDATED") 71 | end 72 | end 73 | ``` 74 | 75 | If the audit observer needs to watch more than one kind of object, 76 | this can be specified with multiple arguments: 77 | 78 | ```ruby 79 | class AuditObserver < Mongoid::Observer 80 | observe :account, :balance 81 | 82 | def after_update(record) 83 | AuditTrail.new(record, "UPDATED") 84 | end 85 | end 86 | ``` 87 | 88 | The AuditObserver will now act on both updates to Account and Balance 89 | by treating them both as records. 90 | 91 | #### Available callback methods 92 | 93 | * after_initialize 94 | * before_validation 95 | * after_validation 96 | * before_create 97 | * around_create 98 | * after_create 99 | * before_update 100 | * around_update 101 | * after_update 102 | * before_upsert 103 | * around_upsert 104 | * after_upsert 105 | * before_save 106 | * around_save 107 | * after_save 108 | * before_destroy 109 | * around_destroy 110 | * after_destroy 111 | 112 | #### Storing Observers in Rails 113 | 114 | If you're using Mongoid within Rails, observer classes are usually stored 115 | in `app/models` with the naming convention of `app/models/audit_observer.rb`. 116 | 117 | #### Configuration 118 | 119 | In order to activate an observer, list it in the `config.mongoid.observers` 120 | configuration setting in your `config/application.rb` file. 121 | 122 | ```ruby 123 | config.mongoid.observers = :comment_observer, :signup_observer 124 | ``` 125 | 126 | Observers will not be invoked unless you define them in your 127 | application configuration. 128 | 129 | #### Loading 130 | 131 | Observers register themselves with the model class that they observe, 132 | since it is the class that notifies them of events when they occur. 133 | As a side-effect, when an observer is loaded, its corresponding model 134 | class is loaded. 135 | 136 | Observers are loaded after the application initializers, so that 137 | observed models can make use of extensions. If by any chance you are 138 | using observed models in the initialization, you can 139 | still load their observers by calling `ModelObserver.instance` before. 140 | Observers are singletons and that call instantiates and registers them. 141 | 142 | ## Authors 143 | 144 | * [Chamnap Chhorn](https://github.com/chamnap) 145 | -------------------------------------------------------------------------------- /lib/mongoid/observer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Mongoid 3 | 4 | # Observer classes respond to life cycle callbacks to implement trigger-like 5 | # behavior outside the original class. This is a great way to reduce the 6 | # clutter that normally comes when the model class is burdened with 7 | # functionality that doesn't pertain to the core responsibility of the 8 | # class. Mongoid's observers work similar to ActiveRecord's. Example: 9 | # 10 | # class CommentObserver < Mongoid::Observer 11 | # def after_save(comment) 12 | # Notifications.comment( 13 | # "admin@do.com", "New comment was posted", comment 14 | # ).deliver 15 | # end 16 | # end 17 | # 18 | # This Observer sends an email when a Comment#save is finished. 19 | # 20 | # class ContactObserver < Mongoid::Observer 21 | # def after_create(contact) 22 | # contact.logger.info('New contact added!') 23 | # end 24 | # 25 | # def after_destroy(contact) 26 | # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!") 27 | # end 28 | # end 29 | # 30 | # This Observer uses logger to log when specific callbacks are triggered. 31 | # 32 | # == Observing a class that can't be inferred 33 | # 34 | # Observers will by default be mapped to the class with which they share a 35 | # name. So CommentObserver will be tied to observing Comment, 36 | # ProductManagerObserver to ProductManager, and so on. If you want to 37 | # name your observer differently than the class you're interested in 38 | # observing, you can use the Observer.observe class method which takes 39 | # either the concrete class (Product) or a symbol for that class (:product): 40 | # 41 | # class AuditObserver < Mongoid::Observer 42 | # observe :account 43 | # 44 | # def after_update(account) 45 | # AuditTrail.new(account, "UPDATED") 46 | # end 47 | # end 48 | # 49 | # If the audit observer needs to watch more than one kind of object, 50 | # this can be specified with multiple arguments: 51 | # 52 | # class AuditObserver < Mongoid::Observer 53 | # observe :account, :balance 54 | # 55 | # def after_update(record) 56 | # AuditTrail.new(record, "UPDATED") 57 | # end 58 | # end 59 | # 60 | # The AuditObserver will now act on both updates to Account and Balance 61 | # by treating them both as records. 62 | # 63 | # == Available callback methods 64 | # 65 | # * after_initialize 66 | # * before_validation 67 | # * after_validation 68 | # * before_create 69 | # * around_create 70 | # * after_create 71 | # * before_update 72 | # * around_update 73 | # * after_update 74 | # * before_upsert 75 | # * around_upsert 76 | # * after_upsert 77 | # * before_save 78 | # * around_save 79 | # * after_save 80 | # * before_destroy 81 | # * around_destroy 82 | # * after_destroy 83 | # 84 | # == Storing Observers in Rails 85 | # 86 | # If you're using Mongoid within Rails, observer classes are usually stored 87 | # in +app/models+ with the naming convention of +app/models/audit_observer.rb+. 88 | # 89 | # == Configuration 90 | # 91 | # In order to activate an observer, list it in the +config.mongoid.observers+ 92 | # configuration setting in your +config/application.rb+ file. 93 | # 94 | # config.mongoid.observers = :comment_observer, :signup_observer 95 | # 96 | # Observers will not be invoked unless you define them in your 97 | # application configuration. 98 | # 99 | # == Loading 100 | # 101 | # Observers register themselves with the model class that they observe, 102 | # since it is the class that notifies them of events when they occur. 103 | # As a side-effect, when an observer is loaded, its corresponding model 104 | # class is loaded. 105 | # 106 | # Observers are loaded after the application initializers, so that 107 | # observed models can make use of extensions. If by any chance you are 108 | # using observed models in the initialization, you can 109 | # still load their observers by calling +ModelObserver.instance+ before. 110 | # Observers are singletons and that call instantiates and registers them. 111 | class Observer < ActiveModel::Observer 112 | 113 | private 114 | 115 | # Adds the specified observer to the class. 116 | # 117 | # @example Add the observer. 118 | # observer.add_observer!(Document) 119 | # 120 | # @param [ Class ] klass The child observer to add. 121 | def add_observer!(klass) 122 | super and define_callbacks(klass) 123 | end 124 | 125 | # Defines all the callbacks for each observer of the model. 126 | # 127 | # @example Define all the callbacks. 128 | # observer.define_callbacks(Document) 129 | # 130 | # @param [ Class ] klass The model to define them on. 131 | def define_callbacks(klass) 132 | observer = self 133 | observer_name = observer.class.name.underscore.gsub('/', '__') 134 | Mongoid::Interceptable.observables.each do |callback| 135 | next unless respond_to?(callback) 136 | callback_meth = :"_notify_#{observer_name}_for_#{callback}" 137 | unless klass.respond_to?(callback_meth) 138 | klass.send(:define_method, callback_meth) do |&block| 139 | if value = observer.update(callback, self, &block) 140 | value 141 | else 142 | block.call if block 143 | end 144 | end 145 | klass.send(callback, callback_meth) 146 | end 147 | end 148 | self 149 | end 150 | 151 | # Are the observers disabled for the object? 152 | # 153 | # @api private 154 | # 155 | # @example If the observer disabled? 156 | # Observer.disabled_for(band) 157 | # 158 | # @param [ Document ] object The model instance. 159 | # 160 | # @return [ true, false ] If the observer is disabled. 161 | def disabled_for?(object) 162 | klass = object.class 163 | return false unless klass.respond_to?(:observers) 164 | klass.observers.disabled_for?(self) || Mongoid.observers.disabled_for?(self) 165 | end 166 | 167 | class << self 168 | 169 | # Attaches the observer to the specified classes. 170 | # 171 | # @example Attach the BandObserver to the class Artist. 172 | # class BandObserver < Mongoid::Observer 173 | # observe :artist 174 | # end 175 | # 176 | # @param [ Array ] models The names of the models. 177 | def observe(*models) 178 | models.flatten! 179 | models.collect! do |model| 180 | model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model 181 | end 182 | singleton_class.redefine_method(:observed_classes) { models } 183 | end 184 | end 185 | end 186 | end -------------------------------------------------------------------------------- /spec/mongoid/observer_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Mongoid::Observer do 4 | 5 | let(:recorder) do 6 | CallbackRecorder.instance 7 | end 8 | 9 | after do 10 | recorder.reset 11 | end 12 | 13 | it "is an instance of an active model observer" do 14 | expect(ActorObserver.instance).to be_a_kind_of(ActiveModel::Observer) 15 | end 16 | 17 | context "when the oberserver observes a different class" do 18 | 19 | before(:all) do 20 | class BandObserver < Mongoid::Observer 21 | observe :record 22 | end 23 | end 24 | 25 | after(:all) do 26 | Object.send(:remove_const, :BandObserver) 27 | end 28 | 29 | it "returns the proper observed classes" do 30 | expect(BandObserver.observed_classes).to eq([ Record ]) 31 | end 32 | end 33 | 34 | context "when the observer is for an embedded document" do 35 | 36 | before do 37 | PhoneObserver.instance 38 | end 39 | 40 | let(:person) do 41 | Person.create 42 | end 43 | 44 | let!(:phone) do 45 | person.phone_numbers.create(number: "0152-1111-1111") 46 | end 47 | 48 | context "when updating the embedded document" do 49 | 50 | before do 51 | phone.update_attribute(:number, "0152-2222-2222") 52 | end 53 | 54 | it "contains the updated value in the observer" do 55 | expect(phone.number_in_observer).to eq("0152-2222-2222") 56 | end 57 | end 58 | end 59 | 60 | context "when the observer has descendants" do 61 | 62 | let!(:observer) do 63 | ActorObserver.instance 64 | end 65 | 66 | let!(:actress) do 67 | Actress.create!(name: "Tina Fey") 68 | end 69 | 70 | it "observes descendent class" do 71 | expect(observer.last_after_create_record.try(:name)).to eq(actress.name) 72 | end 73 | end 74 | 75 | context "when the observer is disabled" do 76 | 77 | let!(:observer) do 78 | ActorObserver.instance 79 | end 80 | 81 | let(:actor) do 82 | Actor.create!(name: "Johnny Depp") 83 | end 84 | 85 | it "does not fire the observer" do 86 | Actor.observers.disable(:all) do 87 | actor and expect(observer.last_after_create_record).not_to eq(actor) 88 | end 89 | end 90 | end 91 | 92 | context "when all observers are disabled" do 93 | 94 | let!(:observer) do 95 | ActorObserver.instance 96 | end 97 | 98 | let(:actor) do 99 | Actor.create!(name: "Johnny Depp") 100 | end 101 | 102 | it "does not fire the observer" do 103 | Mongoid.observers.disable(:all) do 104 | actor and expect(observer.last_after_create_record).not_to eq(actor) 105 | end 106 | end 107 | end 108 | 109 | context "when the document is new" do 110 | 111 | let!(:actor) do 112 | Actor.new 113 | end 114 | 115 | it "observes after initialize" do 116 | expect(recorder.last_callback).to eq(:after_initialize) 117 | end 118 | 119 | it "calls after initialize once" do 120 | expect(recorder.call_count[:after_initialize]).to eq(1) 121 | end 122 | 123 | it "contains the model of the callback" do 124 | expect(recorder.last_record[:after_initialize]).to eq(actor) 125 | end 126 | end 127 | 128 | context "when the document is being created" do 129 | 130 | let!(:actor) do 131 | Actor.create! 132 | end 133 | 134 | [ :before_create, 135 | :after_create, 136 | :around_create, 137 | :before_save, 138 | :after_save, 139 | :around_save ].each do |callback| 140 | 141 | it "observes #{callback}" do 142 | expect(recorder.call_count[callback]).to eq(1) 143 | end 144 | 145 | it "contains the model of the callback" do 146 | expect(recorder.last_record[callback]).to eq(actor) 147 | end 148 | end 149 | end 150 | 151 | context "when the document is being updated" do 152 | 153 | let!(:actor) do 154 | Actor.create! 155 | end 156 | 157 | [ :before_update, 158 | :after_update, 159 | :around_update, 160 | :before_save, 161 | :after_save, 162 | :around_save ].each do |callback| 163 | 164 | before do 165 | recorder.reset 166 | actor.update_attributes!(name: "Johnny Depp") 167 | end 168 | 169 | it "observes #{callback}" do 170 | expect(recorder.call_count[callback]).to eq(1) 171 | end 172 | 173 | it "contains the model of the callback" do 174 | expect(recorder.last_record[callback]).to eq(actor) 175 | end 176 | end 177 | end 178 | 179 | context "when the document is being upserted" do 180 | 181 | let!(:actor) do 182 | Actor.create! 183 | end 184 | 185 | [ :before_upsert, 186 | :after_upsert, 187 | :around_upsert ].each do |callback| 188 | 189 | before do 190 | recorder.reset 191 | actor.upsert 192 | end 193 | 194 | it "observes #{callback}" do 195 | expect(recorder.call_count[callback]).to eq(1) 196 | end 197 | 198 | it "contains the model of the callback" do 199 | expect(recorder.last_record[callback]).to eq(actor) 200 | end 201 | end 202 | end 203 | 204 | context "when custom callbacks are being fired" do 205 | 206 | let!(:actor) do 207 | Actor.create! 208 | end 209 | 210 | [ :before_custom, 211 | :after_custom, 212 | :around_custom ].each do |callback| 213 | 214 | before do 215 | recorder.reset 216 | actor.run_callbacks(:custom) do 217 | end 218 | end 219 | 220 | it "observes #{callback}" do 221 | expect(recorder.call_count[callback]).to eq(1) 222 | end 223 | 224 | it "contains the model of the callback" do 225 | expect(recorder.last_record[callback]).to eq(actor) 226 | end 227 | end 228 | end 229 | 230 | context "when the document is being destroyed" do 231 | 232 | let!(:actor) do 233 | Actor.create! 234 | end 235 | 236 | [ :before_destroy, :after_destroy, :around_destroy ].each do |callback| 237 | 238 | before do 239 | recorder.reset 240 | actor.destroy 241 | end 242 | 243 | it "observes #{callback}" do 244 | expect(recorder.call_count[callback]).to eq(1) 245 | end 246 | 247 | it "contains the model of the callback" do 248 | expect(recorder.last_record[callback]).to eq(actor) 249 | end 250 | end 251 | end 252 | 253 | context "when the document is being validated" do 254 | 255 | let!(:actor) do 256 | Actor.new 257 | end 258 | 259 | [:before_validation, :after_validation].each do |callback| 260 | 261 | before do 262 | recorder.reset 263 | actor.valid? 264 | end 265 | 266 | it "observes #{callback}" do 267 | expect(recorder.call_count[callback]).to eq(1) 268 | end 269 | 270 | it "contains the model of the callback" do 271 | expect(recorder.last_record[callback]).to eq(actor) 272 | end 273 | end 274 | end 275 | 276 | context "when using a custom callback" do 277 | 278 | let(:actor) do 279 | Actor.new 280 | end 281 | 282 | before do 283 | actor.do_something 284 | end 285 | 286 | it "notifies the observers once" do 287 | expect(actor.after_custom_count).to eq(1) 288 | end 289 | end 290 | end --------------------------------------------------------------------------------