vanity 3.1.0 → 4.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +28 -0
  3. data/.github/workflows/test.yml +3 -6
  4. data/.rubocop.yml +114 -0
  5. data/.rubocop_todo.yml +67 -0
  6. data/Appraisals +9 -31
  7. data/CHANGELOG +5 -0
  8. data/Gemfile +7 -3
  9. data/Gemfile.lock +31 -3
  10. data/README.md +4 -9
  11. data/Rakefile +25 -24
  12. data/bin/vanity +1 -1
  13. data/doc/configuring.textile +1 -0
  14. data/gemfiles/rails52.gemfile +6 -3
  15. data/gemfiles/rails52.gemfile.lock +34 -9
  16. data/gemfiles/rails60.gemfile +6 -3
  17. data/gemfiles/rails60.gemfile.lock +34 -9
  18. data/gemfiles/rails61.gemfile +6 -3
  19. data/gemfiles/rails61.gemfile.lock +34 -9
  20. data/lib/generators/vanity/migration_generator.rb +5 -7
  21. data/lib/vanity/adapters/abstract_adapter.rb +43 -45
  22. data/lib/vanity/adapters/active_record_adapter.rb +30 -30
  23. data/lib/vanity/adapters/mock_adapter.rb +14 -18
  24. data/lib/vanity/adapters/mongodb_adapter.rb +73 -69
  25. data/lib/vanity/adapters/redis_adapter.rb +19 -27
  26. data/lib/vanity/adapters.rb +1 -1
  27. data/lib/vanity/autoconnect.rb +6 -7
  28. data/lib/vanity/commands/list.rb +7 -7
  29. data/lib/vanity/commands/report.rb +18 -22
  30. data/lib/vanity/configuration.rb +19 -19
  31. data/lib/vanity/connection.rb +12 -14
  32. data/lib/vanity/experiment/ab_test.rb +82 -70
  33. data/lib/vanity/experiment/alternative.rb +3 -5
  34. data/lib/vanity/experiment/base.rb +24 -19
  35. data/lib/vanity/experiment/bayesian_bandit_score.rb +7 -13
  36. data/lib/vanity/experiment/definition.rb +6 -6
  37. data/lib/vanity/frameworks/rails.rb +39 -39
  38. data/lib/vanity/frameworks.rb +2 -2
  39. data/lib/vanity/helpers.rb +1 -1
  40. data/lib/vanity/metric/active_record.rb +21 -19
  41. data/lib/vanity/metric/base.rb +22 -23
  42. data/lib/vanity/metric/google_analytics.rb +6 -9
  43. data/lib/vanity/metric/remote.rb +3 -5
  44. data/lib/vanity/playground.rb +3 -6
  45. data/lib/vanity/vanity.rb +8 -12
  46. data/lib/vanity/version.rb +1 -1
  47. data/test/adapters/active_record_adapter_test.rb +1 -5
  48. data/test/adapters/mock_adapter_test.rb +0 -2
  49. data/test/adapters/mongodb_adapter_test.rb +1 -5
  50. data/test/adapters/redis_adapter_test.rb +2 -3
  51. data/test/adapters/shared_tests.rb +9 -12
  52. data/test/autoconnect_test.rb +3 -3
  53. data/test/cli_test.rb +0 -1
  54. data/test/configuration_test.rb +18 -34
  55. data/test/connection_test.rb +3 -3
  56. data/test/dummy/Rakefile +1 -1
  57. data/test/dummy/app/controllers/use_vanity_controller.rb +12 -8
  58. data/test/dummy/app/mailers/vanity_mailer.rb +3 -3
  59. data/test/dummy/config/application.rb +1 -1
  60. data/test/dummy/config/boot.rb +3 -3
  61. data/test/dummy/config/environment.rb +1 -1
  62. data/test/dummy/config/environments/development.rb +0 -1
  63. data/test/dummy/config/environments/test.rb +1 -1
  64. data/test/dummy/config/initializers/session_store.rb +1 -1
  65. data/test/dummy/config.ru +1 -1
  66. data/test/dummy/script/rails +2 -2
  67. data/test/experiment/ab_test.rb +148 -154
  68. data/test/experiment/base_test.rb +48 -32
  69. data/test/frameworks/rails/action_controller_test.rb +25 -25
  70. data/test/frameworks/rails/action_mailer_test.rb +2 -2
  71. data/test/frameworks/rails/action_view_test.rb +5 -6
  72. data/test/frameworks/rails/rails_test.rb +147 -181
  73. data/test/helper_test.rb +2 -2
  74. data/test/metric/active_record_test.rb +174 -212
  75. data/test/metric/base_test.rb +21 -46
  76. data/test/metric/google_analytics_test.rb +17 -25
  77. data/test/metric/remote_test.rb +7 -10
  78. data/test/playground_test.rb +7 -14
  79. data/test/templates_test.rb +16 -20
  80. data/test/test_helper.rb +28 -29
  81. data/test/vanity_test.rb +4 -10
  82. data/test/web/rails/dashboard_test.rb +21 -21
  83. data/vanity.gemspec +8 -7
  84. metadata +28 -30
  85. data/gemfiles/rails42.gemfile +0 -33
  86. data/gemfiles/rails42.gemfile.lock +0 -265
  87. data/gemfiles/rails42_protected_attributes.gemfile +0 -34
  88. data/gemfiles/rails42_protected_attributes.gemfile.lock +0 -264
  89. data/gemfiles/rails51.gemfile +0 -33
  90. data/gemfiles/rails51.gemfile.lock +0 -285
