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
@@ -6,9 +6,10 @@ module Vanity
6
6
  # @since 1.4.0
7
7
  def redis_connection(spec)
8
8
  require "redis"
9
- fail "redis >= 2.1 is required" unless valid_redis_version?
9
+ raise "redis >= 2.1 is required" unless valid_redis_version?
10
+
10
11
  require "redis/namespace"
11
- fail "redis-namespace >= 1.1.0 is required" unless valid_redis_namespace_version?
12
+ raise "redis-namespace >= 1.1.0 is required" unless valid_redis_namespace_version?
12
13
 
13
14
  RedisAdapter.new(spec)
14
15
  end
@@ -28,7 +29,7 @@ module Vanity
28
29
  class RedisAdapter < AbstractAdapter
29
30
  attr_reader :redis
30
31
 
31
- def initialize(options)
32
+ def initialize(options) # rubocop:todo Lint/MissingSuper
32
33
  @options = options.clone
33
34
  @options[:db] ||= @options[:database] || (@options[:path] && @options.delete(:path).split("/")[1].to_i)
34
35
  @options[:thread_safe] = true
@@ -40,9 +41,7 @@ module Vanity
40
41
  end
41
42
 
42
43
  def disconnect!
43
- if redis
44
- redis.disconnect!
45
- end
44
+ redis.disconnect! if redis
46
45
  @redis = nil
47
46
  end
48
47
 
@@ -53,8 +52,8 @@ module Vanity
53
52
 
54
53
  def connect!
55
54
  @redis = @options[:redis] || Redis.new(@options)
56
- @metrics = Redis::Namespace.new("vanity:metrics", :redis=>redis)
57
- @experiments = Redis::Namespace.new("vanity:experiments", :redis=>redis)
55
+ @metrics = Redis::Namespace.new("vanity:metrics", redis: redis)
56
+ @experiments = Redis::Namespace.new("vanity:experiments", redis: redis)
58
57
  end
59
58
 
60
59
  def to_s
@@ -74,7 +73,7 @@ module Vanity
74
73
 
75
74
  def metric_track(metric, timestamp, identity, values)
76
75
  call_redis_with_failover(metric, timestamp, identity, values) do
77
- values.each_with_index do |v,i|
76
+ values.each_with_index do |v, i|
78
77
  @metrics.incrby "#{metric}:#{timestamp.to_date}:value:#{i}", v
79
78
  end
80
79
  @metrics.set("#{metric}:last_update_at", Time.now.to_i)
@@ -90,7 +89,6 @@ module Vanity
90
89
  @metrics.del(*@metrics.keys("#{metric}:*"))
91
90
  end
92
91
 
93
-
94
92
  # -- Experiments --
95
93
 
96
94
  def experiment_persisted?(experiment)
@@ -117,7 +115,7 @@ module Vanity
117
115
  completed_at && Time.at(completed_at.to_i)
118
116
  end
119
117
 
120
- def is_experiment_completed?(experiment)
118
+ def is_experiment_completed?(experiment) # rubocop:todo Naming/PredicateName
121
119
  call_redis_with_failover do
122
120
  @experiments.exists("#{experiment}:completed_at")
123
121
  end
@@ -129,7 +127,7 @@ module Vanity
129
127
  end
130
128
  end
131
129
 
132
- def is_experiment_enabled?(experiment)
130
+ def is_experiment_enabled?(experiment) # rubocop:todo Naming/PredicateName
133
131
  value = @experiments.get("#{experiment}:enabled")
134
132
  if Vanity.configuration.experiments_start_enabled
135
133
  value != 'false'
@@ -140,9 +138,9 @@ module Vanity
140
138
 
141
139
  def ab_counts(experiment, alternative)
142
140
  {
143
- :participants => @experiments.scard("#{experiment}:alts:#{alternative}:participants").to_i,
144
- :converted => @experiments.scard("#{experiment}:alts:#{alternative}:converted").to_i,
145
- :conversions => @experiments.get("#{experiment}:alts:#{alternative}:conversions").to_i
141
+ participants: @experiments.scard("#{experiment}:alts:#{alternative}:participants").to_i,
142
+ converted: @experiments.scard("#{experiment}:alts:#{alternative}:converted").to_i,
143
+ conversions: @experiments.get("#{experiment}:alts:#{alternative}:conversions").to_i,
146
144
  }
