-
Notifications
You must be signed in to change notification settings - Fork 332
/
common.rb
284 lines (260 loc) · 10.1 KB
/
common.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
module PublicActivity
# Happens when creating custom activities without either action or a key.
class NoKeyProvided < Exception; end
# Used to smartly transform value from metadata to data.
# Accepts Symbols, which it will send against context.
# Accepts Procs, which it will execute with controller and context.
# @since 0.4.0
def self.resolve_value(context, thing)
case thing
when Symbol
context.__send__(thing)
when Proc
thing.call(PublicActivity.get_controller, context)
when Hash
thing.dup.tap do |hash|
hash.each do |key, value|
hash[key] = PublicActivity.resolve_value(context, value)
end
end
else
thing
end
end
# Common methods shared across the gem.
module Common
extend ActiveSupport::Concern
included do
include Trackable
class_attribute :activity_owner_global, :activity_recipient_global,
:activity_parameters_global, :activity_hooks, :activity_custom_fields_global
set_public_activity_class_defaults
end
# @!group Global options
# @!attribute activity_owner_global
# Global version of activity owner
# @see #activity_owner
# @return [Model]
# @!attribute activity_recipient_global
# Global version of activity recipient
# @see #activity_recipient
# @return [Model]
# @!attribute activity_parameters_global
# Global version of activity parameters
# @see #activity_parameters
# @return [Hash<Symbol, Object>]
# @!attribute activity_hooks
# @return [Hash<Symbol, Proc>]
# Hooks/functions that will be used to decide *if* the activity should get
# created.
#
# The supported keys are:
# * :create
# * :update
# * :destroy
# @!visibility private
@@activity_hooks = {}
# @!endgroup
# Provides some global methods for every model class.
module ClassMethods
#
# @since 1.0.0
# @api private
def set_public_activity_class_defaults
self.activity_owner_global = nil
self.activity_recipient_global = nil
self.activity_parameters_global = {}
self.activity_hooks = {}
self.activity_custom_fields_global = {}
end
# Extracts a hook from the _:on_ option provided in
# {Tracked::ClassMethods#tracked}. Returns nil when no hook exists for
# given action
# {Common#get_hook}
#
# @see Tracked#get_hook
# @param key [String, Symbol] action to retrieve a hook for
# @return [Proc, nil] callable hook or nil
# @since 0.4.0
# @api private
def get_hook(key)
key = key.to_sym
if self.activity_hooks.has_key?(key) and self.activity_hooks[key].is_a? Proc
self.activity_hooks[key]
else
nil
end
end
end
#
# Returns true if PublicActivity is enabled globally.
# @note This method gets overwritten in {Deactivatable#public_activity_enabled?}
# @return [Boolean]
# @api public
# @since 0.5.0
# @see {Deactivatable#public_activity_enabled?}
def public_activity_enabled?
PublicActivity.enabled?
end
# Calls hook safely.
# If a hook for given action exists, calls it with model (self) and
# controller (if available, see {StoreController})
# @param key (see #get_hook)
# @return [Boolean] if hook exists, it's decision, if there's no hook, true
# @since 0.4.0
# @api private
def call_hook_safe(key)
hook = self.class.get_hook(key)
if hook
# provides hook with model and controller
hook.call(self, PublicActivity.get_controller)
else
true
end
end
# Records activity in the database, based on supplied options and configuration in
# {Tracked}.
#
# If {Tracked} is used and configured for this model, `create_activity`
# will also gather data as defined in {Tracked}. If any parameters passed here
# conflict with options defined in {Tracked}, they get precedence over options
# defined in {Tracked::ClassMethods#tracked}.
#
# Whether or not {Tracked} is used, you can provide objects, symbols and procs
# as values for all parameters of activity. See {PublicActivity.resolve_value} for available
# value types.
#
# If {Tracked} is used and hooks are provided, they will be called upon to decide
# if this method should really record an activity. To discard defined hooks and create
# the activity unconditionally, use {PublicActivity::Activity} directly.
#
# # Examples
# current_user.create_activity(:avatar_changed)
# @article.create_activity(action: :commented_on, :owner => current_user)
# @post.create_activity(key: 'blog_post.published', parameters: {words_count: 50})
#
# # Activity Key
# The key will be generated from either:
#
# * the first parameter you pass that is not a hash (`action`)
# * the _:action_ option in the options hash (`action`)
# * the _:key_ option in the options hash ( **full key** )
#
# -------------------
# When you pass an *action* (first two options above), they will be
# added to parameterized model name:
#
# Example:
#
# @article.create_activity :commented_on #=> #<Activity key: 'article.commented_on' ...>
# @article.create_activity action: :commented_on #=> #<Activity key: 'article.commented_on' ...>
# # note the prefix when passing in `key`
# @article.create_activity key: 'article.commented_on' #=> #<Activity key: 'article.commented_on' ...>
# # Options
# Besides `:action` and `:key` covered above, you can pass options
# such as `:owner`, `:parameters`, `:recipient`. In addition, if you've configured any
# *custom fields*, you can pass them in here too.
#
# ## Example
#
# @article.create_activity :commented_on, weather_outside: :sunny
#
# @note This method won't create the activity if hooks reject creation.
# @note Options passed in to this method will take precedence over defaults
# configured in {Tracked}.
#
# @return [PublicActivity::Activity, nil] If created successfully, returns new activity
# @since 0.4.0
# @api public
# @overload create_activity(action, options = {})
# @param [Symbol,String] action Name of the action, will be prefixed
# @param [Hash] options Options with quality higher than instance options
# set in {Tracked#activity}
# @option options [Activist] :owner Owner
# @option options [Activist] :recipient Recipient
# @option options [Hash] :parameters Parameters, see
# {PublicActivity.resolve_value}
# @overload create_activity(options = {})
# @param [Hash] options Options with quality higher than instance options
# set in {Tracked#activity}
# @option options [Symbol,String] :action Name of the action, will be prefixed
# @option options [String] :key Full key, won't be prefixed
# @option options [Activist] :owner Owner
# @option options [Activist] :recipient Recipient
# @option options [Hash] :parameters Parameters, see
# {PublicActivity.resolve_value}
def create_activity(*args)
return unless self.public_activity_enabled?
options = prepare_settings(*args)
if call_hook_safe(options[:key].split('.').last)
return PublicActivity::Adapter.create_activity(self, options)
end
nil
end
# Prepares settings used during creation of Activity record.
# parameters passed directly to tracked model have priority over
# settings specified in tracked() method
#
# @see #create_activity
# @return [Hash] Settings with preserved options that were passed
# @api private
# @overload prepare_settings(action, options = {})
# @see #create_activity
# @overload prepare_settings(options = {})
# @see #create_activity
def prepare_settings(*args)
raw_options = args.extract_options!
action = [args.first, raw_options.delete(:action)].compact.first
key = prepare_key(action, raw_options)
raise NoKeyProvided, "No key provided for #{self.class.name}" unless key
prepare_custom_fields(raw_options.except(:parameters, :params)).merge(
{
key: key,
owner: prepare_relation(:owner, raw_options),
recipient: prepare_relation(:recipient, raw_options),
parameters: prepare_parameters(raw_options.delete(:parameters)),
}
)
end
# Prepares and resolves custom fields
# users can pass to `tracked` method
# @api private
def prepare_custom_fields(options)
customs = self.class.activity_custom_fields_global.clone
customs.merge!(options)
customs.each do |k, v|
customs[k] = PublicActivity.resolve_value(self, v)
end
end
# Prepares i18n parameters that will
# be serialized into the Activity#parameters column.
# If a Symbol or a Proc is passed, it will be resolved
# expecting to return a Hash.
# @api private
def prepare_parameters(parameters)
parameters ||= {}
[self.class.activity_parameters_global, parameters].reduce({}) do |params, value|
params.merge!(PublicActivity.resolve_value(self, value))
end
end
# Prepares relation to be saved
# to Activity. Can be :recipient or :owner
# @api private
def prepare_relation(name, options)
PublicActivity.resolve_value(self,
(options.has_key?(name) ? options[name] : self.class.send("activity_#{name}_global"))
)
end
# Helper method to serialize class name into relevant key
# @return [String] the resulted key
# @param [Symbol | String] action the name of the operation to be done on class
# @param [Hash] options to be used on key generation, defaults to {}
# @api private
def prepare_key(action, options = {})
(
options[:key] ||
((self.class.name.underscore.gsub('/', '_') + "." + action.to_s) if action)
).try(:to_s)
end
end
end