@@ -8,14 +8,14 @@ module Vanity
8
8
  # metrics :signup
9
9
  # end
10
10
  module Definition
11
-
12
11
  attr_reader :playground
13
12
 
14
13
  # Defines a new experiment, given the experiment's name, type and
15
14
  # definition block.
16
15
  def define(name, type, options = nil, &block)
17
- fail "Experiment #{@experiment_id} already defined in playground" if playground.experiments[@experiment_id]
18
- klass = Experiment.const_get(type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
16
+ raise "Experiment #{@experiment_id} already defined in playground" if playground.experiments[@experiment_id]
17
+
18
+ klass = Experiment.const_get(type.to_s.gsub(/\/(.?)/) { "::#{Regexp.last_match(1).upcase}" }.gsub(/(?:^|_)(.)/) { Regexp.last_match(1).upcase })
19
19
  experiment = klass.new(playground, @experiment_id, name, options)
20
20
  experiment.instance_eval(&block)
21
21
  experiment.save
@@ -23,10 +23,10 @@ module Vanity
23
23
  end
24
24
 
25
25
  def new_binding(playground, id)
26
- @playground, @experiment_id = playground, id
26
+ @playground = playground
27
+ @experiment_id = id
27
28
  binding
28
29
  end
29
-
30
30
  end
31
31
  end
32
- end
32
+ end
@@ -69,7 +69,7 @@ module Vanity
69
69
  @vanity_identity
70
70
  elsif cookies[Vanity.configuration.cookie_name]
71
71
  @vanity_identity = cookies[Vanity.configuration.cookie_name]
72
- elsif identity = vanity_identity_from_method(vanity_identity_method)
72
+ elsif identity = vanity_identity_from_method(vanity_identity_method) # rubocop:todo Lint/AssignmentInCondition
73
73
  @vanity_identity = identity
74
74
  elsif response # everyday use
75
75
  @vanity_identity = cookies[Vanity.configuration.cookie_name] || SecureRandom.hex(16)
@@ -96,7 +96,7 @@ module Vanity
96
96
  path: Vanity.configuration.cookie_path,
97
97
  domain: Vanity.configuration.cookie_domain,
98
98
  secure: Vanity.configuration.cookie_secure,
99
- httponly: Vanity.configuration.cookie_httponly
99
+ httponly: Vanity.configuration.cookie_httponly,
100
100
  }
101
101
  result[:domain] ||= ::Rails.application.config.session_options[:domain]
102
102
  result
@@ -118,13 +118,13 @@ module Vanity
118
118
  if symbol && (@object = symbol)
119
119
  class << self
120
120
  define_method :vanity_identity do
121
- @vanity_identity = (String === @object ? @object : @object.id)
121
+ @vanity_identity = (@object.is_a?(String) ? @object : @object.id)
122
122
  end
123
123
  end
124
124
  else
125
125
  class << self
126
126
  define_method :vanity_identity do
127
- @vanity_identity = @vanity_identity || SecureRandom.hex(16)
127
+ @vanity_identity ||= SecureRandom.hex(16)
128
128
  end
129
129
  end
130
130
  end
@@ -137,7 +137,8 @@ module Vanity
137
137
  module Filters
138
138
  # Around filter that sets Vanity.context to controller.
139
139
  def vanity_context_filter
140
- previous, Vanity.context = Vanity.context, self
140
+ previous = Vanity.context
141
+ Vanity.context = self
141
142
  yield