147
145
  end
148
146
 
@@ -174,11 +172,7 @@ module Vanity
174
172
  def ab_seen(experiment, identity, alternative_or_id)
175
173
  with_ab_seen_deprecation(experiment, identity, alternative_or_id) do |expt, ident, alt_id|
176
174
  call_redis_with_failover(expt, ident, alt_id) do
177
- if @experiments.sismember "#{expt}:alts:#{alt_id}:participants", ident
178
- alt_id
179
- else
180
- nil
181
- end
175
+ alt_id if @experiments.sismember "#{expt}:alts:#{alt_id}:participants", ident
182
176
  end
183
177
  end
184
178
  end
@@ -187,9 +181,7 @@ module Vanity
187
181
  def ab_assigned(experiment, identity)
188
182
  call_redis_with_failover do
189
183
  Vanity.playground.experiments[experiment].alternatives.each do |alternative|
190
- if @experiments.sismember "#{experiment}:alts:#{alternative.id}:participants", identity
191
- return alternative.id
192
- end
184
+ return alternative.id if @experiments.sismember "#{experiment}:alts:#{alternative.id}:participants", identity
193
185
  end
194
186
  nil
195
187
  end
@@ -219,7 +211,7 @@ module Vanity
219
211
  def destroy_experiment(experiment)
220
212
  cursor = nil
221
213
 
222
- while cursor != "0" do
214
+ while cursor != "0"
223
215
  cursor, keys = @experiments.scan(cursor || "0", match: "#{experiment}:*")
224
216
 
225
217
  @experiments.del(*keys) unless keys.empty?
@@ -229,11 +221,11 @@ module Vanity
229
221
  protected
230
222
 
231
223
  def call_redis_with_failover(*arguments)
