activr 1.0.0

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.
@@ -0,0 +1,37 @@
1
+ module Activr
2
+
3
+ #
4
+ # Rails Context holder
5
+ #
6
+ class RailsCtx
7
+
8
+ class_attribute :controller
9
+ self.controller = nil
10
+
11
+ class << self
12
+
13
+ # Get current Rails View context
14
+ #
15
+ # @return [ActionView::Base] Rails view instance
16
+ def view_context
17
+ @view_context ||= if defined?(::Rails)
18
+ rails_controller = self.controller || begin
19
+ fake_controller = ApplicationController.new
20
+ fake_controller.request = ActionController::TestRequest.new if defined?(ActionController::TestRequest)
21
+ fake_controller
22
+ end
23
+
24
+ rails_controller.view_context
25
+ end
26
+ end
27
+
28
+ # Clear memoization of current Rails view context
29
+ def clear_view_context!
30
+ @view_context = nil
31
+ end
32
+
33
+ end # class << self
34
+
35
+ end # class RailsCtx
36
+
37
+ end # module Activr
@@ -0,0 +1,73 @@
1
+ module Activr
2
+
3
+ # Hook into Rails
4
+ class Railtie < ::Rails::Railtie
5
+ initializer "activr.set_conf", :after => 'mongoid.load-config' do |app|
6
+ Activr.configure do |config|
7
+ # setup app path
8
+ activr_dir = File.join(::Rails.root, 'app', 'activr')
9
+ if !File.exists?(activr_dir)
10
+ activr_dir = File.join(::Rails.root, 'app')
11
+ end
12
+
13
+ config.app_path = activr_dir
14
+
15
+ use_mongoid_conn = Fwissr['/activr/mongodb/uri'].blank? &&
16
+ (Fwissr['/activr/skip_mongoid_railtie'] != true) &&
17
+ (ENV['ACTIVR_SKIP_MONGOID_RAILTIE'] != 'true') &&
18
+ defined?(Mongoid)
19
+
20
+ if use_mongoid_conn
21
+ # get mongoid conf
22
+ if Mongoid::VERSION.start_with?("2.")
23
+ # Mongoid 2
24
+ config.mongodb[:uri] = "mongodb://#{Mongoid.master.connection.host}:#{Mongoid.master.connection.port}/#{Mongoid.master.name}"
25
+ elsif Mongoid.sessions[:default] && !Mongoid.sessions[:default][:uri].blank?
26
+ # Mongoid >= 3 with :uri setting
27
+ config.mongodb[:uri] = Mongoid.sessions[:default][:uri].dup
28
+ elsif Mongoid.sessions[:default] && !Mongoid.sessions[:default][:database].blank? && !Mongoid.sessions[:default][:hosts].blank?
29
+ # Mongoid >= 3 without :uri setting
30
+ config.mongodb[:uri] = "mongodb://#{Mongoid.sessions[:default][:hosts].first}/#{Mongoid.sessions[:default][:database]}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ initializer 'activr.autoload', :after => "activr.set_conf", :before => :set_autoload_paths do |app|
37
+ app.config.autoload_paths += [
38
+ File.join(Activr.config.app_path, 'activities'),
39
+ File.join(Activr.config.app_path, 'timelines'),
40
+ ]
41
+ end
42
+
43
+ initializer "activr.setup_async_hooks" do |app|
44
+ if defined?(::Resque) && (ENV['ACTIVR_FORCE_SYNC'] != 'true')
45
+ Activr.configure do |config|
46
+ config.async[:route_activity] ||= Activr::Async::Resque::RouteActivity
47
+ config.async[:timeline_handle] ||= Activr::Async::Resque::TimelineHandle
48
+ end
49
+ end
50
+ end
51
+
52
+ initializer "activr.setup_action_controller" do |app|
53
+ ActiveSupport.on_load :action_controller do
54
+ self.class_eval do
55
+ before_filter do |controller|
56
+ Activr::RailsCtx.clear_view_context!
57
+ Activr::RailsCtx.controller = controller
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ rake_tasks do
64
+ load "activr/railties/activr.rake"
65
+ end
66
+
67
+ config.after_initialize do |app|
68
+ # setup registry
69
+ Activr.setup
70
+ end
71
+ end # class Railtie
72
+
73
+ end # module Activr
@@ -0,0 +1,14 @@
1
+ namespace :activr do
2
+
3
+ desc "Create the indexes"
4
+ task :create_indexes => :environment do
5
+ ::Rails.application.eager_load!
6
+
7
+ Activr.setup
8
+
9
+ Activr.storage.create_indexes do |index_name|
10
+ puts "Created index: #{index_name}"
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,268 @@
1
+ module Activr
2
+
3
+ #
4
+ # The registry holds all activities, entities, timelines and timeline entries classes defined in the application
5
+ #
6
+ # The registry singleton is accessible with {Activr.registry}
7
+ #
8
+ class Registry
9
+
10
+ # @return [Hash{Symbol=>Class}] model class associated to entity name
11
+ attr_reader :entity_classes
12
+
13
+ # @return [Hash{Class=>Array<Symbol>}] entity names for activity class
14
+ attr_reader :activity_entities
15
+
16
+ # Init
17
+ def initialize
18
+ @setup = false
19
+
20
+ @timelines = nil
21
+ @timeline_entries = nil
22
+ @activities = nil
23
+ @entities = nil
24
+ @models = nil
25
+
26
+ @entity_classes = { }
27
+ @activity_entities = { }
28
+
29
+ @timeline_entities_for_model = { }
30
+ @activity_entities_for_model = { }
31
+ end
32
+
33
+ # Setup registry
34
+ def setup
35
+ return if @setup
36
+
37
+ # eagger load all classes
38
+ self.activities
39
+ self.timelines
40
+ self.timeline_entries
41
+
42
+ @setup = true
43
+ end
44
+
45
+
46
+ #
47
+ # Classes
48
+ #
49
+
50
+ # Get all registered timelines
51
+ #
52
+ # @return [Hash{String=>Class}] A hash of `<timeline kind> => <timeline class>`
53
+ def timelines
54
+ @timelines ||= self.classes_from_path(Activr.timelines_path)
55
+ end
56
+
57
+ # Get class for given timeline kind
58
+ #
59
+ # @param timeline_kind [String] Timeline kind
60
+ # @return [Class] Timeline class
61
+ def class_for_timeline(timeline_kind)
62
+ result = self.timelines[timeline_kind]
63
+ raise "No class defined for timeline kind: #{timeline_kind}" if result.blank?
64
+ result
65
+ end
66
+
67
+ # Get all registered timeline entries
68
+ #
69
+ # @return [Hash{String=>Hash{String=>Class}}] A hash of `<timeline kind> => { <route kind> => <timeline entry class>, ... }`
70
+ def timeline_entries
71
+ @timeline_entries ||= begin
72
+ result = { }
73
+
74
+ self.timelines.each do |(timeline_kind, timeline_class)|
75
+ dir_name = Activr::Utils.kind_for_class(timeline_class)
76
+ dir_path = File.join(Activr.timelines_path, dir_name)
77
+
78
+ if !File.directory?(dir_path)
79
+ dir_name = Activr::Utils.kind_for_class(timeline_class, 'timeline')
80
+ dir_path = File.join(Activr.timelines_path, dir_name)
81
+ end
82
+
83
+ if File.directory?(dir_path)
84
+ result[timeline_kind] = { }
85
+
86
+ Dir["#{dir_path}/*.rb"].sort.inject(result[timeline_kind]) do |memo, file_path|
87
+ base_name = File.basename(file_path, '.rb')
88
+
89
+ # skip base class
90
+ if (base_name != "base_timeline_entry")
91
+ klass = "#{timeline_class.name}::#{base_name.camelize}".constantize
92
+
93
+ route_kind = if (match_data = base_name.match(/(.+)_timeline_entry$/))
94
+ match_data[1]
95
+ else
96
+ base_name
97
+ end
98
+
99
+ route = timeline_class.routes.find do |timeline_route|
100
+ timeline_route.kind == route_kind
101
+ end
102
+
103
+ raise "Timeline entry class found for an unspecified timeline route: #{file_path} / routes: #{timeline_class.routes.inspect}" unless route
104
+ memo[route_kind] = klass
105
+ end
106
+
107
+ memo
108
+ end
109
+ end
110
+ end
111
+
112
+ result
113
+ end
114
+ end
115
+
116
+ # Get class for timeline entry corresponding to given route in given timeline
117
+ #
118
+ # @param timeline_kind [String] Timeline kind
119
+ # @param route_kind [String] Route kind
120
+ # @return [Class] Timeline entry class
121
+ def class_for_timeline_entry(timeline_kind, route_kind)
122
+ (self.timeline_entries[timeline_kind] && self.timeline_entries[timeline_kind][route_kind]) || Activr::Timeline::Entry
123
+ end
124
+
125
+ # Get all registered activities
126
+ #
127
+ # @return [Hash{String=>Class}] A hash of `<activity kind> => <activity class>`
128
+ def activities
129
+ @activities ||= self.classes_from_path(Activr.activities_path)
130
+ end
131
+
132
+ # Get class for given activity
133
+ #
134
+ # @param activity_kind [String] Activity kind
135
+ # @return [Class] Activity class
136
+ def class_for_activity(activity_kind)
137
+ result = self.activities[activity_kind]
138
+ raise "No class defined for activity kind: #{activity_kind}" if result.blank?
139
+ result
140
+ end
141
+
142
+ # Get all registered entities
143
+ #
144
+ # @return [Hash{Symbol=>Array<Class>}] A hash of `:<entity name> => [ <activity class>, <activity class>, ... ]`
145
+ def entities
146
+ # loading activities triggers calls to #add_entity method
147
+ self.activities if @entities.blank?
148
+
149
+ @entities || { }
150
+ end
151
+
152
+ # Get all registered entities names
153
+ #
154
+ # @return [Array<Symbol>] List of entities names
155
+ def entities_names
156
+ @entities_names ||= self.entities.keys
157
+ end
158
+
159
+ # Register an entity
160
+ #
161
+ # @param entity_name [Symbol] Entity name
162
+ # @param entity_options [Hash] Entity options
163
+ # @param activity_klass [Class] Activity class that uses that entity
164
+ def add_entity(entity_name, entity_options, activity_klass)
165
+ entity_name = entity_name.to_sym
166
+
167
+ if @entity_classes[entity_name] && (@entity_classes[entity_name].name != entity_options[:class].name)
168
+ # otherwise this would break timeline entries deletion mecanism
169
+ raise "Entity name #{entity_name} already used with class #{@entity_classes[entity_name]}, can't redefine it with class #{entity_options[:class]}"
170
+ end
171
+
172
+ # class for entity
173
+ @entity_classes[entity_name] = entity_options[:class]
174
+
175
+ # entities for activity
176
+ @activity_entities[activity_klass] ||= [ ]
177
+ @activity_entities[activity_klass] << entity_name
178
+
179
+ # entities
180
+ @entities ||= { }
181
+ @entities[entity_name] ||= { }
182
+
183
+ if !@entities[entity_name][activity_klass].blank?
184
+ raise "Entity name #{entity_name} already used for activity: #{activity_klass}"
185
+ end
186
+
187
+ @entities[entity_name][activity_klass] = entity_options
188
+ end
189
+
190
+ # Get all models that included mixin {Activr::Entity::ModelMixin}
191
+ #
192
+ # @return [Array<Class>] List of model classes
193
+ def models
194
+ # loading activities triggers models loading
195
+ self.activities if @models.blank?
196
+
197
+ @models || [ ]
198
+ end
199
+
200
+ # Register a model
201
+ #
202
+ # @param model_class [Class] Model class
203
+ def add_model(model_class)
204
+ @models ||= [ ]
205
+ @models << model_class
206
+ end
207
+
208
+ # Get all entities names for given model class
209
+ def activity_entities_for_model(model_class)
210
+ @activity_entities_for_model[model_class] ||= begin
211
+ result = [ ]
212
+
213
+ @entity_classes.each do |entity_name, entity_class|
214
+ result << entity_name if (entity_class == model_class)
215
+ end
216
+
217
+ result
218
+ end
219
+ end
220
+
221
+ # Get all entities names by timelines that can have a reference to given model class
222
+ #
223
+ # @param model_class [Class] Model class
224
+ # @return [Hash{Class=>Array<Symbol>}] Lists of entities names indexed by timeline class
225
+ def timeline_entities_for_model(model_class)
226
+ @timeline_entities_for_model[model_class] ||= begin
227
+ result = { }
228
+
229
+ self.timelines.each do |timeline_kind, timeline_class|
230
+ result[timeline_class] = [ ]
231
+
232
+ timeline_class.routes.each do |route|
233
+ entities_ary = @activity_entities[route.activity_class]
234
+ (entities_ary || [ ]).each do |entity_name|
235
+ result[timeline_class] << entity_name if (@entity_classes[entity_name] == model_class)
236
+ end
237
+ end
238
+
239
+ result[timeline_class].uniq!
240
+ end
241
+
242
+ result
243
+ end
244
+ end
245
+
246
+ # Find all classes in given directory
247
+ #
248
+ # @api private
249
+ #
250
+ # @param dir_path [String] Directory path
251
+ # @return [Hash{String=>Class}] Hash of `<kind> => <Class>`
252
+ def classes_from_path(dir_path)
253
+ Dir["#{dir_path}/*.rb"].sort.inject({ }) do |memo, file_path|
254
+ klass = File.basename(file_path, '.rb').camelize.constantize
255
+
256
+ if !memo[klass.kind].nil?
257
+ raise "Kind #{klass.kind} already used by class #{memo[klass.kind]} so can't use it for class #{klass}"
258
+ end
259
+
260
+ memo[klass.kind] = klass
261
+
262
+ memo
263
+ end
264
+ end
265
+
266
+ end # class Registry
267
+
268
+ end # module Activr
@@ -0,0 +1,404 @@
1
+ module Activr
2
+
3
+ #
4
+ # The storage is the component that uses the database driver to serialize/unserialize activities and timeline entries.
5
+ #
6
+ # The storage singleton is accessible with {Activr.storage}
7
+ #
8
+ class Storage
9
+
10
+ autoload :MongoDriver, 'activr/storage/mongo_driver'
11
+
12
+ # @return [MongoDriver] database driver
13
+ attr_reader :driver
14
+
15
+ def initialize
16
+ @driver = Activr::Storage::MongoDriver.new
17
+
18
+ @hooks = { }
19
+ end
20
+
21
+ # Is it a valid document id
22
+ #
23
+ # @param doc_id [Object] Document id to check
24
+ # @return [true, false]
25
+ def valid_id?(doc_id)
26
+ self.driver.valid_id?(doc_id)
27
+ end
28
+
29
+ # Is it a serialized document id
30
+ #
31
+ # @return [true,false]
32
+ def serialized_id?(doc_id)
33
+ self.driver.serialized_id?(doc_id)
34
+ end
35
+
36
+ # Unserialize a document id
37
+ #
38
+ # @param doc_id [Object] Document id
39
+ # @return [Object] Unserialized document id
40
+ def unserialize_id(doc_id)
41
+ self.driver.unserialize_id(doc_id)
42
+ end
43
+
44
+ # Unserialize given parameter only if it is a serialized document id
45
+ #
46
+ # @param doc_id [Object] Document id
47
+ # @return [Object] Unserialized or unmodified document id
48
+ def unserialize_id_if_necessary(doc_id)
49
+ self.serialized_id?(doc_id) ? self.unserialize_id(doc_id) : doc_id
50
+ end
51
+
52
+
53
+ #
54
+ # Activities
55
+ #
56
+
57
+ # Insert a new activity
58
+ #
59
+ # @param activity [Activity] Activity to insert
60
+ # @return [Object] The inserted activity id
61
+ def insert_activity(activity)
62
+ # serialize
63
+ activity_hash = activity.to_hash
64
+
65
+ # run hook
66
+ self.run_hook(:will_insert_activity, activity_hash)
67
+
68
+ # insert
69
+ self.driver.insert_activity(activity_hash)
70
+ end
71
+
72
+ # Find an activity
73
+ #
74
+ # @param activity_id [Object] Activity id to find
75
+ # @return [Activity, Nil] An activity instance or `nil` if not found
76
+ def find_activity(activity_id)
77
+ activity_hash = self.driver.find_activity(activity_id)
78
+ if activity_hash
79
+ # run hook
80
+ self.run_hook(:did_find_activity, activity_hash)
81
+
82
+ # unserialize
83
+ Activr::Activity.from_hash(activity_hash)
84
+ else
85
+ nil
86
+ end
87
+ end
88
+
89
+ # Find latest activities
90
+ #
91
+ # @note If you use others selectors then 'limit' argument and 'skip' option then you have to setup corresponding indexes in database.
92
+ #
93
+ # @param limit [Integer] Max number of activities to find
94
+ # @param options [Hash] Options hash
95
+ # @option options [Integer] :skip Number of activities to skip (default: 0)
96
+ # @option options [Time] :before Find activities generated before that datetime (excluding)
97
+ # @option options [Time] :after Find activities generated after that datetime (excluding)
98
+ # @option options [Hash{Sym=>String}] :entities Filter by entities values (empty means 'all values')
99
+ # @option options [Array<Class>] :only Find only these activities
100
+ # @option options [Array<Class>] :except Skip these activities
101
+ # @return [Array<Activity>] An array of activities
102
+ def find_activities(limit, options = { })
103
+ # default options
104
+ options = {
105
+ :skip => 0,
106
+ :before => nil,
107
+ :after => nil,
108
+ :entities => { },
109
+ :only => [ ],
110
+ :except => [ ],
111
+ }.merge(options)
112
+
113
+ options[:only] = [ options[:only] ] if (options[:only] && !options[:only].is_a?(Array))
114
+
115
+ # find
116
+ result = self.driver.find_activities(limit, options).map do |activity_hash|
117
+ # run hook
118
+ self.run_hook(:did_find_activity, activity_hash)
119
+
120
+ # unserialize
121
+ Activr::Activity.from_hash(activity_hash)
122
+ end
123
+
124
+ result
125
+ end
126
+
127
+ # Count number of activities
128
+ #
129
+ # @note If you use one of options selectors then you have to setup corresponding indexes in database.
130
+ #
131
+ # @param options [Hash] Options hash
132
+ # @option options [Time] :before Find activities generated before that datetime (excluding)
133
+ # @option options [Time] :after Find activities generated after that datetime (excluding)
134
+ # @option options [Hash{Sym=>String}] :entities Filter by entities values (empty means 'all values')
135
+ # @option options [Array<Class>] :only Find only these activities
136
+ # @option options [Array<Class>] :except Skip these activities
137
+ # @return [Integer] Number of activities
138
+ def count_activities(options = { })
139
+ # default options
140
+ options = {
141
+ :before => nil,
142
+ :after => nil,
143
+ :entities => { },
144
+ :only => [ ],
145
+ :except => [ ],
146
+ }.merge(options)
147
+
148
+ options[:only] = [ options[:only] ] if (options[:only] && !options[:only].is_a?(Array))
149
+
150
+ # count
151
+ self.driver.count_activities(options)
152
+ end
153
+
154
+ # Find number of duplicate activities
155
+ #
156
+ # @param activity [Activity] Activity to search
157
+ # @param after [Time] Search after that datetime
158
+ # @return [Integer] Number of activity duplicates
159
+ def count_duplicate_activities(activity, after)
160
+ entities = { }
161
+
162
+ activity.entities.each do |entity_name, entity|
163
+ entities[entity_name.to_sym] = entity.model_id
164
+ end
165
+
166
+ self.count_activities({
167
+ :only => activity.class,
168
+ :entities => entities,
169
+ :after => after,
170
+ })
171
+ end
172
+
173
+ # Delete activities referring to given entity model instance
174
+ #
175
+ # @param model [Object] Model instance
176
+ def delete_activities_for_entity_model(model)
177
+ Activr.registry.activity_entities_for_model(model.class).each do |entity_name|
178
+ self.driver.delete_activities(:entities => { entity_name => model.id })
179
+ end
180
+ end
181
+
182
+
183
+ #
184
+ # Timeline Entries
185
+ #
186
+
187
+ # Insert a new timeline entry
188
+ #
189
+ # @param timeline_entry [Timeline::Entry] Timeline entry to insert
190
+ # @return [Object] Inserted timeline entry id
191
+ def insert_timeline_entry(timeline_entry)
192
+ # serialize
193
+ timeline_entry_hash = timeline_entry.to_hash
194
+
195
+ # run hook
196
+ self.run_hook(:will_insert_timeline_entry, timeline_entry_hash, timeline_entry.timeline.class)
197
+
198
+ # insert
199
+ self.driver.insert_timeline_entry(timeline_entry.timeline.kind, timeline_entry_hash)
200
+ end
201
+
202
+ # Find a timeline entry
203
+ #
204
+ # @param timeline [Timeline] Timeline instance
205
+ # @param tl_entry_id [Object] Timeline entry id
206
+ # @return [Timeline::Entry, Nil] Found timeline entry
207
+ def find_timeline_entry(timeline, tl_entry_id)
208
+ timeline_entry_hash = self.driver.find_timeline_entry(timeline.kind, tl_entry_id)
209
+ if timeline_entry_hash
210
+ # run hook
211
+ self.run_hook(:did_find_timeline_entry, timeline_entry_hash, timeline.class)
212
+
213
+ # unserialize
214
+ Activr::Timeline::Entry.from_hash(timeline_entry_hash, timeline)
215
+ else
216
+ nil
217
+ end
218
+ end
219
+
220
+ # Find timeline entries by descending timestamp
221
+ #
222
+ # @param timeline [Timeline] Timeline instance
223
+ # @param limit [Integer] Max number of entries to find
224
+ # @param options [Hash] Options hash
225
+ # @option options [Integer] :skip Number of entries to skip (default: 0)
226
+ # @option options [Array<Timeline::Route>] :only An array of routes to fetch
227
+ # @return [Array<Timeline::Entry>] An array of timeline entries
228
+ def find_timeline(timeline, limit, options = { })
229
+ options = {
230
+ :skip => 0,
231
+ :only => [ ],
232
+ }.merge(options)
233
+
234
+ options[:only] = [ options[:only] ] if (options[:only] && !options[:only].is_a?(Array))
235
+
236
+ result = self.driver.find_timeline_entries(timeline.kind, timeline.recipient_id, limit, options).map do |timeline_entry_hash|
237
+ # run hook
238
+ self.run_hook(:did_find_timeline_entry, timeline_entry_hash, timeline.class)
239
+
240
+ # unserialize
241
+ Activr::Timeline::Entry.from_hash(timeline_entry_hash, timeline)
242
+ end
243
+
244
+ result
245
+ end
246
+
247
+ # Count number of timeline entries
248
+ #
249
+ # @param timeline [Timeline] Timeline instance
250
+ # @param options [Hash] Options hash
251
+ # @option options [Array<Timeline::Route>] :only An array of routes to count
252
+ # @return [Integer] Number of timeline entries in given timeline
253
+ def count_timeline(timeline, options = { })
254
+ options = {
255
+ :only => [ ],
256
+ }.merge(options)
257
+
258
+ options[:only] = [ options[:only] ] if (options[:only] && !options[:only].is_a?(Array))
259
+
260
+ self.driver.count_timeline_entries(timeline.kind, timeline.recipient_id, options)
261
+ end
262
+
263
+ # Delete timeline entries
264
+ #
265
+ # @param timeline [Timeline] Timeline instance
266
+ # @param options [Hash] Options hash
267
+ # @option options [Time] :before Delete only timeline entries which timestamp is before that datetime (excluding)
268
+ # @option options [Hash{Sym=>String}] :entity Delete only timeline entries with these entities values
269
+ def delete_timeline(timeline, options = { })
270
+ # default options
271
+ options = {
272
+ :before => nil,
273
+ :entities => { },
274
+ }.merge(options)
275
+
276
+ self.driver.delete_timeline_entries(timeline.kind, timeline.recipient_id, options)
277
+ end
278
+
279
+ # Delete timeline entries referring to given entity model instance
280
+ #
281
+ # @param model [Object] Model instance
282
+ def delete_timeline_entries_for_entity_model(model)
283
+ Activr.registry.timeline_entities_for_model(model.class).each do |timeline_class, entities|
284
+ entities.each do |entity_name|
285
+ self.driver.delete_timeline_entries(timeline_class.kind, nil, :entities => { entity_name => model.id })
286
+ end
287
+ end
288
+ end
289
+
290
+
291
+ #
292
+ # Indexes
293
+ #
294
+
295
+ # Ensure all necessary indexes
296
+ #
297
+ # @yield [String] Created index name
298
+ def create_indexes
299
+ self.driver.create_indexes
300
+ end
301
+
302
+
303
+ #
304
+ # Hooks
305
+ #
306
+
307
+ # Hook: run just before inserting an activity document in the database
308
+ #
309
+ # @example Insert the 'foo' meta into all activities
310
+ #
311
+ # Activr.storage.will_insert_activity do |activity_hash|
312
+ # activity_hash['meta'] ||= { }
313
+ # activity_hash['meta']['foo'] = 'bar'
314
+ # end
315
+ #
316
+ def will_insert_activity(&block)
317
+ register_hook(:will_insert_activity, block)
318
+ end
319
+
320
+ # Hook: run just after fetching an activity document from the database
321
+ #
322
+ # @example Ignore the 'foo' meta
323
+ #
324
+ # Activr.storage.did_find_activity do |activity_hash|
325
+ # if activity_hash['meta']
326
+ # activity_hash['meta'].delete('foo')
327
+ # end
328
+ # end
329
+ #
330
+ def did_find_activity(&block)
331
+ register_hook(:did_find_activity, block)
332
+ end
333
+
334
+ # Hook: run just before inserting a timeline entry document in the database
335
+ #
336
+ # @example Insert the 'bar' field into all timeline entries documents
337
+ #
338
+ # Activr.storage.will_insert_timeline_entry do |timeline_entry_hash, timeline_class|
339
+ # timeline_entry_hash['bar'] = 'baz'
340
+ # end
341
+ #
342
+ def will_insert_timeline_entry(&block)
343
+ register_hook(:will_insert_timeline_entry, block)
344
+ end
345
+
346
+ # Hook: run just after fetching a timeline entry document from the database
347
+ #
348
+ # @example Ignore the 'bar' field
349
+ #
350
+ # Activr.storage.did_find_timeline_entry do |timeline_entry_hash, timeline_class|
351
+ # timeline_entry_hash.delete('bar')
352
+ # end
353
+ #
354
+ def did_find_timeline_entry(&block)
355
+ register_hook(:did_find_timeline_entry, block)
356
+ end
357
+
358
+
359
+ # Register a hook
360
+ #
361
+ # @api private
362
+ #
363
+ # @param name [Symbol] Hook name
364
+ # @param block [Proc] Hook code
365
+ def register_hook(name, block)
366
+ @hooks[name] ||= [ ]
367
+ @hooks[name] << block
368
+ end
369
+
370
+ # Get hooks
371
+ #
372
+ # @api private
373
+ # @note Returns all hooks if `name` is `nil`
374
+ #
375
+ # @param name [Symbol] Hook name
376
+ # @return [Array<Proc>] List of hooks
377
+ def hooks(name = nil)
378
+ name ? (@hooks[name] || [ ]) : @hooks
379
+ end
380
+
381
+ # Run a hook
382
+ #
383
+ # @api private
384
+ #
385
+ # @param name [Symbol] Hook name
386
+ # @param args [Array] Hook arguments
387
+ def run_hook(name, *args)
388
+ return if @hooks[name].blank?
389
+
390
+ @hooks[name].each do |hook|
391
+ args.any? ? hook.call(*args) : hook.call
392
+ end
393
+ end
394
+
395
+ # Reset all hooks
396
+ #
397
+ # @api private
398
+ def clear_hooks!
399
+ @hooks = { }
400
+ end
401
+
402
+ end # class Storage
403
+
404
+ end # module Activr