association_observers 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|