Class: Vanity::Playground

Inherits:
Object show all
Defined in:
lib/vanity/playground.rb

Overview

Playground catalogs all your experiments, holds the Vanity configuration.

Examples:

Vanity.playground.logger = my_logger
puts Vanity.playground.map(&:name)

Constant Summary collapse

DEFAULTS =
{ :collecting => true, :load_path=>"experiments" }
DEFAULT_ADD_PARTICIPANT_PATH =
'/vanity/add_participant'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Playground

Created new Playground. Unless you need to, use the global Vanity.playground.

First argument is connection specification (see #redis=), last argument is a set of options, both are optional. Supported options are:

  • connection -- Connection specification
  • namespace -- Namespace to use
  • load_path -- Path to load experiments/metrics from
  • logger -- Logger to use


24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/vanity/playground.rb', line 24

def initialize(*args)
  options = Hash === args.last ? args.pop : {}
  # In the case of Rails, use the Rails logger and collect only for
  # production environment by default.
  defaults = options[:rails] ? DEFAULTS.merge(:collecting => ::Rails.env.production?, :logger => ::Rails.logger) : DEFAULTS
  if config_file_exists?
    env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
    config = load_config_file[env]
    if Hash === config
      config = config.inject({}) { |h,kv| h[kv.first.to_sym] = kv.last ; h }
    else
      config = { :connection=>config }
    end
  else
    config = {}
  end

  @options = defaults.merge(config).merge(options)
  if @options[:host] == 'redis' && @options.values_at(:host, :port, :db).any?
    warn "Deprecated: please specify Redis connection as URL (\"redis://host:port/db\")"
    establish_connection :adapter=>"redis", :host=>@options[:host], :port=>@options[:port], :database=>@options[:db] || @options[:database]
  elsif @options[:redis]
    @adapter = RedisAdapter.new(:redis=>@options[:redis])
  else
    connection_spec = args.shift || @options[:connection]
    if connection_spec
      connection_spec = "redis://" + connection_spec unless connection_spec[/^\w+:/]
      establish_connection connection_spec
    end
  end

  warn "Deprecated: namespace option no longer supported directly" if @options[:namespace]
  @load_path = @options[:load_path] || DEFAULTS[:load_path]
  unless @logger = @options[:logger]
    @logger = Logger.new(STDOUT)
    @logger.level = Logger::ERROR
  end
  @loading = []
  @use_js = false
  self.add_participant_path = DEFAULT_ADD_PARTICIPANT_PATH
  @collecting = !!@options[:collecting]
end

Instance Attribute Details

#add_participant_path ⇒ Object

Path to the add_participant action, necessary if you have called use_js!



77
78
79
# File 'lib/vanity/playground.rb', line 77

def add_participant_path
  @add_participant_path
end

#db ⇒ Object

Deprecated. Use redis.server instead.



68
69
70
# File 'lib/vanity/playground.rb', line 68

def db
  @db
end

#host ⇒ Object

Deprecated. Use redis.server instead.



68
69
70
# File 'lib/vanity/playground.rb', line 68

def host
  @host
end

#load_path ⇒ Object

Path to load experiment files from.



71
72
73
# File 'lib/vanity/playground.rb', line 71

def load_path
  @load_path
end

#logger ⇒ Object

Logger.



74
75
76
# File 'lib/vanity/playground.rb', line 74

def logger
  @logger
end

#namespace ⇒ Object

Deprecated. Use redis.server instead.



68
69
70
# File 'lib/vanity/playground.rb', line 68

def namespace
  @namespace
end

#password ⇒ Object

Deprecated. Use redis.server instead.



68
69
70
# File 'lib/vanity/playground.rb', line 68

def password
  @password
end

#port ⇒ Object

Deprecated. Use redis.server instead.



68
69
70
# File 'lib/vanity/playground.rb', line 68

def port
  @port
end

Instance Method Details

#collecting=(enabled) ⇒ Object

Turns data collection on and off.

Since:

  • 1.4.0



182
183
184
# File 'lib/vanity/playground.rb', line 182

def collecting=(enabled)
  @collecting = !!enabled
end

#collecting? ⇒ Boolean

True if collection data (metrics and experiments). You only want to collect data in production environment, everywhere else run with collection off.

Returns:

  • (Boolean)

Since:

  • 1.4.0



175
176
177
# File 'lib/vanity/playground.rb', line 175

def collecting?
  @collecting
end

#config_file_exists?(basename = "vanity.yml") ⇒ Boolean

Returns:

  • (Boolean)


281
282
283
# File 'lib/vanity/playground.rb', line 281

def config_file_exists?(basename = "vanity.yml")
  File.exists?(config_file_root + basename)
end

#config_file_root ⇒ Object



277
278
279
# File 'lib/vanity/playground.rb', line 277

def config_file_root
  (defined?(::Rails) ? ::Rails.root : Pathname.new(".")) + "config"
end

#connected? ⇒ Boolean

Returns true if connection is open.

Returns:

  • (Boolean)

Since:

  • 1.4.0



299
300
301
# File 'lib/vanity/playground.rb', line 299

def connected?
  @adapter && @adapter.active?
end

#connection ⇒ Object

Returns the current connection. Establishes new connection is necessary.

Since:

  • 1.4.0



292
293
294
# File 'lib/vanity/playground.rb', line 292

def connection
  @adapter || establish_connection
end

#define(name, type, options = {}, &block) ⇒ Object

Defines a new experiment. Generally, do not call this directly, use one of the definition methods (ab_test, measure, etc).

See Also:



83
84
85
86
87
88
89
90
91
92
# File 'lib/vanity/playground.rb', line 83

def define(name, type, options = {}, &block)
  warn "Deprecated: if you need this functionality let's make a better API"
  id = name.to_s.downcase.gsub(/\W/, "_").to_sym
  raise "Experiment #{id} already defined once" if experiments[id]
  klass = Experiment.const_get(type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
  experiment = klass.new(self, id, name, options)
  experiment.instance_eval &block
  experiment.save
  experiments[id] = experiment
end

#disconnect! ⇒ Object

Closes the current connection.

Since:

  • 1.4.0



306
307
308
# File 'lib/vanity/playground.rb', line 306

def disconnect!
  @adapter.disconnect! if @adapter
end

#establish_connection(spec = nil) ⇒ Object

This is the preferred way to programmatically create a new connection (or switch to a new connection). If no connection was established, the playground will create a new one by calling this method with no arguments.

With no argument, uses the connection specified in config/vanity.yml file for the current environment (RACK_ENV, RAILS_ENV or development). If there is no config/vanity.yml file, picks the configuration from config/redis.yml, or defaults to Redis on localhost, port 6379.

If the argument is a symbol, uses the connection specified in config/vanity.yml for that environment. For example:

Vanity.playground.establish_connection :production

If the argument is a string, it is processed as a URL. For example:

Vanity.playground.establish_connection "redis://redis.local/5"

Otherwise, the argument is a hash and specifies the adapter name and any additional options understood by that adapter (as with config/vanity.yml). For example:

Vanity.playground.establish_connection :adapter=>:redis,
                                     :host=>"redis.local"

Since:

  • 1.4.0



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/vanity/playground.rb', line 245

def establish_connection(spec = nil)
  @spec = spec
  disconnect! if @adapter
  case spec
  when nil
    if config_file_exists?
      env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
      spec = load_config_file[env]
      fail "No configuration for #{env}" unless spec
      establish_connection spec
    elsif config_file_exists?("redis.yml")
      env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
      redis = load_config_file("redis.yml")[env]
      fail "No configuration for #{env}" unless redis
      establish_connection "redis://" + redis
    else
      establish_connection :adapter=>"redis"
    end
  when Symbol
    spec = load_config_file[spec.to_s]
    establish_connection spec
  when String
    uri = URI.parse(spec)
    params = CGI.parse(uri.query) if uri.query
    establish_connection :adapter=>uri.scheme, :username=>uri.user, :password=>uri.password,
      :host=>uri.host, :port=>uri.port, :path=>uri.path, :params=>params
  else
    spec = spec.inject({}) { |hash,(k,v)| hash[k.to_sym] = v ; hash }
    @adapter = Adapters.establish_connection(spec)
  end
end

#experiment(name) ⇒ Object

Returns the experiment. You may not have guessed, but this method raises an exception if it cannot load the experiment's definition.

See Also:



98
99
100
101
102
# File 'lib/vanity/playground.rb', line 98

def experiment(name)
  id = name.to_s.downcase.gsub(/\W/, "_").to_sym
  warn "Deprecated: pleae call experiment method with experiment identifier (a Ruby symbol)" unless id == name
  experiments[id.to_sym] or raise NameError, "No experiment #{id}"
end

#experiments ⇒ Object

Returns hash of experiments (key is experiment id).

See Also:



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/vanity/playground.rb', line 135

def experiments
  unless @experiments
    @experiments = {}
    @logger.info "Vanity: loading experiments from #{load_path}"
    Dir[File.join(load_path, "*.rb")].each do |file|
      experiment = Experiment::Base.load(self, @loading, file)
      experiment.save
    end
  end
  @experiments
end

#load! ⇒ Object

Loads all metrics and experiments. Rails calls this during initialization.



157
158
159
160
# File 'lib/vanity/playground.rb', line 157

def load!
  experiments
  metrics
end

#load_config_file(basename = "vanity.yml") ⇒ Object



285
286
287
# File 'lib/vanity/playground.rb', line 285

def load_config_file(basename = "vanity.yml")
  YAML.load(ERB.new(File.read(config_file_root + basename)).result)
end

#metric(id) ⇒ Object

Returns a metric (raises NameError if no metric with that identifier).

See Also:

Since:

  • 1.1.0



166
167
168
# File 'lib/vanity/playground.rb', line 166

def metric(id)
  metrics[id.to_sym] or raise NameError, "No metric #{id}"
end

#metrics ⇒ Object

Returns hash of metrics (key is metric id).

See Also:

Since:

  • 1.1.0



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/vanity/playground.rb', line 190

def metrics
  unless @metrics
    @metrics = {}
    @logger.info "Vanity: loading metrics from #{load_path}/metrics"
    Dir[File.join(load_path, "metrics/*.rb")].each do |file|
      Metric.load self, @loading, file
    end
    if config_file_exists? && remote = load_config_file["metrics"]
      remote.each do |id, url|
        fail "Metric #{id} already defined in playground" if metrics[id.to_sym]
        metric = Metric.new(self, id)
        metric.remote url
        metrics[id.to_sym] = metric
      end
    end
  end
  @metrics
end

#reconnect! ⇒ Object

Closes the current connection and establishes a new one.

Since:

  • 1.3.0



313
314
315
# File 'lib/vanity/playground.rb', line 313

def reconnect!
  establish_connection(@spec)
end

#redis ⇒ Object



340
341
342
343
# File 'lib/vanity/playground.rb', line 340

def redis
  warn "Deprecated: use connection method instead"
  connection
end

#redis=(spec_or_connection) ⇒ Object

Deprecated. Use establish_connection or configuration file instead.



326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/vanity/playground.rb', line 326

def redis=(spec_or_connection)
  warn "Deprecated: use establish_connection method instead"
  case spec_or_connection
  when String
    establish_connection "redis://" + spec_or_connection
  when ::Redis
    @connection = Adapters::RedisAdapter.new(spec_or_connection)
  when :mock
    establish_connection :adapter=>:mock
  else
    raise "I don't know what to do with #{spec_or_connection.inspect}"
  end
end

#reload! ⇒ Object

Reloads all metrics and experiments. Rails calls this for each request in development mode.



149
150
151
152
153
# File 'lib/vanity/playground.rb', line 149

def reload!
  @experiments = nil
  @metrics = nil
  load!
end

#test! ⇒ Object

Deprecated. Use Vanity.playground.collecting = true/false instead. Under Rails, collecting is true in production environment, false in all other environments, which is exactly what you want.



320
321
322
323
# File 'lib/vanity/playground.rb', line 320

def test!
  warn "Deprecated: use collecting = false instead"
  self.collecting = false
end

#track!(id, count = 1) ⇒ Object

Tracks an action associated with a metric.

Examples:

Vanity.playground.track! :uploaded_video

Since:

  • 1.1.0



215
216
217
# File 'lib/vanity/playground.rb', line 215

def track!(id, count = 1)
  metric(id).track! count
end

#use_js! ⇒ Object

Call to indicate that participants should be added via js This helps keep robots from participating in the ab test and skewing results.

If you use this, there are two more steps:

  • Set Vanity.playground.add_participant_path = '/path/to/vanity/action', this should point to the add_participant path that is added with Vanity::Rails::Dashboard, make sure that this action is available to all users
  • Add <%= vanity_js %> to any page that needs uses an ab_test. vanity_js needs to be included after your call to ab_test so that it knows which version of the experiment the participant is a member of. The helper will render nothing if the there are no ab_tests running on the current page, so adding vanity_js to the bottom of your layouts is a good option. Keep in mind that if you call use_js! and don't include vanity_js in your view no participants will be recorded.


123
124
125
# File 'lib/vanity/playground.rb', line 123

def use_js!
  @use_js = true
end

#using_js? ⇒ Boolean

Returns:

  • (Boolean)


127
128
129
# File 'lib/vanity/playground.rb', line 127

def using_js?
  @use_js
end