Skip to content

Commit 6b29bad

Browse files
committed
extract code from mongoid
1 parent f6c71bf commit 6b29bad

File tree

14 files changed

+645
-25
lines changed

14 files changed

+645
-25
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ gem "bundler", "~> 1.6.1"
44
gem "rake", "~> 10.2.2"
55
gem "rspec", "~> 2.14.1"
66
gem "pry", "~> 0.9.12.6"
7-
gem "database_cleaner", "~> 1.2.0"
87
gem "coveralls", require: false
8+
gem "codeclimate-test-reporter"
99

1010
# Specify your gem's dependencies in mongoid-observers.gemspec
1111
gemspec

lib/mongoid/observer.rb

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,192 @@
1+
# encoding: utf-8
12
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+
# "[email protected]", "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.
2111
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+
#
122+
# @since 2.0.0.rc.8
123+
def add_observer!(klass)
124+
super and define_callbacks(klass)
125+
end
126+
127+
# Defines all the callbacks for each observer of the model.
128+
#
129+
# @example Define all the callbacks.
130+
# observer.define_callbacks(Document)
131+
#
132+
# @param [ Class ] klass The model to define them on.
133+
#
134+
# @since 2.0.0.rc.8
135+
def define_callbacks(klass)
136+
observer = self
137+
observer_name = observer.class.name.underscore.gsub('/', '__')
138+
Mongoid::Interceptable::CALLBACKS.each do |callback|
139+
next unless respond_to?(callback)
140+
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
141+
unless klass.respond_to?(callback_meth)
142+
klass.send(:define_method, callback_meth) do |&block|
143+
if value = observer.update(callback, self, &block)
144+
value
145+
else
146+
block.call if block
147+
end
148+
end
149+
klass.send(callback, callback_meth)
150+
end
151+
end
152+
self
153+
end
154+
155+
# Are the observers disabled for the object?
156+
#
157+
# @api private
158+
#
159+
# @example If the observer disabled?
160+
# Observer.disabled_for(band)
161+
#
162+
# @param [ Document ] object The model instance.
163+
#
164+
# @return [ true, false ] If the observer is disabled.
165+
def disabled_for?(object)
166+
klass = object.class
167+
return false unless klass.respond_to?(:observers)
168+
klass.observers.disabled_for?(self) || Mongoid.observers.disabled_for?(self)
169+
end
170+
171+
class << self
172+
173+
# Attaches the observer to the specified classes.
174+
#
175+
# @example Attach the BandObserver to the class Artist.
176+
# class BandObserver < Mongoid::Observer
177+
# observe :artist
178+
# end
179+
#
180+
# @param [ Array<Symbol> ] models The names of the models.
181+
#
182+
# @since 3.0.15
183+
def observe(*models)
184+
models.flatten!
185+
models.collect! do |model|
186+
model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model
187+
end
188+
singleton_class.redefine_method(:observed_classes) { models }
189+
end
190+
end
3191
end
4192
end
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
module Mongoid
22
module Composable
3-
extend ActiveSupport::Concern
4-
53
include ActiveModel::Observing
64
end
75
end

spec/app/models/actor.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Actor
2+
include Mongoid::Document
3+
field :name
4+
5+
def do_something
6+
run_callbacks(:custom) do
7+
self.name = "custom"
8+
end
9+
end
10+
end

spec/app/models/actor_observer.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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

spec/app/models/actress.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class Actress < Actor
2+
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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::CALLBACKS.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

spec/app/models/person.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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/app/models/phone.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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/phone_observer.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class PhoneObserver < Mongoid::Observer
2+
3+
def after_save(phone)
4+
phone.number_in_observer = phone.number
5+
end
6+
end

0 commit comments

Comments
 (0)