vanity 2.0.0.beta8 → 2.0.0.beta9
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.
- data/.travis.yml +4 -2
- data/Appraisals +1 -1
- data/Gemfile +2 -1
- data/Gemfile.lock +4 -1
- data/gemfiles/rails32.gemfile +3 -2
- data/gemfiles/rails32.gemfile.lock +4 -5
- data/gemfiles/rails4.gemfile +2 -1
- data/gemfiles/rails4.gemfile.lock +3 -1
- data/gemfiles/rails41.gemfile +2 -1
- data/gemfiles/rails41.gemfile.lock +3 -1
- data/gemfiles/rails42.gemfile +2 -1
- data/gemfiles/rails42.gemfile.lock +3 -1
- data/lib/vanity.rb +11 -5
- data/lib/vanity/adapters.rb +20 -0
- data/lib/vanity/adapters/abstract_adapter.rb +0 -18
- data/lib/vanity/autoconnect.rb +1 -0
- data/lib/vanity/configuration.rb +211 -0
- data/lib/vanity/connection.rb +100 -0
- data/lib/vanity/frameworks.rb +2 -0
- data/lib/vanity/frameworks/rails.rb +7 -5
- data/lib/vanity/helpers.rb +1 -0
- data/{config → lib/vanity}/locales/vanity.en.yml +0 -0
- data/{config → lib/vanity}/locales/vanity.pt-BR.yml +0 -0
- data/lib/vanity/playground.rb +138 -338
- data/lib/vanity/vanity.rb +166 -0
- data/lib/vanity/version.rb +1 -1
- data/test/configuration_test.rb +90 -0
- data/test/connection_test.rb +46 -0
- data/test/data/redis.yml.url +2 -0
- data/test/data/vanity.yml.activerecord +6 -0
- data/test/data/vanity.yml.mock +4 -0
- data/test/data/vanity.yml.redis +5 -0
- data/test/data/vanity.yml.redis-erb +3 -0
- data/test/experiment/ab_test.rb +1 -1
- data/test/experiment/base_test.rb +1 -1
- data/test/frameworks/rails/rails_test.rb +34 -30
- data/test/metric/remote_test.rb +8 -8
- data/test/playground_test.rb +1 -27
- data/test/templates_test.rb +3 -2
- data/test/test_helper.rb +24 -12
- data/test/vanity_test.rb +130 -0
- metadata +25 -5
@@ -0,0 +1,100 @@
|
|
1
|
+
module Vanity
|
2
|
+
class Connection
|
3
|
+
class InvalidSpecification < StandardError; end
|
4
|
+
|
5
|
+
DEFAULT_SPECIFICATION = { adapter: "redis" }
|
6
|
+
|
7
|
+
attr_reader :adapter, :specification
|
8
|
+
|
9
|
+
# With no argument, uses the connection specified in the configuration
|
10
|
+
# file, or defaults to Redis on localhost, port 6379.
|
11
|
+
# @example
|
12
|
+
# Vanity::Connection.new
|
13
|
+
#
|
14
|
+
# If the argument is a string, it is processed as a URL.
|
15
|
+
# @example
|
16
|
+
# Vanity::Connection.new("redis://redis.local/5")
|
17
|
+
#
|
18
|
+
# If the argument is a Hash, and contains a key `:redis` the value is used
|
19
|
+
# as a redis connection.
|
20
|
+
# @example
|
21
|
+
# $shared_redis_connection = Redis.new
|
22
|
+
# Vanity::Connection.new(adapter: :redis, redis: $shared_redis_connection)
|
23
|
+
#
|
24
|
+
# Otherwise, the argument is a hash and specifies the adapter name and any
|
25
|
+
# additional options understood by that adapter (as with
|
26
|
+
# config/vanity.yml). Note that all keys are expected to be symbols.
|
27
|
+
# @example
|
28
|
+
# Vanity::Connection.new(
|
29
|
+
# :adapter=>:redis,
|
30
|
+
# :host=>"redis.local"
|
31
|
+
# )
|
32
|
+
# @since 2.0.0
|
33
|
+
def initialize(specification=nil)
|
34
|
+
@specification = specification || DEFAULT_SPECIFICATION
|
35
|
+
|
36
|
+
if Autoconnect.playground_should_autoconnect?
|
37
|
+
@adapter = setup_connection(@specification)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Closes the current connection.
|
42
|
+
#
|
43
|
+
# @since 2.0.0
|
44
|
+
def disconnect!
|
45
|
+
@adapter.disconnect! if connected?
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns true if connection is open.
|
49
|
+
#
|
50
|
+
# @since 2.0.0
|
51
|
+
def connected?
|
52
|
+
@adapter && @adapter.active?
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def setup_connection(spec)
|
58
|
+
case spec
|
59
|
+
when String
|
60
|
+
spec_hash = build_specification_hash_from_url(spec)
|
61
|
+
establish_connection(spec_hash)
|
62
|
+
when Hash
|
63
|
+
validate_specification_hash(spec)
|
64
|
+
if spec[:redis]
|
65
|
+
establish_connection(
|
66
|
+
adapter: :redis,
|
67
|
+
redis: spec[:redis]
|
68
|
+
)
|
69
|
+
else
|
70
|
+
establish_connection(spec)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
raise InvalidSpecification.new("Unsupported connection specification: #{spec.inspect}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_specification_hash_from_url(connection_url)
|
78
|
+
uri = URI.parse(connection_url)
|
79
|
+
params = CGI.parse(uri.query) if uri.query
|
80
|
+
{
|
81
|
+
adapter: uri.scheme,
|
82
|
+
username: uri.user,
|
83
|
+
password: uri.password,
|
84
|
+
host: uri.host,
|
85
|
+
port: uri.port,
|
86
|
+
path: uri.path,
|
87
|
+
params: params
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def validate_specification_hash(spec)
|
92
|
+
all_symbol_keys = spec.keys.all? { |key| key.is_a?(Symbol) }
|
93
|
+
raise InvalidSpecification unless all_symbol_keys
|
94
|
+
end
|
95
|
+
|
96
|
+
def establish_connection(spec)
|
97
|
+
Adapters.establish_connection(spec)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/vanity/frameworks.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
module Vanity
|
2
2
|
module Rails
|
3
3
|
def self.load!
|
4
|
-
|
5
|
-
|
4
|
+
::Rails.configuration.before_initialize do
|
5
|
+
Vanity.configuration.logger ||= ::Rails.logger
|
6
|
+
Vanity.configuration.setup_locales
|
7
|
+
end
|
6
8
|
|
7
9
|
# Do this at the very end of initialization, allowing you to change
|
8
10
|
# connection adapter, turn collection on/off, etc.
|
9
11
|
::Rails.configuration.after_initialize do
|
10
|
-
Vanity.
|
12
|
+
Vanity.load! if Vanity.connection.connected?
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
@@ -44,7 +46,7 @@ module Vanity
|
|
44
46
|
@_vanity_experiments[name] ||= alternative
|
45
47
|
@_vanity_experiments[name].value
|
46
48
|
end
|
47
|
-
|
49
|
+
|
48
50
|
if block
|
49
51
|
define_method(:vanity_identity) { block.call(self) }
|
50
52
|
else
|
@@ -323,7 +325,7 @@ module Vanity
|
|
323
325
|
exp.chooses(exp.alternatives[params[:a].to_i].value)
|
324
326
|
render :file=>Vanity.template("_experiment"), :locals=>{:experiment=>exp}
|
325
327
|
end
|
326
|
-
|
328
|
+
|
327
329
|
def reset
|
328
330
|
exp = Vanity.playground.experiment(params[:e].to_sym)
|
329
331
|
exp.reset
|
data/lib/vanity/helpers.rb
CHANGED
File without changes
|
File without changes
|
data/lib/vanity/playground.rb
CHANGED
@@ -1,221 +1,142 @@
|
|
1
1
|
require "uri"
|
2
2
|
|
3
3
|
module Vanity
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# @example
|
8
|
-
# Vanity.playground.logger = my_logger
|
9
|
-
# puts Vanity.playground.map(&:name)
|
4
|
+
# Playground catalogs all your experiments. For configuration please see
|
5
|
+
# Vanity::Configuration, for connection management, please see
|
6
|
+
# Vanity::Connection.
|
10
7
|
class Playground
|
11
8
|
|
12
|
-
DEFAULTS = { :collecting => true, :load_path=>"experiments" }
|
13
|
-
DEFAULT_ADD_PARTICIPANT_PATH = '/vanity/add_participant'
|
14
|
-
|
15
9
|
# Created new Playground. Unless you need to, use the global
|
16
10
|
# Vanity.playground.
|
17
|
-
|
18
|
-
# First argument is connection specification (see #redis=), last argument is
|
19
|
-
# a set of options, both are optional. Supported options are:
|
20
|
-
# - connection -- Connection specification
|
21
|
-
# - load_path -- Path to load experiments/metrics from
|
22
|
-
# - logger -- Logger to use
|
23
|
-
# - redis -- A Redis object that will be used for the connection
|
24
|
-
def initialize(*args)
|
25
|
-
options = Hash === args.last ? args.pop : {}
|
26
|
-
# In the case of Rails, use the Rails logger and collect only for
|
27
|
-
# production environment by default.
|
28
|
-
defaults = options[:rails] ? DEFAULTS.merge(:collecting => true, :logger => ::Rails.logger) : DEFAULTS
|
29
|
-
if config_file_exists?
|
30
|
-
env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
|
31
|
-
config = load_config_file[env]
|
32
|
-
if Hash === config
|
33
|
-
config = config.inject({}) { |h,kv| h[kv.first.to_sym] = kv.last ; h }
|
34
|
-
else
|
35
|
-
config = { :connection=>config }
|
36
|
-
end
|
37
|
-
else
|
38
|
-
config = {}
|
39
|
-
end
|
40
|
-
|
41
|
-
@options = defaults.merge(config).merge(options)
|
42
|
-
|
43
|
-
@load_path = @options[:load_path] || DEFAULTS[:load_path]
|
44
|
-
|
45
|
-
I18n.load_path += locale_file_paths
|
46
|
-
unless @logger = @options[:logger]
|
47
|
-
@logger = Logger.new(STDOUT)
|
48
|
-
@logger.level = Logger::ERROR
|
49
|
-
end
|
50
|
-
|
51
|
-
autoconnect(@options, args) if Vanity::Autoconnect.playground_should_autoconnect?
|
52
|
-
|
11
|
+
def initialize
|
53
12
|
@loading = []
|
54
|
-
@use_js = false
|
55
|
-
@failover_on_datastore_error = false
|
56
|
-
self.add_participant_path = DEFAULT_ADD_PARTICIPANT_PATH
|
57
|
-
@collecting = !!@options[:collecting]
|
58
13
|
end
|
59
14
|
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
attr_accessor :logger
|
65
|
-
|
66
|
-
# Path to the add_participant action.
|
67
|
-
attr_accessor :add_participant_path
|
68
|
-
|
69
|
-
attr_accessor :on_datastore_error
|
70
|
-
|
71
|
-
attr_accessor :request_filter
|
72
|
-
|
73
|
-
# Path to custom templates (overriding those in the gem)
|
74
|
-
attr_writer :custom_templates_path
|
75
|
-
def custom_templates_path
|
76
|
-
@custom_templates_path ||= (File.expand_path(File.join(::Rails.root, 'app', 'views', 'vanity')) if defined?(::Rails))
|
15
|
+
# @deprecated
|
16
|
+
# @see Configuration#experiments_path
|
17
|
+
def load_path
|
18
|
+
Vanity.configuration.experiments_path
|
77
19
|
end
|
78
20
|
|
79
|
-
#
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
def experiment(name)
|
84
|
-
id = name.to_s.downcase.gsub(/\W/, "_").to_sym
|
85
|
-
warn "Deprecated: please call experiment method with experiment identifier (a Ruby symbol)" unless id == name
|
86
|
-
experiments[id.to_sym] or raise NoExperimentError, "No experiment #{id}"
|
21
|
+
# @deprecated
|
22
|
+
# @see Configuration#experiments_path
|
23
|
+
def load_path=(path)
|
24
|
+
Vanity.configuration.experiments_path = path
|
87
25
|
end
|
88
26
|
|
27
|
+
# @deprecated
|
28
|
+
# @see Configuration#logger
|
29
|
+
def logger
|
30
|
+
Vanity.configuration.logger
|
31
|
+
end
|
89
32
|
|
90
|
-
#
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
# when converted to_s (so could be used for caching, for example)
|
95
|
-
def participant_info(participant_id)
|
96
|
-
participant_array = []
|
97
|
-
experiments.values.sort_by(&:name).each do |e|
|
98
|
-
index = connection.ab_assigned(e.id, participant_id)
|
99
|
-
if index
|
100
|
-
participant_array << [e, e.alternatives[index.to_i]]
|
101
|
-
end
|
102
|
-
end
|
103
|
-
participant_array
|
33
|
+
# @deprecated
|
34
|
+
# @see Configuration#logger
|
35
|
+
def logger=(logger)
|
36
|
+
Vanity.configuration.logger = logger
|
104
37
|
end
|
105
38
|
|
39
|
+
# @deprecated
|
40
|
+
# @see Configuration#templates_path
|
41
|
+
def custom_templates_path
|
42
|
+
Vanity.configuration.templates_path
|
43
|
+
end
|
106
44
|
|
107
|
-
|
45
|
+
def custom_templates_path=(path)
|
46
|
+
Vanity.configuration.templates_path = path
|
47
|
+
end
|
108
48
|
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
# If you want to use this:
|
113
|
-
# - Add <%= vanity_js %> to any page that needs uses an ab_test. vanity_js
|
114
|
-
# needs to be included after your call to ab_test so that it knows which
|
115
|
-
# version of the experiment the participant is a member of. The helper
|
116
|
-
# will render nothing if the there are no ab_tests running on the current
|
117
|
-
# page, so adding vanity_js to the bottom of your layouts is a good
|
118
|
-
# option. Keep in mind that if you call use_js! and don't include
|
119
|
-
# vanity_js in your view no participants will be recorded.
|
120
|
-
#
|
121
|
-
# Note that a custom JS callback path can be set using:
|
122
|
-
# - Set Vanity.playground.add_participant_path = '/path/to/vanity/action',
|
123
|
-
# this should point to the add_participant path that is added with
|
124
|
-
# Vanity::Rails::Dashboard, make sure that this action is available
|
125
|
-
# to all users.
|
49
|
+
# @deprecated
|
50
|
+
# @see Configuration#use_js
|
126
51
|
def use_js!
|
127
|
-
|
52
|
+
Vanity.configuration.use_js = true
|
128
53
|
end
|
129
54
|
|
55
|
+
# @deprecated
|
56
|
+
# @see Configuration#use_js
|
130
57
|
def using_js?
|
131
|
-
|
58
|
+
Vanity.configuration.use_js
|
132
59
|
end
|
133
60
|
|
61
|
+
# @deprecated
|
62
|
+
# @see Configuration#add_participant_route
|
63
|
+
def add_participant_path
|
64
|
+
Vanity.configuration.add_participant_route
|
65
|
+
end
|
134
66
|
|
135
|
-
#
|
67
|
+
# @deprecated
|
68
|
+
# @see Configuration#add_participant_route=
|
69
|
+
def add_participant_path=(path)
|
70
|
+
Vanity.configuration.add_participant_route=path
|
71
|
+
end
|
136
72
|
|
137
|
-
# Turns on passing of errors to the Proc returned by #on_datastore_error.
|
138
|
-
# Call Vanity.playground.failover_on_datastore_error! to turn this on.
|
139
|
-
#
|
140
73
|
# @since 1.9.0
|
74
|
+
# @deprecated
|
75
|
+
# @see Configuration#failover_on_datastore_error
|
141
76
|
def failover_on_datastore_error!
|
142
|
-
|
77
|
+
Vanity.configuration.failover_on_datastore_error = true
|
143
78
|
end
|
144
79
|
|
145
|
-
# Returns whether to failover on an error raise by the datastore adapter.
|
146
|
-
#
|
147
80
|
# @since 1.9.0
|
81
|
+
# @deprecated
|
82
|
+
# @see Configuration#failover_on_datastore_error
|
148
83
|
def failover_on_datastore_error?
|
149
|
-
|
84
|
+
Vanity.configuration.failover_on_datastore_error
|
150
85
|
end
|
151
86
|
|
152
|
-
# Must return a Proc that accepts as parameters: the thrown error, the
|
153
|
-
# calling Class, the calling method, and an array of arguments passed to
|
154
|
-
# the calling method. The return value is ignored.
|
155
|
-
#
|
156
|
-
# Proc.new do |error, klass, method, arguments|
|
157
|
-
# ...
|
158
|
-
# end
|
159
|
-
#
|
160
|
-
# The default implementation logs this information to Playground#logger.
|
161
|
-
#
|
162
|
-
# Set a custom action by calling Vanity.playground.on_datastore_error =
|
163
|
-
# Proc.new { ... }.
|
164
|
-
#
|
165
87
|
# @since 1.9.0
|
88
|
+
# @deprecated
|
89
|
+
# @see Configuration#on_datastore_error
|
166
90
|
def on_datastore_error
|
167
|
-
|
91
|
+
Vanity.configuration.on_datastore_error
|
168
92
|
end
|
169
93
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
log << " [#{error.message}]"
|
175
|
-
log << " [#{arguments.join(' ')}]"
|
176
|
-
@logger.error(log)
|
177
|
-
nil
|
178
|
-
end
|
94
|
+
# @deprecated
|
95
|
+
# @see Configuration#on_datastore_error
|
96
|
+
def on_datastore_error=(closure)
|
97
|
+
Vanity.configuration.on_datastore_error = closure
|
179
98
|
end
|
180
|
-
protected :default_on_datastore_error
|
181
|
-
|
182
99
|
|
183
|
-
# -- Blocking or ignoring visitors --
|
184
|
-
|
185
|
-
# Must return a Proc that accepts as a parameter the request object, if
|
186
|
-
# made available by the implement framework. The return value should be a
|
187
|
-
# boolean whether to ignore the request. This is called only for the JS
|
188
|
-
# callback action.
|
189
|
-
#
|
190
|
-
# Proc.new do |request|
|
191
|
-
# ...
|
192
|
-
# end
|
193
|
-
#
|
194
|
-
# The default implementation does a simple test of whether the request's
|
195
|
-
# HTTP_USER_AGENT header contains a URI, since well behaved bots typically
|
196
|
-
# include a reference URI in their user agent strings. (Original idea:
|
197
|
-
# http://stackoverflow.com/a/9285889.)
|
198
|
-
#
|
199
|
-
# Alternatively, one could filter an explicit list of IPs, add additional
|
200
|
-
# user agent strings to filter, or any custom test. Set a custom filter
|
201
|
-
# by calling Vanity.playground.request_filter = Proc.new { ... }.
|
202
|
-
#
|
203
100
|
# @since 1.9.0
|
101
|
+
# @deprecated
|
102
|
+
# @see Configuration#request_filter
|
204
103
|
def request_filter
|
205
|
-
|
104
|
+
Vanity.configuration.request_filter
|
206
105
|
end
|
207
106
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
107
|
+
# @deprecated
|
108
|
+
# @see Configuration#request_filter=
|
109
|
+
def request_filter=(filter)
|
110
|
+
Vanity.configuration.request_filter = filter
|
111
|
+
end
|
112
|
+
|
113
|
+
# @since 1.4.0
|
114
|
+
# @deprecated
|
115
|
+
# @see Configuration#collecting
|
116
|
+
def collecting?
|
117
|
+
Vanity.configuration.collecting
|
118
|
+
end
|
119
|
+
|
120
|
+
# @since 1.4.0
|
121
|
+
# @deprecated
|
122
|
+
# @see Configuration#collecting
|
123
|
+
def collecting=(enabled)
|
124
|
+
Vanity.configuration.collecting = enabled
|
215
125
|
end
|
216
|
-
protected :default_request_filter
|
217
126
|
|
218
|
-
#
|
127
|
+
# @deprecated
|
128
|
+
# @see Vanity#reload!
|
129
|
+
def reload!
|
130
|
+
Vanity.reload!
|
131
|
+
end
|
132
|
+
|
133
|
+
# @deprecated
|
134
|
+
# @see Vanity#load!
|
135
|
+
def load!
|
136
|
+
Vanity.load!
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns hash of experiments (key is experiment id). This creates the
|
219
140
|
# Experiment and persists it to the datastore.
|
220
141
|
#
|
221
142
|
# @see Vanity::Experiment
|
@@ -223,8 +144,8 @@ module Vanity
|
|
223
144
|
return @experiments if @experiments
|
224
145
|
|
225
146
|
@experiments = {}
|
226
|
-
|
227
|
-
Dir[File.join(
|
147
|
+
Vanity.logger.info("Vanity: loading experiments from #{Vanity.configuration.experiments_path}")
|
148
|
+
Dir[File.join(Vanity.configuration.experiments_path, "*.rb")].each do |file|
|
228
149
|
Experiment::Base.load(self, @loading, file)
|
229
150
|
end
|
230
151
|
@experiments
|
@@ -234,21 +155,6 @@ module Vanity
|
|
234
155
|
experiments.keys.all? { |id| connection.experiment_persisted?(id) }
|
235
156
|
end
|
236
157
|
|
237
|
-
# Reloads all metrics and experiments. Rails calls this for each request in
|
238
|
-
# development mode.
|
239
|
-
def reload!
|
240
|
-
@experiments = nil
|
241
|
-
@metrics = nil
|
242
|
-
load!
|
243
|
-
end
|
244
|
-
|
245
|
-
# Loads all metrics and experiments. Rails calls this during
|
246
|
-
# initialization.
|
247
|
-
def load!
|
248
|
-
experiments
|
249
|
-
metrics
|
250
|
-
end
|
251
|
-
|
252
158
|
# Returns a metric (raises NameError if no metric with that identifier).
|
253
159
|
#
|
254
160
|
# @see Vanity::Metric
|
@@ -257,40 +163,18 @@ module Vanity
|
|
257
163
|
metrics[id.to_sym] or raise NameError, "No metric #{id}"
|
258
164
|
end
|
259
165
|
|
260
|
-
# True if collection data (metrics and experiments). You only want to
|
261
|
-
# collect data in production environment, everywhere else run with
|
262
|
-
# collection off.
|
263
|
-
#
|
264
|
-
# @since 1.4.0
|
265
|
-
def collecting?
|
266
|
-
@collecting
|
267
|
-
end
|
268
|
-
|
269
|
-
# Turns data collection on and off.
|
270
|
-
#
|
271
|
-
# @since 1.4.0
|
272
|
-
def collecting=(enabled)
|
273
|
-
@collecting = !!enabled
|
274
|
-
end
|
275
|
-
|
276
166
|
# Returns hash of metrics (key is metric id).
|
277
167
|
#
|
278
168
|
# @see Vanity::Metric
|
279
169
|
# @since 1.1.0
|
170
|
+
# @deprecated
|
280
171
|
def metrics
|
281
172
|
unless @metrics
|
282
173
|
@metrics = {}
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
if config_file_exists? && remote = load_config_file["metrics"]
|
288
|
-
remote.each do |id, url|
|
289
|
-
fail "Metric #{id} already defined in playground" if metrics[id.to_sym]
|
290
|
-
metric = Metric.new(self, id)
|
291
|
-
metric.remote url
|
292
|
-
metrics[id.to_sym] = metric
|
293
|
-
end
|
174
|
+
Vanity.logger.info("Vanity: loading metrics from #{Vanity.configuration.experiments_path}/metrics")
|
175
|
+
|
176
|
+
Dir[File.join(Vanity.configuration.experiments_path, "metrics/*.rb")].each do |file|
|
177
|
+
Metric.load(self, @loading, file)
|
294
178
|
end
|
295
179
|
end
|
296
180
|
@metrics
|
@@ -303,156 +187,72 @@ module Vanity
|
|
303
187
|
#
|
304
188
|
# @since 1.1.0
|
305
189
|
def track!(id, count = 1)
|
306
|
-
metric(id).track!
|
190
|
+
metric(id).track!(count)
|
307
191
|
end
|
308
192
|
|
309
|
-
|
310
|
-
#
|
311
|
-
|
312
|
-
# This is the preferred way to programmatically create a new connection (or
|
313
|
-
# switch to a new connection). If no connection was established, the
|
314
|
-
# playground will create a new one by calling this method with no arguments.
|
315
|
-
#
|
316
|
-
# With no argument, uses the connection specified in config/vanity.yml file
|
317
|
-
# for the current environment (RACK_ENV, RAILS_ENV or development). If there
|
318
|
-
# is no config/vanity.yml file, picks the configuration from
|
319
|
-
# config/redis.yml, or defaults to Redis on localhost, port 6379.
|
320
|
-
#
|
321
|
-
# If the argument is a symbol, uses the connection specified in
|
322
|
-
# config/vanity.yml for that environment. For example:
|
323
|
-
# Vanity.playground.establish_connection :production
|
324
|
-
#
|
325
|
-
# If the argument is a string, it is processed as a URL. For example:
|
326
|
-
# Vanity.playground.establish_connection "redis://redis.local/5"
|
327
|
-
#
|
328
|
-
# Otherwise, the argument is a hash and specifies the adapter name and any
|
329
|
-
# additional options understood by that adapter (as with config/vanity.yml).
|
330
|
-
# For example:
|
331
|
-
# Vanity.playground.establish_connection :adapter=>:redis,
|
332
|
-
# :host=>"redis.local"
|
193
|
+
# Returns the experiment. You may not have guessed, but this method raises
|
194
|
+
# an exception if it cannot load the experiment's definition.
|
333
195
|
#
|
334
|
-
# @
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
if config_file_exists?
|
341
|
-
env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
|
342
|
-
spec = load_config_file[env]
|
343
|
-
fail "No configuration for #{env}" unless spec
|
344
|
-
establish_connection spec
|
345
|
-
elsif config_file_exists?("redis.yml")
|
346
|
-
env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
|
347
|
-
redis = load_config_file("redis.yml")[env]
|
348
|
-
fail "No configuration for #{env}" unless redis
|
349
|
-
establish_connection "redis://" + redis
|
350
|
-
else
|
351
|
-
establish_connection :adapter=>"redis"
|
352
|
-
end
|
353
|
-
when Symbol
|
354
|
-
spec = load_config_file[spec.to_s]
|
355
|
-
establish_connection spec
|
356
|
-
when String
|
357
|
-
uri = URI.parse(spec)
|
358
|
-
params = CGI.parse(uri.query) if uri.query
|
359
|
-
establish_connection :adapter=>uri.scheme, :username=>uri.user, :password=>uri.password,
|
360
|
-
:host=>uri.host, :port=>uri.port, :path=>uri.path, :params=>params
|
361
|
-
else
|
362
|
-
spec = spec.inject({}) { |hash,(k,v)| hash[k.to_sym] = v ; hash }
|
363
|
-
@adapter = Adapters.establish_connection(spec)
|
364
|
-
end
|
196
|
+
# @see Vanity::Experiment
|
197
|
+
# @deprecated
|
198
|
+
def experiment(name)
|
199
|
+
id = name.to_s.downcase.gsub(/\W/, "_").to_sym
|
200
|
+
Vanity.logger.warn("Deprecated: Please call experiment method with experiment identifier (a Ruby symbol)") unless id == name
|
201
|
+
experiments[id.to_sym] or raise NoExperimentError, "No experiment #{id}"
|
365
202
|
end
|
366
203
|
|
367
|
-
def config_file_root
|
368
|
-
(defined?(::Rails) ? ::Rails.root : Pathname.new(".")) + "config"
|
369
|
-
end
|
370
204
|
|
371
|
-
|
372
|
-
File.exists?(config_file_root + basename)
|
373
|
-
end
|
205
|
+
# -- Participant Information --
|
374
206
|
|
375
|
-
|
376
|
-
|
207
|
+
# Returns an array of all experiments this participant is involved in, with their assignment.
|
208
|
+
# This is done as an array of arrays [[<experiment_1>, <assignment_1>], [<experiment_2>, <assignment_2>]], sorted by experiment name, so that it will give a consistent string
|
209
|
+
# when converted to_s (so could be used for caching, for example)
|
210
|
+
def participant_info(participant_id)
|
211
|
+
participant_array = []
|
212
|
+
experiments.values.sort_by(&:name).each do |e|
|
213
|
+
index = connection.ab_assigned(e.id, participant_id)
|
214
|
+
if index
|
215
|
+
participant_array << [e, e.alternatives[index.to_i]]
|
216
|
+
end
|
217
|
+
end
|
218
|
+
participant_array
|
377
219
|
end
|
378
220
|
|
379
|
-
|
380
|
-
|
381
|
-
|
221
|
+
# @since 1.4.0
|
222
|
+
# @deprecated
|
223
|
+
# @see Vanity::Connection
|
224
|
+
def establish_connection(spec=nil)
|
225
|
+
disconnect!
|
226
|
+
Vanity.connect!(spec)
|
382
227
|
end
|
383
228
|
|
384
|
-
# Returns the current connection. Establishes new connection is necessary.
|
385
|
-
#
|
386
229
|
# @since 1.4.0
|
230
|
+
# @deprecated
|
231
|
+
# @see Vanity.connection
|
387
232
|
def connection
|
388
|
-
|
233
|
+
Vanity.connection.adapter
|
389
234
|
end
|
390
235
|
|
391
|
-
# Returns true if connection is open.
|
392
|
-
#
|
393
236
|
# @since 1.4.0
|
237
|
+
# @deprecated
|
238
|
+
# @see Vanity.connection
|
394
239
|
def connected?
|
395
|
-
|
240
|
+
Vanity.connection.connected?
|
396
241
|
end
|
397
242
|
|
398
|
-
# Closes the current connection.
|
399
|
-
#
|
400
243
|
# @since 1.4.0
|
244
|
+
# @deprecated
|
245
|
+
# @see Vanity.disconnect!
|
401
246
|
def disconnect!
|
402
|
-
|
247
|
+
Vanity.disconnect!
|
403
248
|
end
|
404
249
|
|
405
250
|
# Closes the current connection and establishes a new one.
|
406
251
|
#
|
407
252
|
# @since 1.3.0
|
253
|
+
# @deprecated
|
408
254
|
def reconnect!
|
409
|
-
|
255
|
+
Vanity.reconnect!
|
410
256
|
end
|
411
|
-
|
412
|
-
protected
|
413
|
-
|
414
|
-
def autoconnect(options, arguments)
|
415
|
-
if options[:redis]
|
416
|
-
@adapter = RedisAdapter.new(:redis=>options[:redis])
|
417
|
-
else
|
418
|
-
connection_spec = arguments.shift || options[:connection]
|
419
|
-
if connection_spec
|
420
|
-
connection_spec = "redis://" + connection_spec unless connection_spec[/^\w+:/]
|
421
|
-
establish_connection connection_spec
|
422
|
-
else
|
423
|
-
establish_connection
|
424
|
-
end
|
425
|
-
end
|
426
|
-
end
|
427
|
-
|
428
|
-
end
|
429
|
-
|
430
|
-
# In the case of Rails, use the Rails logger and collect only for
|
431
|
-
# production environment by default.
|
432
|
-
class << self
|
433
|
-
|
434
|
-
# The playground instance.
|
435
|
-
#
|
436
|
-
# @see Vanity::Playground
|
437
|
-
attr_accessor :playground
|
438
|
-
def playground
|
439
|
-
# In the case of Rails, use the Rails logger and collect only for
|
440
|
-
# production environment by default.
|
441
|
-
@playground ||= Playground.new(:rails=>defined?(::Rails))
|
442
|
-
end
|
443
|
-
|
444
|
-
# Returns the Vanity context. For example, when using Rails this would be
|
445
|
-
# the current controller, which can be used to get/set the vanity identity.
|
446
|
-
def context
|
447
|
-
Thread.current[:vanity_context]
|
448
|
-
end
|
449
|
-
|
450
|
-
# Sets the Vanity context. For example, when using Rails this would be
|
451
|
-
# set by the set_vanity_context before filter (via Vanity::Rails#use_vanity).
|
452
|
-
def context=(context)
|
453
|
-
Thread.current[:vanity_context] = context
|
454
|
-
end
|
455
|
-
|
456
|
-
|
457
257
|
end
|
458
258
|
end
|