association_observers 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/Gemfile +6 -0
- data/README.md +57 -2
- data/Rakefile +18 -0
- data/association_observers.gemspec +3 -3
- data/lib/association_observers.rb +43 -31
- data/lib/association_observers/{activerecord.rb → active_record.rb} +15 -16
- data/lib/association_observers/{datamapper.rb → data_mapper.rb} +11 -20
- data/lib/association_observers/extensions/delayed_job.rb +13 -0
- data/lib/association_observers/extensions/resque.rb +30 -0
- data/lib/association_observers/extensions/sidekiq.rb +32 -0
- data/lib/association_observers/notifiers/base.rb +46 -21
- data/lib/association_observers/notifiers/propagation_notifier.rb +2 -2
- data/lib/association_observers/orm/active_record.rb +48 -0
- data/lib/association_observers/orm/base.rb +61 -0
- data/lib/association_observers/orm/data_mapper.rb +61 -0
- data/lib/association_observers/queue.rb +71 -0
- data/lib/association_observers/railtie.rb +11 -4
- data/lib/association_observers/ruby18.rb +26 -8
- data/lib/association_observers/version.rb +1 -1
- data/lib/association_observers/workers/many_delayed_notification.rb +41 -0
- data/spec/activerecord/association_observers_spec.rb +32 -1
- data/spec/activerecord/delayed_job/association_observers_spec.rb +8 -0
- data/spec/activerecord/readme_example_spec.rb +1 -1
- data/spec/activerecord/resque/association_observers_spec.rb +8 -0
- data/spec/activerecord/sidekiq/association_observers_spec.rb +8 -0
- data/spec/datamapper/association_observers_spec.rb +37 -2
- data/spec/{active_record_helper.rb → helpers/active_record_helper.rb} +4 -1
- data/spec/{datamapper_helper.rb → helpers/datamapper_helper.rb} +1 -1
- data/spec/helpers/delayed_job_helper.rb +26 -0
- data/spec/helpers/resque_helper.rb +5 -0
- data/spec/helpers/sidekiq_helper.rb +4 -0
- metadata +174 -156
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module AssociationObservers
|
4
|
+
module Workers
|
5
|
+
class ManyDelayedNotification
|
6
|
+
@queue = AssociationObservers::options[:queue][:name].to_sym
|
7
|
+
|
8
|
+
alias :standard_initialize :initialize
|
9
|
+
def initialize(*args)
|
10
|
+
standard_initialize(*args)
|
11
|
+
# notifier has been dumped two times, reset it here
|
12
|
+
@notifier = args.last
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.perform(*args)
|
16
|
+
self.new(*args).perform
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Queue
|
22
|
+
private
|
23
|
+
|
24
|
+
# overwriting of the enqueue method, using the Resque enqueue method already
|
25
|
+
def enqueue(task, *args)
|
26
|
+
Resque.enqueue(task, *args[0..-2] << Marshal.dump(args.last))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module AssociationObservers
|
3
|
+
module Workers
|
4
|
+
class ManyDelayedNotification
|
5
|
+
include Sidekiq::Worker
|
6
|
+
|
7
|
+
sidekiq_options :queue => AssociationObservers::options[:queue][:name].to_sym
|
8
|
+
|
9
|
+
alias :perform_action! :perform
|
10
|
+
alias :standard_initialize :initialize
|
11
|
+
|
12
|
+
def initialize ; ; end
|
13
|
+
|
14
|
+
def perform(*args)
|
15
|
+
standard_initialize(*args)
|
16
|
+
# notifier has been dumped two times, reset it here
|
17
|
+
@notifier = args.last
|
18
|
+
perform_action!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Queue
|
24
|
+
private
|
25
|
+
|
26
|
+
# overwriting of the method. Sidekiq workers use a method called perform_async
|
27
|
+
def enqueue(task, *args)
|
28
|
+
task.perform_async(*args[0..-2] << Marshal.dump(args.last))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -2,14 +2,19 @@
|
|
2
2
|
module Notifier
|
3
3
|
class Base
|
4
4
|
attr_reader :callback, :observers, :options
|
5
|
-
# @
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
5
|
+
# @overload initialize(callback, observers, options)
|
6
|
+
# Initializes a usable notifier
|
7
|
+
# @param [Symbol] callback callback key to which this observer will respond (:create, :update, :save, :destroy)
|
8
|
+
# @param [Array] observers list of observers asssociations in the symbolized-underscored form (SimpleAssociation => :simple_association)
|
9
|
+
# @param [Hash] options additional options for the notifier
|
10
|
+
# @option options [Class] :observer_class the class of the observer (important when the observer association is polymorphic)
|
11
|
+
# @overload initialize
|
12
|
+
# Initializes a ghost notifier for idempotent method extraction purposes
|
13
|
+
def initialize(*args)
|
14
|
+
@options = args.extract_options!
|
15
|
+
raise "something is wrong, the notifiers was wrongly initialized" unless args.size == 0 or args.size == 2
|
16
|
+
@callback, @observers = args
|
17
|
+
@observers = Array(@observers)
|
13
18
|
end
|
14
19
|
|
15
20
|
|
@@ -17,11 +22,14 @@ module Notifier
|
|
17
22
|
# implemented as a filter where it is seen if the triggered callback corresponds to the callback this observer
|
18
23
|
# responds to
|
19
24
|
#
|
20
|
-
# @param [
|
25
|
+
# @param [Array] args [callback: key from the callback that has just been triggered, to_exclude: list of associations to exclude from the notifying chain]
|
21
26
|
# @param [Object] observable the object which triggered the callback
|
22
|
-
def update(
|
23
|
-
|
24
|
-
|
27
|
+
def update(args, observable)
|
28
|
+
callback, to_exclude = args
|
29
|
+
return unless accepted_callback?(callback)
|
30
|
+
observers = self.observers
|
31
|
+
observers = observers.reject{|assoc| to_exclude.include?(assoc) }
|
32
|
+
observers = observers.select{|assoc| observable.association(assoc).klass == @options[:observer_class] } if @options.has_key?(:observer_class)
|
25
33
|
notify(observable, observers.map{|assoc| observable.send(assoc)}.compact)
|
26
34
|
end
|
27
35
|
|
@@ -39,14 +47,24 @@ module Notifier
|
|
39
47
|
|
40
48
|
private
|
41
49
|
|
50
|
+
# helper method which checks whether the given callback is compatible with the notifier callback.
|
51
|
+
# Example: if the notifier is marked for :save, then :create is a valid callback.
|
52
|
+
# if the notifier is marked for :update, then :create is not a valid callback.
|
53
|
+
def accepted_callback?(callback)
|
54
|
+
case @callback
|
55
|
+
when :save then [:create, :update, :save].include?(callback)
|
56
|
+
when :update then [:update, :save].include?(callback)
|
57
|
+
else callback == @callback
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
42
61
|
# Notifies all observers; filters the observers into two groups: one-to-many and one-to-one collections
|
43
62
|
# @param [Object] observable the object which is notifying
|
44
63
|
# @param [Array] observers the associated observers which will be notified
|
45
|
-
def notify(observable, observers
|
64
|
+
def notify(observable, observers)
|
46
65
|
many, ones = observers.partition{|obs| obs.respond_to?(:size) }
|
47
|
-
|
48
|
-
|
49
|
-
notify_ones(observable, ones, &action)
|
66
|
+
notify_many(observable, many)
|
67
|
+
notify_ones(observable, ones)
|
50
68
|
end
|
51
69
|
|
52
70
|
# TODO: make this notify private as soon as possible again
|
@@ -58,9 +76,7 @@ module Notifier
|
|
58
76
|
# @param [Array[ActiveRecord::Relation]] many_observers the observers which will be notified; each element represents a one-to-many relation
|
59
77
|
def notify_many(observable, many_observers)
|
60
78
|
many_observers.each do |observers|
|
61
|
-
AssociationObservers::
|
62
|
-
yield(observable, observer) if conditions(observable, observer)
|
63
|
-
end if conditions_many(observable, observers)
|
79
|
+
AssociationObservers::queue.enqueue_notifications(observers, observable, self) if conditions_many(observable, observers)
|
64
80
|
end
|
65
81
|
end
|
66
82
|
|
@@ -69,9 +85,18 @@ module Notifier
|
|
69
85
|
# @param [Object] observable the object which is notifying
|
70
86
|
# @param [Array[Object]] observers the observers which will be notified; each element represents a one-to-one association
|
71
87
|
def notify_ones(observable, observers)
|
72
|
-
observers.each do |
|
73
|
-
|
88
|
+
observers.each do |uniq_observer|
|
89
|
+
AssociationObservers::queue.enqueue_notifications([uniq_observer], observable, self,
|
90
|
+
:batch_size => 1, :klass => uniq_observer.class)
|
74
91
|
end
|
75
92
|
end
|
93
|
+
|
94
|
+
# conditionally executes the notifier action. This is explicitly here so that its call can be queued and
|
95
|
+
# the background worker can call it. I don't like it, but it was decided this way because we can't marshal
|
96
|
+
# procs, therefore we can't pass procs to the workers. It is a necessary evil.
|
97
|
+
def conditional_action(observable, observer)
|
98
|
+
action(observable, observer) if conditions(observable, observer)
|
99
|
+
end
|
100
|
+
|
76
101
|
end
|
77
102
|
end
|
@@ -5,12 +5,12 @@
|
|
5
5
|
class PropagationNotifier < Notifier::Base
|
6
6
|
|
7
7
|
def conditions(observable, observer) ; observer.observable? ; end
|
8
|
-
def conditions_many(observable, observers) ;
|
8
|
+
def conditions_many(observable, observers) ; AssociationObservers::orm_adapter.collection_class(observers).observable? ; end
|
9
9
|
|
10
10
|
# propagates the message to the observer's observer if the
|
11
11
|
# observer is indeed observed by any entity
|
12
12
|
def action(observable, observer, callback=@callback)
|
13
|
-
|
13
|
+
observer.send(:notify_observers, [callback, [@options[:observable_association_name] ] ])
|
14
14
|
end
|
15
15
|
|
16
16
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require "association_observers/orm/base"
|
3
|
+
|
4
|
+
module AssociationObservers
|
5
|
+
module Orm
|
6
|
+
class ActiveRecord < Base
|
7
|
+
def self.find(klass, primary_key)
|
8
|
+
klass.find_by_id(primary_key)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @see AssociationObservers::Orm::Base.find_all
|
12
|
+
def self.find_all(klass, attributes)
|
13
|
+
klass.send("find_all_by_#{attributes.keys.join('_and_')}", *attributes.values)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @see AssociationObservers::Orm::Base.get_field
|
17
|
+
def self.get_field(collection, attrs={})
|
18
|
+
collection.is_a?(::ActiveRecord::Relation) or collection.respond_to?(:proxy_association) ?
|
19
|
+
collection.limit(attrs[:limit]).offset(attrs[:offset]).pluck(*attrs[:fields].map{|attr| "#{collection_class(collection).arel_table.name}.#{attr}" }) :
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
# @see AssociationObservers::Orm::Base.collection_class
|
24
|
+
def self.collection_class(collection)
|
25
|
+
collection.klass
|
26
|
+
end
|
27
|
+
|
28
|
+
# @see AssociationObservers::Orm::Base.class_variable_set
|
29
|
+
def self.class_variable_set(klass, name)
|
30
|
+
klass.cattr_accessor name
|
31
|
+
end
|
32
|
+
|
33
|
+
# @see AssociationObservers::Orm::Base.batched_each
|
34
|
+
def self.batched_each(collection, batch, &block)
|
35
|
+
if collection.is_a?(::ActiveRecord::Relation) ?
|
36
|
+
collection.find_each(:batch_size => batch, &block) :
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @see AssociationObservers::Orm::Base.validate_parameters
|
42
|
+
def self.validate_parameters(observer, observable_associations, notifier_names, callbacks)
|
43
|
+
raise "Invalid callback; possible options: :create, :update, :save, :destroy" unless callbacks.all?{|o|[:create,:update,:save,:destroy].include?(o.to_sym)}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module AssociationObservers
|
3
|
+
module Orm
|
4
|
+
class Base
|
5
|
+
# finds record by primary key
|
6
|
+
# @abstract
|
7
|
+
#
|
8
|
+
# @param [Class] klass the class of the record to look for
|
9
|
+
# @param [Object] primary_key primary key of the record to look for
|
10
|
+
# @return [Symbol] ORM class method that fetches records from the DB
|
11
|
+
def self.find(klass, primary_key)
|
12
|
+
raise "should be defined in an adapter for the used ORM"
|
13
|
+
end
|
14
|
+
|
15
|
+
# finds all records which match the given attributes
|
16
|
+
# @abstract
|
17
|
+
#
|
18
|
+
# @param [Class] klass the class of the records to look for
|
19
|
+
# @param [Hash] attributes list of key/value associations which have to be matched by the found records
|
20
|
+
# @return [Symbol] ORM class method that fetches records from the DB
|
21
|
+
def self.find_all(klass, attributes)
|
22
|
+
raise "should be defined in an adapter for the used ORM"
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [Array] collection records to iterate through
|
26
|
+
# @param [Array] attrs attributes to fetch for
|
27
|
+
# @return [Array] a collection of the corresponding values to the given keys for each record
|
28
|
+
def self.get_field(collection, attrs)
|
29
|
+
attrs[:offset] == 0 ? collection.map{|elem| elem.send(*attrs[:fields])} : []
|
30
|
+
end
|
31
|
+
|
32
|
+
# @abstract
|
33
|
+
# @param [Array] collection objects container
|
34
|
+
# @return [Symbol] ORM collection method name to get the model of its children
|
35
|
+
def self.collection_class(collection)
|
36
|
+
raise "should be defined in an adapter for the used ORM"
|
37
|
+
end
|
38
|
+
|
39
|
+
# implementation of a batched each enumerator on a collection
|
40
|
+
#
|
41
|
+
# @param [Array] collection records to batch through
|
42
|
+
def self.batched_each(collection, batch=1, &block)
|
43
|
+
batch > 1 ?
|
44
|
+
collection.each_slice(batch) { |batch| batch.each(&block) } :
|
45
|
+
collection.each(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @abstract
|
49
|
+
# checks the parameters received by the observer DSL call, handles unexpected input according by triggering exceptions,
|
50
|
+
# warnings, deprecation messages
|
51
|
+
# @param [Class] observer the observer class
|
52
|
+
# @param [Array] observable_associations collection of the names of associations on the observer which will be observed
|
53
|
+
# @param [Array] notifier_classes collection of the notifiers for the observation
|
54
|
+
# @param [Array] observer_callbacks collection of the callbacks/methods to be observed
|
55
|
+
def self.validate_parameters(observer, observable_associations, notifier_classes, observer_callbacks)
|
56
|
+
raise "should be defined in an adapter for the used ORM"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require "association_observers/orm/base"
|
3
|
+
|
4
|
+
module AssociationObservers
|
5
|
+
module Orm
|
6
|
+
class DataMapper < Base
|
7
|
+
def self.find(klass, primary_key)
|
8
|
+
klass.get(primary_key)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @see AssociationObservers::Orm::Base.find_all
|
12
|
+
def self.find_all(klass, attributes)
|
13
|
+
klass.all(attributes)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @see AssociationObservers::Orm::Base.get_field
|
17
|
+
def self.get_field(collection, attrs={})
|
18
|
+
collection.is_a?(::DataMapper::Collection) ?
|
19
|
+
collection.all(attrs) :
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
# @see AssociationObservers::Orm::Base.collection_class
|
24
|
+
def self.collection_class(collection)
|
25
|
+
collection.model
|
26
|
+
end
|
27
|
+
|
28
|
+
# @see AssociationObservers::Orm::Base.class_variable_set
|
29
|
+
def self.class_variable_set(klass, name)
|
30
|
+
klass.instance_eval <<-END
|
31
|
+
@@#{name}=nil
|
32
|
+
def #{name}
|
33
|
+
@@#{name}
|
34
|
+
end
|
35
|
+
|
36
|
+
def #{name}=(value)
|
37
|
+
@@#{name} = value
|
38
|
+
end
|
39
|
+
END
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# @see AssociationObservers::Orm::Base.batched_each
|
44
|
+
def self.batched_each(collection, batch, &block)
|
45
|
+
collection.is_a?(::DataMapper::Collection) ?
|
46
|
+
collection.each(&block) : # datamapper batches already by 500 https://groups.google.com/forum/?fromgroups=#!searchin/datamapper/batches/datamapper/lAZWFN4TWAA/G1Gu-ams_QMJ
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
# @see AssociationObservers::Orm::Base.validate_parameters
|
51
|
+
def self.validate_parameters(observer, observable_associations, notifier_names, callbacks)
|
52
|
+
observable_associations.each do |o|
|
53
|
+
if observer.relationships[o].is_a?(::DataMapper::Associations::ManyToMany::Relationship)
|
54
|
+
warn "this gem does not currently support observation behaviour for many to many relationships"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
# the queue handles the notification distributions which the notifiers trigger. The Notifier usually knows what to notify
|
5
|
+
# and whom to notify. It passes this information to the queue, which strategizes the dispatching. It is more than a singleton;
|
6
|
+
# it is designed as a single inter-process object. Why? Because one cannot marshall procedures, and this unique inter-process
|
7
|
+
# object acts as a container for the procedures which will be handled assynchronously by the message queue solution. It is also
|
8
|
+
# a proxy to the used background queue solution: the queueing basically proxies the queueing somewhere else (Delayed Job, Resque...)
|
9
|
+
module AssociationObservers
|
10
|
+
class Queue
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
|
14
|
+
# encapsulates enqueuing strategy. if the callback is to a destroy action, one cannot afford to enqueue, because the
|
15
|
+
# observable will be deleted by then. So, perform destroy notifications synchronously right away. If not, the strategy
|
16
|
+
# for now is get the object ids and enqueue them with the notifier.
|
17
|
+
#
|
18
|
+
# @param [ActiveRecord:Relation, DataMapper::Relationship] observers to be notified
|
19
|
+
# @param [Notifier::Base] notifier encapsulates the notification logic
|
20
|
+
# @param [Hash] opts other possible options that can't be inferred from the given arguments
|
21
|
+
def enqueue_notifications(observers, observable, notifier, opts={})
|
22
|
+
klass = opts[:klass] || AssociationObservers::orm_adapter.collection_class(observers)
|
23
|
+
batch_size = opts[:batch_size] || klass.observable_options[:batch_size]
|
24
|
+
|
25
|
+
if notifier.callback.eql?(:destroy)
|
26
|
+
method = RUBY_VERSION < "1.9" ?
|
27
|
+
AssociationObservers::Backports::Proc.fake_curry(notifier.method(:conditional_action).to_proc, observable) :
|
28
|
+
notifier.method(:conditional_action).to_proc.curry[observable]
|
29
|
+
AssociationObservers::orm_adapter.batched_each(observers, batch_size, &method)
|
30
|
+
else
|
31
|
+
# create workers
|
32
|
+
i = 0
|
33
|
+
loop do
|
34
|
+
ids = AssociationObservers::orm_adapter.get_field(observers, :fields => [:id], :limit => batch_size, :offset => i*batch_size).compact
|
35
|
+
break if ids.empty?
|
36
|
+
enqueue(Workers::ManyDelayedNotification, ids, klass.name, observable.id, observable.class.name, notifier)
|
37
|
+
i += 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def engine=(engine)
|
43
|
+
AssociationObservers::options[:queue][:engine] = engine
|
44
|
+
initialize_queue_engine
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize_queue_engine
|
48
|
+
engine = AssociationObservers::options[:queue][:engine]
|
49
|
+
return if engine.nil?
|
50
|
+
raise "#{engine}: unsupported engine" unless %w(delayed_job resque sidekiq).include?(engine.to_s)
|
51
|
+
# first, remove stuff from previous engine
|
52
|
+
# TODO: can une exclude modules???
|
53
|
+
#if AssociationObservers::options[:queue_engine]
|
54
|
+
#
|
55
|
+
#end
|
56
|
+
require "association_observers/extensions/#{engine}"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# enqueues the task with the given arguments to be processed asynchronously
|
62
|
+
# this method implements the fallback, which is: execute synchronously
|
63
|
+
# @note this method is overwritten by the message queue adapters. If your background queue engine is not supported,
|
64
|
+
# overwrite this method and delegate to your background queue.
|
65
|
+
def enqueue(task, *args)
|
66
|
+
t = task.new(*args)
|
67
|
+
t.perform
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -2,18 +2,25 @@
|
|
2
2
|
module AssociationObservers
|
3
3
|
def self.initialize_railtie
|
4
4
|
ActiveSupport.on_load :active_record do
|
5
|
-
require 'association_observers/
|
6
|
-
end
|
7
|
-
ActiveSupport.on_load :data_mapper do
|
8
|
-
require 'association_observers/datamapper'
|
5
|
+
require 'association_observers/active_record'
|
9
6
|
end
|
7
|
+
# ORM Adapters
|
8
|
+
require 'association_observers/data_mapper' if defined?(DataMapper)
|
9
|
+
|
10
10
|
end
|
11
11
|
class Railtie < Rails::Railtie
|
12
|
+
config.association_observers = ActiveSupport::OrderedOptions.new.merge(AssociationObservers::options)
|
13
|
+
config.association_observers.queue = ActiveSupport::OrderedOptions.new.merge(config.association_observers.queue)
|
14
|
+
|
12
15
|
initializer 'association_observers.insert_into_orm' do
|
13
16
|
AssociationObservers.initialize_railtie
|
14
17
|
end
|
15
18
|
initializer 'association_observers.autoload', :before => :set_autoload_paths do |app|
|
16
19
|
app.config.autoload_paths << Rails.root.join("app", "notifiers")
|
17
20
|
end
|
21
|
+
initializer 'association_observers.rails_configuration_options' do
|
22
|
+
AssociationObservers::options.merge!(config.association_observers)
|
23
|
+
AssociationObservers::queue.initialize_queue_engine
|
24
|
+
end
|
18
25
|
end
|
19
26
|
end
|