vanity 3.1.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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