232
- calling_method = caller[0][/`.*'/][1..-2]
224
+ calling_method = caller(1..1).first[/`.*'/][1..-2]
233
225
  begin
234
226
  yield
235
- rescue => e
236
- if Vanity.configuration.failover_on_datastore_error
227
+ rescue StandardError => e
228
+ if Vanity.configuration.failover_on_datastore_error # rubocop:todo Style/GuardClause
237
229
  Vanity.configuration.on_datastore_error.call(e, self.class, calling_method, arguments)
238
230
  else
239
231
  raise e
@@ -17,4 +17,4 @@ module Vanity
17
17
  end
18
18
  end
19
19
  end
20
- end
20
+ end
@@ -2,8 +2,7 @@ module Vanity
2
2
  # A singleton responsible for determining if the playground should connect
3
3
  # to the datastore.
4
4
  module Autoconnect
5
-
6
- BLACKLISTED_RAILS_RAKE_TASKS = [
5
+ BLACKLISTED_RAILS_RAKE_TASKS = [
7
6
  'about',
8
7
  'assets:clean',
9
8
  'assets:clobber',
@@ -38,7 +37,7 @@ module Vanity
38
37
  'stats',
39
38
  'time:zones:all',
40
39
  'tmp:clear',
41
- 'tmp:create'
40
+ 'tmp:create',
42
41
  ]
43
42
  ENVIRONMENT_VANITY_DISABLED_FLAG = "VANITY_DISABLED"
44
43
 
@@ -46,10 +45,10 @@ module Vanity
46
45
  def should_connect?
47
46
  !environment_disabled? && !in_blacklisted_rake_task?
48
47
  end
49
- alias_method :playground_should_autoconnect?, :should_connect?
48
+ alias playground_should_autoconnect? should_connect?
50
49
 
51
50
  def schema_relevant?
52
- current_rake_tasks.any? { |task| task =~ /\Adb:/ }
51
+ current_rake_tasks.any? { |task| task.start_with?('db:') }
53
52
  end
54
53
 
55
54
  def environment_disabled?
@@ -62,9 +61,9 @@ module Vanity
62
61
 
63
62
  def current_rake_tasks
64
63
  ::Rake.application.top_level_tasks
65
- rescue
64
+ rescue StandardError
66
65
  []
67
66
  end
68
67
  end
69
68
  end
70
- end
69
+ end
@@ -4,16 +4,16 @@ module Vanity
4
4
  # Lists all experiments and metrics.
5
5
  def list
6
6
  Vanity.playground.experiments.each do |id, experiment|
7
- puts "experiment :%-.20s (%-.40s)" % [id, experiment.name]
8
- if experiment.respond_to?(:alternatives)
9
- experiment.alternatives.each do |alt|
10
- hash = experiment.fingerprint(alt)
11
- puts " %s: %-40.40s (%s)" % [alt.name, alt.value, hash]
12
- end
7
+ puts format("experiment :%-.20s (%-.40s)", id, experiment.name)
8
+ next unless experiment.respond_to?(:alternatives)
9
+
10
+ experiment.alternatives.each do |alt|
11
+ hash = experiment.fingerprint(alt)
12
+ puts format(" %s: %-40.40s (%s)", alt.name, alt.value, hash)
13
13
  end
14
14
  end
15
15
  Vanity.playground.metrics.each do |id, metric|
16
- puts "metric :%-.20s (%-.40s)" % [id, metric.name]
16
+ puts format("metric :%-.20s (%-.40s)", id, metric.name)
17
17
  end
18
18
  end
19
19
  end
@@ -2,11 +2,9 @@ require "erb"
2
2
  require "cgi"
3
3
 
4
4
  module Vanity
5
-
6
5
  # Render method available to templates (when used by Vanity command line,
7
6
  # outside Rails).
8
7
  module Render
9
-
10
8
  # Render the named template. Used for reporting and the dashboard.
11
9
  def render(path_or_options, locals = {})
12
10
  if path_or_options.respond_to?(:keys)
@@ -29,21 +27,23 @@ module Vanity
29
27
  end
30
28
 
31
29
  class ProxyEmpty < String
32
- def method_missing(method, *args, &block); self.class.new end
30
+ def method_missing(_method, *_args) # rubocop:todo Style/MissingRespondToMissing
31
+ self.class.new
32
+ end
33
33
  end
34
34
 
35
35
  # prevent certain url helper methods from failing so we can run erb templates outside of rails for reports.
36
- def method_missing(method, *args, &block)
36
+ def method_missing(method, *args, &block) # rubocop:todo Style/MissingRespondToMissing
37
37
  %w(url_for flash).include?(method.to_s) ? ProxyEmpty.new : super
38
38
  end
39
39
 
40
40
  # Dumbed down from Rails' simple_format.
41
- def vanity_simple_format(text, options={})
42
- open = "<p #{options.map { |k,v| "#{k}=\"#{CGI.escapeHTML v}\"" }.join(" ")}>"
43
- text = open + text.gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
44
- gsub(/\n\n+/, "</p>\n\n#{open}"). # 2+ newline -> paragraph
45
- gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') + # 1 newline -> br
46
- "</p>"
41
+ def vanity_simple_format(text, options = {})
42
+ open = "<p #{options.map { |k, v| "#{k}=\"#{CGI.escapeHTML v}\"" }.join(' ')}>"
43
+ text = open + text.gsub(/\r\n?/, "\n") # \r\n and \r -> \n # rubocop:todo Lint/UselessAssignment
44
+ .gsub(/\n\n+/, "</p>\n\n#{open}") # 2+ newline -> paragraph
45
+ .gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') + # 1 newline -> br
46
+ "</p>"
47
47
  end
48
48
 
49
49
  protected
@@ -54,7 +54,7 @@ module Vanity
54
54
  struct = Struct.new(*keys)
55
55
  struct.send :include, Render
56
56
  locals = struct.new(*locals.values_at(*keys))
57
- path = "#{Vanity.template(path)}.erb" unless path =~ /\/.*\.erb\z/
57
+ path = "#{Vanity.template(path)}.erb" unless /\/.*\.erb\z/.match?(path)
58
58
  dir, base = File.split(path)
59
59
  path = File.join(dir, partialize(base))
60
60
  erb = ERB.new(File.read(path), nil, '<>')
@@ -63,10 +63,10 @@ module Vanity
63
63
  end
64
64
 
65
65
  def partialize(template_name)
66
- if template_name[0] != '_'
67
- "_#{template_name}"
68
- else
66
+ if template_name[0] == '_'
69
67
  template_name
68
+ else
69
+ "_#{template_name}"
70
70
  end
71
71
  end
72
72
  end
@@ -80,20 +80,16 @@ module Vanity
80
80
  # arguments.
81
81
  def report(output = nil)
82
82
  html = render(Vanity.template("_report.erb"),
83
- :experiments=>Vanity.playground.experiments,
84
- :experiments_persisted=>Vanity.playground.experiments_persisted?,
85
- :metrics=>Vanity.playground.metrics
86
- )
83
+ experiments: Vanity.playground.experiments,
84
+ experiments_persisted: Vanity.playground.experiments_persisted?,
85
+ metrics: Vanity.playground.metrics)
87
86
  if output
88
- File.open output, 'w' do |file|
89
- file.write html
90
- end
87
+ File.write(output, html)
91
88
  puts "New report available in #{output}"
92
89
  else
93
90
  $stdout.write html
94
91
  end
95
92
  end
96
-
97
93
  end
98
94
  end
99
95
  end
@@ -7,7 +7,7 @@ module Vanity
7
7
  LEGACY_CONNECTION_KEY = :connection
8
8
  LEGACY_REDIS_CONFIG_FILE = "redis.yml"
9
9
 
10
- class<<self
10
+ class << self
11
11
  private
12
12
 
13
13
  def default_logger # :nodoc:
@@ -30,9 +30,9 @@ module Vanity
30
30
  #
31
31
  def default_request_filter(request) # :nodoc:
32
32
  request &&
33
- request.env &&
34
- request.env["HTTP_USER_AGENT"] &&
35
- request.env["HTTP_USER_AGENT"].match( /(?:https?:\/\/)|(?:bot|spider|crawler)/i )
33
+ request.env &&
34
+ request.env["HTTP_USER_AGENT"] &&
35
+ request.env["HTTP_USER_AGENT"].match(/(?:https?:\/\/)|(?:bot|spider|crawler)/i)
36
36
  end
37
37
  end
38
38
 
@@ -46,7 +46,7 @@ module Vanity
46
46
  failover_on_datastore_error: false,
47
47
  locales_path: File.expand_path(File.join(File.dirname(__FILE__), 'locales')),
48
48
  logger: default_logger,
49
- on_datastore_error: ->(error, klass, method, arguments) {
49
+ on_datastore_error: lambda { |error, klass, method, arguments|
50
50
  default_on_datastore_error(error, klass, method, arguments)
51
51
  },
52
52
  on_assignment: nil,
@@ -188,7 +188,7 @@ module Vanity
188
188
  # We independently list each attr_accessor to includes docs, otherwise
189
189
  # something like DEFAULTS.each { |key, value| attr_accessor key } would be
190
190
  # shorter.
191
- DEFAULTS.each do |default, value|
191
+ DEFAULTS.each do |default, _value|
192
192
  define_method default do
193
193
  self[default]
194
194
  end
@@ -208,22 +208,22 @@ module Vanity
208
208
  end
209
209
 
210
210
  # @return nil or a hash of symbolized keys for connection settings
211
- def connection_params(file_name=nil)
211
+ def connection_params(file_name = nil)
212
212
  file_name ||= config_file
213
213
  file_path = File.join(config_path, file_name)
214
214
 
215
- if File.exist?(file_path)
216
- config = YAML.load(ERB.new(File.read(file_path)).result)
215
+ if File.exist?(file_path) # rubocop:todo Style/GuardClause
216
+ config = YAML.safe_load(ERB.new(File.read(file_path)).result)
217
217
  config ||= {}
218
218
  params_for_environment = config[environment.to_s]
219
219
 
220
- unless params_for_environment
221
- raise MissingEnvironment.new("No configuration for #{environment}")
222
- end
220
+ raise MissingEnvironment, "No configuration for #{environment}" unless params_for_environment
223
221
 
224
222
  # Symbolize keys if it's a hash.
225
223
  if params_for_environment.respond_to?(:inject)
226
- params_for_environment.inject({}) { |h,kv| h[kv.first.to_sym] = kv.last ; h }
224
+ params_for_environment.each_with_object({}) do |kv, h|
225
+ h[kv.first.to_sym] = kv.last
226
+ end
227
227
  else
228
228
  params_for_environment
229
229
  end
@@ -238,11 +238,11 @@ module Vanity
238
238
 
239
239
  connection_url = connection_config[LEGACY_CONNECTION_KEY]
240
240
 
241
- if connection_url
242
- logger.warn(%q{Deprecated: Please specify connection urls using the `url` key with a protocol prefix instead of `connection`. This fallback will be removed in a future version.})
241
+ if connection_url # rubocop:todo Style/GuardClause
242
+ logger.warn('Deprecated: Please specify connection urls using the `url` key with a protocol prefix instead of `connection`. This fallback will be removed in a future version.')
243
243
 
244
244
  # Legacy lack of protocol handling
245
- if connection_url =~ /^\w+:/
245
+ if /^\w+:/.match?(connection_url)
246
246
  connection_url
247
247
  else
248
248
  "redis://" + connection_url
@@ -254,10 +254,10 @@ module Vanity
254
254
  def redis_url_from_file
255
255
  connection_url = connection_params(LEGACY_REDIS_CONFIG_FILE)
256
256
 
257
- if connection_url
258
- logger.warn(%q{Deprecated: Please specify the vanity config file, the default fallback to "config/redis.yml" may be removed in a future version.})
257
+ if connection_url # rubocop:todo Style/GuardClause
258
+ logger.warn('Deprecated: Please specify the vanity config file, the default fallback to "config/redis.yml" may be removed in a future version.')
259
259
 
260
- if connection_url =~ /^\w+:/
260
+ if /^\w+:/.match?(connection_url)
261
261
  connection_url
262
262
  else
263
263
  "redis://" + connection_url
@@ -30,12 +30,10 @@ module Vanity
30
30
  # :host=>"redis.local"
31
31
  # )
32
32
  # @since 2.0.0
33
- def initialize(specification=nil)
33
+ def initialize(specification = nil)
34
34
  @specification = Specification.new(specification || DEFAULT_SPECIFICATION).to_h
35
35
 
36
- if Autoconnect.playground_should_autoconnect?
37
- @adapter = setup_connection(@specification)
38
- end
36
+ @adapter = setup_connection(@specification) if Autoconnect.playground_should_autoconnect?
39
37
  end
40
38
 
41
39
  # Closes the current connection.
@@ -68,16 +66,16 @@ module Vanity
68
66
  when String
69
67
  @spec = build_specification_hash_from_url(spec)
70
68
  when Hash
71
- if spec[:redis]
72
- @spec = {
73
- adapter: :redis,
74
- redis: spec[:redis]
75
- }
76
- else
77
- @spec = spec
78
- end
69
+ @spec = if spec[:redis]
70
+ {
71
+ adapter: :redis,
72
+ redis: spec[:redis],
73
+ }
74
+ else
75
+ spec
76
+ end
79
77
  else
80
- raise InvalidSpecification.new("Unsupported connection specification: #{spec.inspect}")
78
+ raise InvalidSpecification, "Unsupported connection specification: #{spec.inspect}"
81
79
  end
82
80
 
83
81
  validate_specification_hash(@spec)
@@ -104,7 +102,7 @@ module Vanity
104
102
  host: uri.host,
105
103
  port: uri.port,
106
104
  path: uri.path,
107
- params: params
105
+ params: params,
108
106
  }
109
107
  end
110
108
  end