142
143
  ensure
143
144
  Vanity.context = previous
@@ -158,9 +159,9 @@ module Vanity
158
159
  # http://example.com/.
159
160
  def vanity_query_parameter_filter
160
161
  query_params = request.query_parameters
161
- if request.get? && query_params[:_vanity]
162
+ if request.get? && query_params[:_vanity] # rubocop:todo Style/GuardClause
162
163
  hashes = Array(query_params.delete(:_vanity))
163
- Vanity.playground.experiments.each do |id, experiment|
164
+ Vanity.playground.experiments.each do |_id, experiment|
164
165
  if experiment.respond_to?(:alternatives)
165
166
  experiment.alternatives.each do |alt|
166
167
  if hashes.delete(experiment.fingerprint(alt))
@@ -185,15 +186,12 @@ module Vanity
185
186
  # Filter to track metrics. Pass _track param along to call track! on that
186
187
  # alternative.
187
188
  def vanity_track_filter
188
- if request.get? && params[:_track]
189
- Vanity.track! params[:_track]
190
- end
189
+ Vanity.track! params[:_track] if request.get? && params[:_track]
191
190
  end
192
191
 
193
192
  protected :vanity_context_filter, :vanity_query_parameter_filter, :vanity_reload_filter
194
193
  end
195
194
 
196
-
197
195
  # Introduces ab_test helper (controllers and views). Similar to the generic
198
196
  # ab_test method, with the ability to capture content (applicable to views,
199
197
  # see examples).
@@ -221,13 +219,13 @@ module Vanity
221
219
  # <%= count %> features to choose from!
222
220
  # <% end %>
223
221
  def ab_test(name, &block)
224
- current_request = respond_to?(:request) ? self.request : nil
222
+ current_request = respond_to?(:request) ? request : nil
225
223
  value = Vanity.ab_test(name, current_request)
226
224
 
227
225
  if block
228
226
  content = capture(value, &block)
229
227
  if defined?(block_called_from_erb?) && block_called_from_erb?(block)
230
- concat(content)
228
+ concat(content)
231
229
  else
232
230
  content
233
231
  end
@@ -238,20 +236,21 @@ module Vanity
238
236
 
239
237
  # Generate url with the identity of the current user and the metric to track on click
240
238
  def vanity_track_url_for(identity, metric, options = {})
241
- options = options.merge(:_identity => identity, :_track => metric)
239
+ options = options.merge(_identity: identity, _track: metric)
242
240
  url_for(options)
243
241
  end
244
242
 
245
243
  # Generate url with the fingerprint for the current Vanity experiment
246
244
  def vanity_tracking_image(identity, metric, options = {})
247
- options = options.merge(:controller => :vanity, :action => :image, :_identity => identity, :_track => metric)
248
- image_tag(url_for(options), :width => "1px", :height => "1px", :alt => "")
245
+ options = options.merge(controller: :vanity, action: :image, _identity: identity, _track: metric)
246
+ image_tag(url_for(options), width: "1px", height: "1px", alt: "")
249
247
  end
250
248
 
251
249
  def vanity_js
252
250
  return if Vanity.context.vanity_active_experiments.nil? || Vanity.context.vanity_active_experiments.empty?
251
+
253
252
  javascript_tag do
254
- render :file => Vanity.template("_vanity.js.erb"), :formats => [:js]
253
+ render file: Vanity.template("_vanity.js.erb"), formats: [:js]
255
254
  end
256
255
  end
257
256
 
@@ -267,7 +266,7 @@ module Vanity
267
266
  end
268
267
  end
269
268
 
270
- def vanity_simple_format(text, html_options={})
269
+ def vanity_simple_format(text, html_options = {})
271
270
  vanity_html_safe(simple_format(text, html_options))
272
271
  end
273
272
 
@@ -299,7 +298,6 @@ module Vanity
299
298
  end
300
299
  end
301
300
 
302
-
303
301
  # When configuring use_js to true, you must set up a route to
304
302
  # add_participant_route.
305
303
  #
@@ -323,17 +321,21 @@ module Vanity
323
321
  h = {}
324
322
  params[:v].split(',').each do |pair|
325
323
  exp_id, answer = pair.split('=')
326
- exp = Vanity.playground.experiment(exp_id.to_s.to_sym) rescue nil
324
+ exp = begin
325
+ Vanity.playground.experiment(exp_id.to_s.to_sym)
326
+ rescue StandardError
327
+ nil
328
+ end
327
329
  answer = answer.to_i
328
330
 
329
331
  if !exp || !exp.alternatives[answer]
330
332
  head 404
331
- return
333
+ return # rubocop:todo Lint/NonLocalExitFromIterator
332
334
  end
333
335
  h[exp] = exp.alternatives[answer].value
334
336
  end
335
337
 
336
- h.each{ |e,a| e.chooses(a, request) }
338
+ h.each { |e, a| e.chooses(a, request) }
337
339
  head 200
338
340
  end
339
341
  end
@@ -354,16 +356,16 @@ module Vanity
354
356
 
355
357
  def index
356
358
  set_vanity_view_path
357
- render :template=>"_report", :content_type=>Mime[:html], :locals=>{
358
- :experiments=>Vanity.playground.experiments,
359
- :experiments_persisted=>Vanity.playground.experiments_persisted?,
360
- :metrics=>Vanity.playground.metrics
359
+ render template: "_report", content_type: Mime[:html], locals: {
360
+ experiments: Vanity.playground.experiments,
361
+ experiments_persisted: Vanity.playground.experiments_persisted?,
362
+ metrics: Vanity.playground.metrics,
361
363
  }
362
364
  end
363
365
 
364
366
  def participant
365
367
  set_vanity_view_path
366
- render :template=>"_participant", :locals=>{:participant_id => params[:id], :participant_info => Vanity.playground.participant_info(params[:id])}, :content_type=>Mime[:html]
368
+ render template: "_participant", locals: { participant_id: params[:id], participant_info: Vanity.playground.participant_info(params[:id]) }, content_type: Mime[:html]
367
369
  end
368
370
 
369
371
  def complete
@@ -372,12 +374,12 @@ module Vanity
372
374
  alt = exp.alternatives[params[:a].to_i]
373
375
  confirmed = params[:confirmed]
374
376
  # make the user confirm before completing an experiment
375
- if confirmed && confirmed.to_i==alt.id && exp.active?
377
+ if confirmed && confirmed.to_i == alt.id && exp.active?
376
378
  exp.complete!(alt.id)
377
- render :template=>"_experiment", :locals=>{:experiment=>exp}
379
+ render template: "_experiment", locals: { experiment: exp }
378
380
  else
379
381
  @to_confirm = alt.id
380
- render :template=>"_experiment", :locals=>{:experiment=>exp}
382
+ render template: "_experiment", locals: { experiment: exp }
381
383
  end
382
384
  end
383
385
 
@@ -385,21 +387,21 @@ module Vanity
385
387
  set_vanity_view_path
386
388
  exp = Vanity.playground.experiment(params[:e].to_sym)
387
389
  exp.enabled = false
388
- render :template=>"_experiment", :locals=>{:experiment=>exp}
390
+ render template: "_experiment", locals: { experiment: exp }
389
391
  end
390
392
 
391
393
  def enable
392
394
  set_vanity_view_path
393
395
  exp = Vanity.playground.experiment(params[:e].to_sym)
394
396
  exp.enabled = true
395
- render :template=>"_experiment", :locals=>{:experiment=>exp}
397
+ render template: "_experiment", locals: { experiment: exp }
396
398
  end
397
399
 
398
400
  def chooses
399
401
  set_vanity_view_path
400
402
  exp = Vanity.playground.experiment(params[:e].to_sym)
401
403
  exp.chooses(exp.alternatives[params[:a].to_i].value)
402
- render :template=>"_experiment", :locals=>{:experiment=>exp}
404
+ render template: "_experiment", locals: { experiment: exp }
403
405
  end
404
406
 
405
407
  def reset
@@ -407,7 +409,7 @@ module Vanity
407
409
  exp = Vanity.playground.experiment(params[:e].to_sym)
408
410
  exp.reset
409
411
  flash[:notice] = I18n.t 'vanity.experiment_has_been_reset', name: exp.name
410
- render :template=>"_experiment", :locals=>{:experiment=>exp}
412
+ render template: "_experiment", locals: { experiment: exp }
411
413
  end
412
414
 
413
415
  include AddParticipant
@@ -416,13 +418,12 @@ module Vanity
416
418
  module TrackingImage
417
419
  def image
418
420
  # send image
419
- send_file(File.expand_path("../images/x.gif", File.dirname(__FILE__)), :type => 'image/gif', :stream => false, :disposition => 'inline')
421
+ send_file(File.expand_path("../images/x.gif", File.dirname(__FILE__)), type: 'image/gif', stream: false, disposition: 'inline')
420
422
  end
421
423
  end
422
424
  end
423
425
  end
424
426
 
425
-
426
427
  # Enhance ActionController with use_vanity, filters and helper methods.
427
428
  ActiveSupport.on_load(:action_controller) do
428
429
  # Include in controller, add view helper methods.
@@ -434,7 +435,6 @@ ActiveSupport.on_load(:action_controller) do
434
435
  end
435
436
  end
436
437
 
437
-
438
438
  # Include in mailer, add view helper methods.
439
439
  ActiveSupport.on_load(:action_mailer) do
440
440
  include Vanity::Rails::UseVanityMailer
@@ -448,8 +448,8 @@ if defined?(PhusionPassenger)
448
448
  if forked
449
449
  begin
450
450
  Vanity.playground.reconnect! if Vanity.playground.collecting?
451
- rescue Exception=>ex
452
- Rails.logger.error "Error reconnecting: #{ex.to_s}"
451
+ rescue Exception => e # rubocop:todo Lint/RescueException
452
+ Rails.logger.error "Error reconnecting: #{e}"
453
453
  end
454
454
  end
455
455
  end
@@ -1,8 +1,8 @@
1
- # TODO turn this into a real rails engine jobbie
1
+ # TODO: turn this into a real rails engine jobbie
2
2
  # Automatically configure Vanity.
3
3
  if defined?(Rails)
4
4
  class Plugin < Rails::Railtie # :nodoc:
5
- initializer "vanity.require" do |app|
5
+ initializer "vanity.require" do |_app|
6
6
  require 'vanity/frameworks/rails'
7
7
 
8
8
  Vanity::Rails.load!
@@ -32,7 +32,7 @@ module Vanity
32
32
  # render action: Vanity.ab_test(:new_page)
33
33
  # end
34
34
  # @since 1.2.0
35
- def ab_test(name, request=nil, &block)
35
+ def ab_test(name, request = nil, &block)
36
36
  request ||= Vanity.context.respond_to?(:request) ? Vanity.context.request : nil
37
37
 
38
38
  alternative = Vanity.playground.experiment(name).choose(request)
@@ -1,6 +1,5 @@
1
1
  module Vanity
2
2
  class Metric
3
-
4
3
  AGGREGATES = [:average, :minimum, :maximum, :sum]
5
4
 
6
5
  # Use an ActiveRecord model to get metric data from database table. Also
@@ -35,15 +34,15 @@ module Vanity
35
34
  # @since 1.2.0
36
35
  # @see Vanity::Metric::ActiveRecord
37
36
  def model(class_or_scope, options = nil)
38
- ActiveSupport.on_load(:active_record, :yield=>true) do
37
+ ActiveSupport.on_load(:active_record, yield: true) do
39
38
  class_or_scope = class_or_scope.constantize if class_or_scope.is_a?(String)
40
- options = options || {}
39
+ options ||= {}
41
40
  conditions = options.delete(:conditions)
42
41
 
43
42
  @ar_scoped = conditions ? class_or_scope.where(conditions) : class_or_scope
44
43
  @ar_aggregate = AGGREGATES.find { |key| options.has_key?(key) }
45
44
  @ar_column = options.delete(@ar_aggregate)
46
- fail "Cannot use multiple aggregates in a single metric" if AGGREGATES.find { |key| options.has_key?(key) }
45
+ raise "Cannot use multiple aggregates in a single metric" if AGGREGATES.find { |key| options.has_key?(key) }
47
46
 
48
47
  @ar_timestamp = options.delete(:timestamp) || :created_at
49
48
  @ar_timestamp, @ar_timestamp_table = @ar_timestamp.to_s.split('.').reverse
@@ -51,7 +50,7 @@ module Vanity
51
50
 
52
51
  @ar_identity_block = options.delete(:identity)
53
52
 
54
- fail "Unrecognized options: #{options.keys * ", "}" unless options.empty?
53
+ raise "Unrecognized options: #{options.keys * ', '}" unless options.empty?
55
54
 
56
55
  @ar_scoped.after_create(self)
57
56
  extend ActiveRecord
@@ -63,31 +62,31 @@ module Vanity
63
62
  #
64
63
  # @since 1.3.0
65
64
  module ActiveRecord
66
-
67
65
  # This values method queries the database.
68
66
  def values(sdate, edate)
69
67
  time = Time.now.in_time_zone
70
68
  difference = time.to_date - Date.today
71
- sdate = sdate + difference
72
- edate = edate + difference
69
+ sdate += difference
70
+ edate += difference
73
71
 
74
72
  grouped = @ar_scoped
75
- .where(@ar_timestamp_table => { @ar_timestamp => (sdate.to_time...(edate + 1).to_time) })
76
- .group("date(#{@ar_scoped.quoted_table_name}.#{@ar_scoped.connection.quote_column_name(@ar_timestamp)})")
73
+ .where(@ar_timestamp_table => { @ar_timestamp => (sdate.to_time...(edate + 1).to_time) })
74
+ .group("date(#{@ar_scoped.quoted_table_name}.#{@ar_scoped.connection.quote_column_name(@ar_timestamp)})")
77
75
 
78
- if @ar_column
79
- grouped = grouped.send(@ar_aggregate, @ar_column)
80
- else
81
- grouped = grouped.count
82
- end
76
+ grouped = if @ar_column
77
+ grouped.send(@ar_aggregate, @ar_column)
78
+ else
79
+ grouped.count
80
+ end
83
81
 
84
- grouped = Hash[grouped.map {|k,v| [k.to_date, v] }]
82
+ grouped = grouped.map { |k, v| [k.to_date, v] }.to_h
85
83
  (sdate..edate).inject([]) { |ordered, date| ordered << (grouped[date] || 0) }
86
84
  end
87
85
 
88
86
  # This track! method stores nothing, but calls the hooks.
89
87
  def track!(args = nil)
90
88
  return unless @playground.collecting?
89
+
91
90
  call_hooks(*track_args(args))
92
91
  end
93
92
 
@@ -100,12 +99,15 @@ module Vanity
100
99
  # AR model after_create callback notifies all the hooks.
101
100
  def after_create(record)
102
101
  return unless @playground.collecting?
102
+
103
103
  count = @ar_column ? (record.send(@ar_column) || 0) : 1
104
104
 
105
- identity = Vanity.context.vanity_identity rescue nil
106
- identity ||= if @ar_identity_block
107
- @ar_identity_block.call(record)
105
+ identity = begin
106
+ Vanity.context.vanity_identity
107
+ rescue StandardError
108
+ nil
108
109
  end
110
+ identity ||= (@ar_identity_block.call(record) if @ar_identity_block)
109
111
 
110
112
  call_hooks record.send(@ar_timestamp), identity, [count] if count > 0 && @ar_scoped.exists?(record.id)
111
113
  end
@@ -1,5 +1,4 @@
1
1
  module Vanity
2
-
3
2
  # A metric is an object that implements two methods: +name+ and +values+. It
4
3
  # can also respond to addition methods (+track!+, +bounds+, etc), these are
5
4
  # optional.
@@ -10,7 +9,6 @@ module Vanity
10
9
  #
11
10
  # @since 1.1.0
12
11
  class Metric
13
-
14
12
  # These methods are available when defining a metric in a file loaded
15
13
  # from the +experiments/metrics+ directory.
16
14
  #
@@ -20,22 +18,22 @@ module Vanity
20
18
  # description "Most boring metric ever"
21
19
  # end
22
20
  module Definition
23
-
24
21
  attr_reader :playground
25
22
 
26
23
  # Defines a new metric, using the class Vanity::Metric.
27
24
  def metric(name, &block)
28
- fail "Metric #{@metric_id} already defined in playground" if playground.metrics[@metric_id]
25
+ raise "Metric #{@metric_id} already defined in playground" if playground.metrics[@metric_id]
26
+
29
27
  metric = Metric.new(playground, name.to_s, @metric_id)
30
28
  metric.instance_eval(&block)
31
29
  playground.metrics[@metric_id] = metric
32
30
  end
33
31
 
34
32
  def new_binding(playground, id)
35
- @playground, @metric_id = playground, id
33
+ @playground = playground
34
+ @metric_id = id
36
35
  binding
37
36
  end
38
-
39
37
  end
40
38
 
41
39
  # Startup metrics for pirates. AARRR stands for:
@@ -46,7 +44,6 @@ module Vanity
46
44
  # * Revenue
47
45
  # Read more: http://500hats.typepad.com/500blogs/2007/09/startup-metrics.html
48
46
  class << self
49
-
50
47
  # Helper method to return description for a metric.
51
48
  #
52
49
  # A metric object may have a +description+ method that returns a detailed
@@ -68,7 +65,7 @@ module Vanity
68
65
  # @example
69
66
  # upper = Vanity::Metric.bounds(metric).last
70
67
  def bounds(metric)
71
- metric.respond_to?(:bounds) && metric.bounds || [nil, nil]
68
+ (metric.respond_to?(:bounds) && metric.bounds) || [nil, nil]
72
69
  end
73
70
 
74
71
  # Returns data set for a given date range. The data set is an array of
@@ -92,37 +89,37 @@ module Vanity
92
89
 
93
90
  # Playground uses this to load metric definitions.
94
91
  def load(playground, stack, file)
95
- fail "Circular dependency detected: #{stack.join('=>')}=>#{file}" if stack.include?(file)
92
+ raise "Circular dependency detected: #{stack.join('=>')}=>#{file}" if stack.include?(file)
93
+
96
94
  source = File.read(file)
97
95
  stack.push file
98
96
  id = File.basename(file, ".rb").downcase.gsub(/\W/, "_").to_sym
99
97
  context = Object.new
100
98
  context.instance_eval do
101
99
  extend Definition
102
- metric = eval(source, context.new_binding(playground, id), file)
103
- fail NameError.new("Expected #{file} to define metric #{id}", id) unless playground.metrics[id]
100
+ metric = eval(source, context.new_binding(playground, id), file) # rubocop:todo Security/Eval
101
+ raise NameError.new("Expected #{file} to define metric #{id}", id) unless playground.metrics[id]
102
+
104
103
  metric
105
104
  end
106
- rescue
105
+ rescue StandardError
107
106
  error = NameError.exception($!.message, id)
108
107
  error.set_backtrace $!.backtrace
109
108
  raise error
110
109
  ensure
111
110
  stack.pop
112
111
  end
113
-
114
112
  end
115
113
 
116
-
117
114
  # Takes playground (need this to access Redis), friendly name and optional
118
115
  # id (can infer from name).
119
116
  def initialize(playground, name, id = nil)
120
- @playground, @name = playground, name.to_s
117
+ @playground = playground
118
+ @name = name.to_s
121
119
  @id = (id || name.to_s.downcase.gsub(/\W+/, '_')).to_sym
122
120
  @hooks = []
123
121
  end
124
122
 
125
-
126
123
  # -- Tracking --
127
124
 
128
125
  # Called to track an action associated with this metric. Most common is not
@@ -135,9 +132,10 @@ module Vanity
135
132
  # foo_and_bar.track! [5,11]
136
133
  def track!(args = nil)
137
134
  return unless @playground.collecting?
135
+
138
136
  timestamp, identity, values = track_args(args)
139
137
  connection.metric_track @id, timestamp, identity, values
140
- @playground.logger.info "vanity: #{@id} with value #{values.join(", ")}"
138
+ @playground.logger.info "vanity: #{@id} with value #{values.join(', ')}"
141
139
  call_hooks timestamp, identity, values
142
140
  end
143
141
 
@@ -152,7 +150,11 @@ module Vanity
152
150
  when Numeric
153
151
  values = [args]
154
152
  end
155
- identity ||= Vanity.context.vanity_identity rescue nil
153
+ identity ||= begin
154
+ Vanity.context.vanity_identity
155
+ rescue StandardError
156
+ nil
157
+ end
156
158
  [timestamp || Time.now, identity, values || [1]]
157
159
  end
158
160
  protected :track_args
@@ -183,12 +185,11 @@ module Vanity
183
185
  def bounds
184
186
  end
185
187
 
186
-
187
188
  # -- Reporting --
188
189
 
189
190
  # Human readable metric name. All metrics must implement this method.
190
191
  attr_reader :name
191
- alias :to_s :name
192
+ alias to_s name
192
193
 
193
194
  # Human readable description. Use two newlines to break paragraphs.
194
195
  attr_writer :description
@@ -218,7 +219,6 @@ module Vanity
218
219
  connection.get_metric_last_update_at(@id)
219
220
  end
220
221
 
221
-
222
222
  # -- Storage --
223
223
 
224
224
  def destroy!
@@ -235,9 +235,8 @@ module Vanity
235
235
 
236
236
  def call_hooks(timestamp, identity, values)
237
237
  @hooks.each do |hook|
238
- hook.call @id, timestamp, values.first || 1, :identity=>identity
238
+ hook.call @id, timestamp, values.first || 1, identity: identity
239
239
  end
240
240
  end
241
-
242
241
  end
243
242
  end
@@ -1,6 +1,5 @@
1
1
  module Vanity
2
2
  class Metric
3
-
4
3
  # Use Google Analytics metric. Note: you must +require "garb"+ before
5
4
  # vanity.
6
5
  #
@@ -17,13 +16,13 @@ module Vanity
17
16
  # @see Vanity::Metric::GoogleAnalytics
18
17
  def google_analytics(web_property_id, *args)
19
18
  require "garb"
20
- options = Hash === args.last ? args.pop : {}
19
+ options = args.last.is_a?(Hash) ? args.pop : {}
21
20
  metric = args.shift || :pageviews
22
21
  @ga_resource = Vanity::Metric::GoogleAnalytics::Resource.new(web_property_id, metric)
23
- @ga_mapper = options[:mapper] ||= lambda { |entry| entry.send(@ga_resource.metrics.elements.first).to_i }
22
+ @ga_mapper = options[:mapper] ||= ->(entry) { entry.send(@ga_resource.metrics.elements.first).to_i }
24
23
  extend GoogleAnalytics
25
24
  rescue LoadError
26
- fail LoadError, "Google Analytics metrics require Garb, please gem install garb first"
25
+ raise LoadError, "Google Analytics metrics require Garb, please gem install garb first"
27
26
  end
28
27
 
29
28
  # Calling google_analytics method on a metric extends it with these modules,
@@ -31,19 +30,18 @@ module Vanity
31
30
  #
32
31
  # @since 1.3.0
33
32
  module GoogleAnalytics
34
-
35
33
  # Returns values from GA using parameters specified by prior call to
36
34
  # google_analytics.
37
35
  def values(from, to)
38
- data = @ga_resource.results(from, to).inject({}) do |hash,entry|
39
- hash.merge(entry.date=>@ga_mapper.call(entry))
36
+ data = @ga_resource.results(from, to).inject({}) do |hash, entry|
37
+ hash.merge(entry.date => @ga_mapper.call(entry))
40
38
  end
41
39
  (from..to).map { |day| data[day.strftime('%Y%m%d')] || 0 }
42
40
  end
43
41
 
44
42
  # Hooks not supported for GA metrics.
45
43
  def hook
46
- fail "Cannot use hooks with Google Analytics methods"
44
+ raise "Cannot use hooks with Google Analytics methods"
47
45
  end
48
46
 
49
47
  # Garb report.
@@ -77,7 +75,6 @@ module Vanity
77
75
  Garb::ReportResponse.new(send_request_for_body).results
78
76
  end
79
77
  end
80
-
81
78
  end
82
79
  end
83
80
  end
@@ -3,7 +3,6 @@ require "cgi"
3
3
 
4
4
  module Vanity
5
5
  class Metric
6
-
7
6
  # Specifies the base URL to use for a remote metric. For example:
8
7
  # metric :sandbox do
9
8
  # remote "http://api.vanitydash.com/metrics/sandbox"
@@ -26,9 +25,9 @@ module Vanity
26
25
  # - Set values by their index using +values[0]+, +values[1]+, etc or
27
26
  # - Set values by series name using +values[foo]+, +values[bar]+, etc.
28
27
  module Remote
29
-
30
28
  def track!(args = nil)
31
29
  return unless @playground.collecting?
30
+
32
31
  timestamp, identity, values = track_args(args)
33
32
  params = ["metric=#{CGI.escape @id.to_s}", "timestamp=#{CGI.escape timestamp.httpdate}"]
34
33
  params << "identity=#{CGI.escape identity.to_s}" if identity
@@ -36,9 +35,9 @@ module Vanity
36
35
  params << @remote_url.query if @remote_url.query
37
36
  @mutex.synchronize do
38
37
  @http ||= Net::HTTP.start(@remote_url.host, @remote_url.port)
39
- @http.request Net::HTTP::Post.new(@remote_url.path, "Content-Type"=>"application/x-www-form-urlencoded"), params.join("&")
38
+ @http.request Net::HTTP::Post.new(@remote_url.path, "Content-Type" => "application/x-www-form-urlencoded"), params.join("&")
40
39
  end
41
- rescue Timeout::Error, StandardError
40
+ rescue Timeout::Error, StandardError # rubocop:todo Lint/ShadowedException
42
41
  @playground.logger.error "Error sending data for metric #{name}: #{$!}"
43
42
  @http = nil
44
43
  ensure
@@ -47,7 +46,6 @@ module Vanity
47
46
 
48
47
  # "Don't worry, be crappy. Revolutionary means you ship and then test."
49
48
  # -- Guy Kawazaki
50
-
51
49
  end
52
50
  end
53
51
  end