vanity 1.3.0 → 1.4.0.beta

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 (99) hide show
  1. data/CHANGELOG +61 -3
  2. data/Gemfile +22 -14
  3. data/README.rdoc +9 -4
  4. data/Rakefile +72 -12
  5. data/bin/vanity +16 -4
  6. data/lib/vanity.rb +7 -5
  7. data/lib/vanity/adapters/abstract_adapter.rb +135 -0
  8. data/lib/vanity/adapters/mock_adapter.rb +157 -0
  9. data/lib/vanity/adapters/mongo_adapter.rb +162 -0
  10. data/lib/vanity/adapters/redis_adapter.rb +154 -0
  11. data/lib/vanity/backport.rb +0 -17
  12. data/lib/vanity/commands/upgrade.rb +34 -0
  13. data/lib/vanity/experiment/ab_test.rb +46 -41
  14. data/lib/vanity/experiment/base.rb +13 -15
  15. data/lib/vanity/frameworks/rails.rb +5 -9
  16. data/lib/vanity/metric/active_record.rb +10 -4
  17. data/lib/vanity/metric/base.rb +46 -23
  18. data/lib/vanity/metric/google_analytics.rb +7 -0
  19. data/lib/vanity/metric/remote.rb +53 -0
  20. data/lib/vanity/playground.rb +133 -49
  21. data/test/{ab_test_test.rb → experiment/ab_test.rb} +47 -3
  22. data/test/{experiment_test.rb → experiment/base_test.rb} +8 -8
  23. data/test/metric/active_record_test.rb +253 -0
  24. data/test/metric/base_test.rb +293 -0
  25. data/test/metric/google_analytics_test.rb +104 -0
  26. data/test/metric/remote_test.rb +108 -0
  27. data/test/myapp/app/controllers/application_controller.rbc +66 -0
  28. data/test/myapp/app/controllers/main_controller.rb +3 -3
  29. data/test/myapp/app/controllers/main_controller.rbc +347 -0
  30. data/test/myapp/config/boot.rbc +2534 -0
  31. data/test/myapp/config/environment.rbc +403 -0
  32. data/test/myapp/config/routes.rbc +174 -0
  33. data/test/myapp/log/production.log +2601 -0
  34. data/test/passenger_test.rb +14 -5
  35. data/test/passenger_test.rbc +0 -0
  36. data/test/playground_test.rbc +256 -0
  37. data/test/rails_test.rb +75 -22
  38. data/test/rails_test.rbc +4086 -0
  39. data/test/test_helper.rb +30 -7
  40. data/test/test_helper.rbc +4297 -0
  41. data/vanity.gemspec +6 -2
  42. metadata +74 -73
  43. data/lib/vanity/commands.rb +0 -2
  44. data/lib/vanity/mock_redis.rb +0 -76
  45. data/test/metric_test.rb +0 -622
  46. data/vendor/cache/RedCloth-4.2.2.gem +0 -0
  47. data/vendor/cache/actionmailer-2.3.5.gem +0 -0
  48. data/vendor/cache/actionpack-2.3.5.gem +0 -0
  49. data/vendor/cache/activerecord-2.3.5.gem +0 -0
  50. data/vendor/cache/activeresource-2.3.5.gem +0 -0
  51. data/vendor/cache/activesupport-2.3.5.gem +0 -0
  52. data/vendor/cache/autotest-4.2.7.gem +0 -0
  53. data/vendor/cache/autotest-fsevent-0.2.1.gem +0 -0
  54. data/vendor/cache/autotest-growl-0.2.0.gem +0 -0
  55. data/vendor/cache/bundler-0.9.7.gem +0 -0
  56. data/vendor/cache/classifier-1.3.1.gem +0 -0
  57. data/vendor/cache/directory_watcher-1.3.1.gem +0 -0
  58. data/vendor/cache/fastthread-1.0.7.gem +0 -0
  59. data/vendor/cache/garb-0.7.0.gem +0 -0
  60. data/vendor/cache/happymapper-0.3.0.gem +0 -0
  61. data/vendor/cache/jekyll-0.5.7.gem +0 -0
  62. data/vendor/cache/libxml-ruby-1.1.3.gem +0 -0
  63. data/vendor/cache/liquid-2.0.0.gem +0 -0
  64. data/vendor/cache/maruku-0.6.0.gem +0 -0
  65. data/vendor/cache/mocha-0.9.8.gem +0 -0
  66. data/vendor/cache/open4-1.0.1.gem +0 -0
  67. data/vendor/cache/passenger-2.2.9.gem +0 -0
  68. data/vendor/cache/rack-1.0.1.gem +0 -0
  69. data/vendor/cache/rails-2.3.5.gem +0 -0
  70. data/vendor/cache/rake-0.8.7.gem +0 -0
  71. data/vendor/cache/rubygems-update-1.3.5.gem +0 -0
  72. data/vendor/cache/shoulda-2.10.3.gem +0 -0
  73. data/vendor/cache/sqlite3-ruby-1.2.5.gem +0 -0
  74. data/vendor/cache/stemmer-1.0.1.gem +0 -0
  75. data/vendor/cache/syntax-1.0.0.gem +0 -0
  76. data/vendor/cache/sys-uname-0.8.4.gem +0 -0
  77. data/vendor/cache/timecop-0.3.4.gem +0 -0
  78. data/vendor/redis-rb/LICENSE +0 -20
  79. data/vendor/redis-rb/README.markdown +0 -36
  80. data/vendor/redis-rb/Rakefile +0 -62
  81. data/vendor/redis-rb/bench.rb +0 -44
  82. data/vendor/redis-rb/benchmarking/suite.rb +0 -24
  83. data/vendor/redis-rb/benchmarking/worker.rb +0 -71
  84. data/vendor/redis-rb/bin/distredis +0 -33
  85. data/vendor/redis-rb/examples/basic.rb +0 -16
  86. data/vendor/redis-rb/examples/incr-decr.rb +0 -18
  87. data/vendor/redis-rb/examples/list.rb +0 -26
  88. data/vendor/redis-rb/examples/sets.rb +0 -36
  89. data/vendor/redis-rb/lib/dist_redis.rb +0 -124
  90. data/vendor/redis-rb/lib/hash_ring.rb +0 -128
  91. data/vendor/redis-rb/lib/pipeline.rb +0 -21
  92. data/vendor/redis-rb/lib/redis.rb +0 -370
  93. data/vendor/redis-rb/lib/redis/raketasks.rb +0 -1
  94. data/vendor/redis-rb/profile.rb +0 -22
  95. data/vendor/redis-rb/redis-rb.gemspec +0 -30
  96. data/vendor/redis-rb/spec/redis_spec.rb +0 -637
  97. data/vendor/redis-rb/spec/spec_helper.rb +0 -4
  98. data/vendor/redis-rb/speed.rb +0 -16
  99. data/vendor/redis-rb/tasks/redis.tasks.rb +0 -140
@@ -51,6 +51,13 @@ module Vanity
51
51
  @ga_resource
52
52
  end
53
53
 
54
+ # Unkown (for now).
55
+ def last_update_at
56
+ end
57
+
58
+ def track!(args = nil)
59
+ end
60
+
54
61
  class Resource
55
62
  # GA profile used for this report. Populated after calling results.
56
63
  attr_reader :profile
@@ -0,0 +1,53 @@
1
+ require "net/http"
2
+ require "cgi"
3
+
4
+ module Vanity
5
+ class Metric
6
+
7
+ # Specifies the base URL to use for a remote metric. For example:
8
+ # metric :sandbox do
9
+ # remote "http://api.vanitydash.com/metrics/sandbox"
10
+ # end
11
+ def remote(url = nil)
12
+ @remote_url = URI.parse(url) if url
13
+ @mutex ||= Mutex.new
14
+ extend Remote
15
+ @remote_url
16
+ end
17
+
18
+ # To update a remote metric, make a POST request to the metric URL with the
19
+ # content type "application/x-www-form-urlencoded" and the following
20
+ # fields:
21
+ # - The +metric+ identifier,
22
+ # - The +timestamp+ must be RFC 2616 formatted (in Ruby just call +httpdate+
23
+ # on the Time object),
24
+ # - The +identity+ (optional),
25
+ # - Pass consecutive values using the field +values[]+, or
26
+ # - Set values by their index using +values[0]+, +values[1]+, etc or
27
+ # - Set values by series name using +values[foo]+, +values[bar]+, etc.
28
+ module Remote
29
+
30
+ def track!(args = nil)
31
+ return unless @playground.collecting?
32
+ timestamp, identity, values = track_args(args)
33
+ params = ["metric=#{CGI.escape @id.to_s}", "timestamp=#{CGI.escape timestamp.httpdate}"]
34
+ params << "identity=#{CGI.escape identity.to_s}" if identity
35
+ params.concat values.map { |v| "values[]=#{v.to_i}" }
36
+ params << @remote_url.query if @remote_url.query
37
+ @mutex.synchronize do
38
+ @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("&")
40
+ end
41
+ rescue Timeout::Error, StandardError
42
+ @playground.logger.error "Error sending data for metric #{name}: #{$!}"
43
+ @http = nil
44
+ ensure
45
+ call_hooks timestamp, identity, values
46
+ end
47
+
48
+ # "Don't worry, be crappy. Revolutionary means you ship and then test."
49
+ # -- Guy Kawazaki
50
+
51
+ end
52
+ end
53
+ end
@@ -1,3 +1,5 @@
1
+ require "uri"
2
+
1
3
  module Vanity
2
4
 
3
5
  # Playground catalogs all your experiments, holds the Vanity configuration.
@@ -7,11 +9,7 @@ module Vanity
7
9
  # puts Vanity.playground.map(&:name)
8
10
  class Playground
9
11
 
10
- DEFAULTS = {
11
- :connection=>"localhost:6379",
12
- :load_path=>"experiments",
13
- :namespace=>"vanity:#{Vanity::Version::MAJOR}"
14
- }
12
+ DEFAULTS = { :load_path=>"experiments" }
15
13
 
16
14
  # Created new Playground. Unless you need to, use the global
17
15
  # Vanity.playground.
@@ -26,22 +24,23 @@ module Vanity
26
24
  options = args.pop if Hash === args.last
27
25
  @options = DEFAULTS.merge(options || {})
28
26
  if @options.values_at(:host, :port, :db).any?
29
- warn "Deprecated: please specify Redis connection as single argument (\"host:port\")"
30
- @connection_spec = "#{@options[:host]}:#{@options[:port]}:#{@options[:db]}"
27
+ warn "Deprecated: please specify Redis connection as URL (\"redis://host:port/db\")"
28
+ establish_connection :adapter=>"redis", :host=>options[:host], :port=>options[:port], :database=>options[:db]
31
29
  elsif @options[:redis]
32
- @redis = @options[:redis]
30
+ @adapter = RedisAdapter.new(:redis=>@options[:redis])
33
31
  else
34
- @connection_spec = args.shift || @options[:connection]
32
+ connection_spec = args.shift || @options[:connection]
33
+ establish_connection "redis://" + connection_spec if connection_spec
35
34
  end
36
35
 
37
- @namespace = @options[:namespace] || DEFAULTS[:namespace]
36
+ warn "Deprecated: namespace option no longer supported directly" if @options[:namespace]
38
37
  @load_path = @options[:load_path] || DEFAULTS[:load_path]
39
- @logger = @options[:logger]
40
- unless @logger
38
+ unless @logger = @options[:logger]
41
39
  @logger = Logger.new(STDOUT)
42
40
  @logger.level = Logger::ERROR
43
41
  end
44
42
  @loading = []
43
+ @collecting = true
45
44
  end
46
45
 
47
46
  # Deprecated. Use redis.server instead.
@@ -115,6 +114,22 @@ module Vanity
115
114
  metrics[id.to_sym] or raise NameError, "No metric #{id}"
116
115
  end
117
116
 
117
+ # True if collection data (metrics and experiments). You only want to
118
+ # collect data in production environment, everywhere else run with
119
+ # collection off.
120
+ #
121
+ # @since 1.4.0
122
+ def collecting?
123
+ @collecting
124
+ end
125
+
126
+ # Turns data collection on and off.
127
+ #
128
+ # @since 1.4.0
129
+ def collecting=(enabled)
130
+ @collecting = !!enabled
131
+ end
132
+
118
133
  # Returns hash of metrics (key is metric id).
119
134
  #
120
135
  # @see Vanity::Metric
@@ -126,6 +141,14 @@ module Vanity
126
141
  Dir[File.join(load_path, "metrics/*.rb")].each do |file|
127
142
  Metric.load self, @loading, file
128
143
  end
144
+ if File.exist?("config/vanity.yml") && remote = YAML.load_file("config/vanity.yml")["metrics"]
145
+ remote.each do |id, url|
146
+ fail "Metric #{id} already defined in playground" if metrics[id.to_sym]
147
+ metric = Metric.new(self, id)
148
+ metric.remote url
149
+ metrics[id.to_sym] = metric
150
+ end
151
+ end
129
152
  end
130
153
  @metrics
131
154
  end
@@ -142,61 +165,122 @@ module Vanity
142
165
 
143
166
 
144
167
  # -- Connection management --
145
-
146
- # Tells the playground where to find Redis. Accepts one of the following:
147
- # - "hostname:port"
148
- # - ":port"
149
- # - "hostname:port:db"
150
- # - Instance of Redis connection.
151
- def redis=(spec_or_connection)
152
- case spec_or_connection
168
+
169
+ # This is the preferred way to programmatically create a new connection (or
170
+ # switch to a new connection). If no connection was established, the
171
+ # playground will create a new one by calling this method with no arguments.
172
+ #
173
+ # With no argument, uses the connection specified in config/vanity.yml file
174
+ # for the current environment (RACK_ENV, RAILS_ENV or development). If there
175
+ # is no config/vanity.yml file, picks the configuration from
176
+ # config/redis.yml, or defaults to Redis on localhost, port 6379.
177
+ #
178
+ # If the argument is a symbol, uses the connection specified in
179
+ # config/vanity.yml for that environment. For example:
180
+ # Vanity.playground.establish_connection :production
181
+ #
182
+ # If the argument is a string, it is processed as a URL. For example:
183
+ # Vanity.playground.establish_connection "redis://redis.local/5"
184
+ #
185
+ # Otherwise, the argument is a hash and specifies the adapter name and any
186
+ # additional options understood by that adapter (as with config/vanity.yml).
187
+ # For example:
188
+ # Vanity.playground.establish_connection :adapter=>:redis,
189
+ # :host=>"redis.local"
190
+ #
191
+ # @since 1.4.0
192
+ def establish_connection(spec = nil)
193
+ disconnect! if @adapter
194
+ case spec
195
+ when nil
196
+ if File.exists?("config/vanity.yml")
197
+ env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
198
+ spec = YAML.load_file("config/vanity.yml")[env]
199
+ fail "No configuration for #{env}" unless spec
200
+ establish_connection spec
201
+ elsif File.exists?("config/redis.yml")
202
+ env = ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
203
+ redis = YAML.load_file("config/redis.yml")[env]
204
+ fail "No configuration for #{env}" unless redis
205
+ establish_connection "redis://" + redis
206
+ else
207
+ establish_connection :adapter=>"redis"
208
+ end
209
+ when Symbol
210
+ spec = YAML.load_file("config/vanity.yml")[spec.to_s]
211
+ establish_connection spec
153
212
  when String
154
- @connection_spec = spec_or_connection
155
- host, port, db = spec_or_connection.split(':').map { |x| x unless x.empty? }
156
- @redis = Redis.new(:host=>host, :port=>port, :thread_safe=>true, :db=>db, :thread_safe=>true)
157
- when Redis
158
- @connection_spec = nil
159
- @redis = spec_or_connection
160
- when :mock
161
- @connection_spec = nil
162
- @redis = MockRedis.new
213
+ uri = URI.parse(spec)
214
+ params = CGI.parse(uri.query) if uri.query
215
+ establish_connection :adapter=>uri.scheme, :username=>uri.user, :password=>uri.password,
216
+ :host=>uri.host, :port=>uri.port, :path=>uri.path, :params=>params
163
217
  else
164
- raise "I don't know what to do with #{spec_or_connection.inspect}"
218
+ spec = spec.inject({}) { |hash,(k,v)| hash[k.to_sym] = v ; hash }
219
+ begin
220
+ require "vanity/adapters/#{spec[:adapter]}_adapter"
221
+ rescue LoadError
222
+ raise "Could not find #{spec[:adapter]} in your load path"
223
+ end
224
+ @adapter = Adapters.establish_connection(spec)
165
225
  end
166
226
  end
167
227
 
168
- def redis
169
- self.redis = @connection_spec unless @redis
170
- @redis
228
+ # Returns the current connection. Establishes new connection is necessary.
229
+ #
230
+ # @since 1.4.0
231
+ def connection
232
+ @adapter || establish_connection
171
233
  end
172
234
 
235
+ # Returns true if connection is open.
236
+ #
237
+ # @since 1.4.0
173
238
  def connected?
174
- !@redis.nil?
239
+ @adapter && @adapter.active?
175
240
  end
176
241
 
242
+ # Closes the current connection.
243
+ #
244
+ # @since 1.4.0
177
245
  def disconnect!
178
- @redis.quit if connected? rescue nil
179
- @redis = nil
246
+ @adapter.disconnect! if @adapter
180
247
  end
181
248
 
249
+ # Closes the current connection and establishes a new one.
250
+ #
251
+ # @since 1.3.0
182
252
  def reconnect!
183
- raise "Connect reconnect without connection specification" unless String === @connection_spec
184
- disconnect! rescue nil
253
+ establish_connection
185
254
  end
186
255
 
187
- def mock!
188
- warn "Deprecated: use Vanity.playground.test!"
189
- test!
190
- end
191
-
192
- # Use this when testing to disable Redis (e.g. if your CI server doesn't
193
- # have Redis).
194
- #
195
- # @example Put this in config/environments/test.rb
196
- # config.after_initialize { Vanity.playground.test! }
256
+ # Deprecated. Use Vanity.playground.collecting = true/false instead. Under
257
+ # Rails, collecting is true in production environment, false in all other
258
+ # environments, which is exactly what you want.
197
259
  def test!
198
- self.redis = :mock
260
+ warn "Deprecated: use collecting = false instead"
261
+ self.collecting = false
262
+ end
263
+
264
+ # Deprecated. Use establish_connection or configuration file instead.
265
+ def redis=(spec_or_connection)
266
+ warn "Deprecated: use establish_connection method instead"
267
+ case spec_or_connection
268
+ when String
269
+ establish_connection "redis://" + spec_or_connection
270
+ when ::Redis
271
+ @connection = Adapters::RedisAdapter.new(spec_or_connection)
272
+ when :mock
273
+ establish_connection :adapter=>:mock
274
+ else
275
+ raise "I don't know what to do with #{spec_or_connection.inspect}"
276
+ end
199
277
  end
278
+
279
+ def redis
280
+ warn "Deprecated: use connection method instead"
281
+ connection
282
+ end
283
+
200
284
  end
201
285
 
202
286
  @playground = Playground.new
@@ -359,10 +359,10 @@ class AbTestTest < ActionController::TestCase
359
359
  assert experiment(:abcd).score.alts.all? { |alt| alt.z_score.nan? }
360
360
  assert experiment(:abcd).score.alts.all? { |alt| alt.probability == 0 }
361
361
  assert experiment(:abcd).score.alts.all? { |alt| alt.difference.nil? }
362
- assert 1, experiment(:abcd).score.best.id
362
+ assert_equal 1, experiment(:abcd).score.best.id
363
363
  assert_nil experiment(:abcd).score.choice
364
- assert 1, experiment(:abcd).score.base.id
365
- assert 1, experiment(:abcd).score.least.id
364
+ assert_equal 2, experiment(:abcd).score.base.id
365
+ assert_equal 1, experiment(:abcd).score.least.id
366
366
  end
367
367
 
368
368
  def test_scoring_with_some_performers
@@ -647,6 +647,50 @@ This experiment did not run long enough to find a clear winner.
647
647
  end
648
648
 
649
649
 
650
+ # -- No collection --
651
+
652
+ def test_no_collection_does_not_track
653
+ not_collecting!
654
+ metric "Coolness"
655
+ new_ab_test :abcd do
656
+ metrics :coolness
657
+ end
658
+ Vanity.playground.track! :coolness
659
+ assert_equal 0, experiment(:abcd).alternatives.sum(&:conversions)
660
+ end
661
+
662
+ def test_no_collection_and_completion
663
+ not_collecting!
664
+ new_ab_test :quick do
665
+ outcome_is { alternatives[1] }
666
+ metrics :coolness
667
+ end
668
+ experiment(:quick).complete!
669
+ assert_nil experiment(:quick).outcome
670
+ end
671
+
672
+ def test_no_collection_and_chooses
673
+ not_collecting!
674
+ new_ab_test :simple do
675
+ alternatives :a, :b, :c
676
+ end
677
+ assert !experiment(:simple).showing?(experiment(:simple).alternatives[1])
678
+ experiment(:simple).chooses(:b)
679
+ assert experiment(:simple).showing?(experiment(:simple).alternatives[1])
680
+ assert !experiment(:simple).showing?(experiment(:simple).alternatives[2])
681
+ end
682
+
683
+ def test_no_collection_chooses_without_database
684
+ not_collecting!
685
+ new_ab_test :simple do
686
+ alternatives :a, :b, :c
687
+ end
688
+ choice = experiment(:simple).choose
689
+ assert [:a, :b, :c].include?(choice)
690
+ assert_equal choice, experiment(:simple).choose
691
+ end
692
+
693
+
650
694
  # -- Helper methods --
651
695
 
652
696
  def fake(name, args)
@@ -30,12 +30,6 @@ class ExperimentTest < Test::Unit::TestCase
30
30
  end
31
31
  end
32
32
 
33
- def test_uses_playground_namespace_for_experiment
34
- new_ab_test(:ice_cream_flavor) { metrics :happiness }
35
- assert_equal "vanity:#{Vanity::Version::MAJOR}:ice_cream_flavor", experiment(:ice_cream_flavor).send(:key)
36
- assert_equal "vanity:#{Vanity::Version::MAJOR}:ice_cream_flavor:participants", experiment(:ice_cream_flavor).send(:key, "participants")
37
- end
38
-
39
33
 
40
34
  # -- Loading experiments --
41
35
 
@@ -80,7 +74,7 @@ class ExperimentTest < Test::Unit::TestCase
80
74
  def test_reloading_experiments
81
75
  new_ab_test(:ab) { metrics :happiness }
82
76
  new_ab_test(:cd) { metrics :happiness }
83
- assert 2, Vanity.playground.experiments.size
77
+ assert_equal 2, Vanity.playground.experiments.size
84
78
  Vanity.playground.reload!
85
79
  assert Vanity.playground.experiments.empty?
86
80
  end
@@ -114,7 +108,7 @@ class ExperimentTest < Test::Unit::TestCase
114
108
 
115
109
  def test_experiment_keeps_created_timestamp_across_definitions
116
110
  past = Date.today - 1
117
- Timecop.travel past do
111
+ Timecop.freeze past do
118
112
  new_ab_test(:ice_cream_flavor) { metrics :happiness }
119
113
  assert_equal past.to_time.to_i, experiment(:ice_cream_flavor).created_at.to_i
120
114
  end
@@ -133,4 +127,10 @@ class ExperimentTest < Test::Unit::TestCase
133
127
  assert_equal "Because 31 is not enough ...", experiment(:ice_cream_flavor).description
134
128
  end
135
129
 
130
+ def test_experiment_stores_nothing_when_collection_disabled
131
+ not_collecting!
132
+ new_ab_test(:ice_cream_flavor) { metrics :happiness }
133
+ experiment(:ice_cream_flavor).complete!
134
+ end
135
+
136
136
  end
@@ -0,0 +1,253 @@
1
+ require "test/test_helper"
2
+
3
+ class Sky < ActiveRecord::Base
4
+ connection.drop_table :skies if table_exists?
5
+ connection.create_table :skies do |t|
6
+ t.integer :height
7
+ t.timestamps
8
+ end
9
+
10
+ named_scope :high, lambda { { :conditions=>"height >= 4" } }
11
+ end
12
+
13
+
14
+ context "ActiveRecord Metric" do
15
+
16
+ test "record count" do
17
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
18
+ f.write <<-RUBY
19
+ metric "Sky is limit" do
20
+ model Sky
21
+ end
22
+ RUBY
23
+ end
24
+ Vanity.playground.metrics
25
+ Sky.create!
26
+ assert_equal 1, Sky.count
27
+ assert_equal 1, Vanity::Metric.data(metric(:sky_is_limit)).last.last
28
+ end
29
+
30
+ test "record sum" do
31
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
32
+ f.write <<-RUBY
33
+ metric "Sky is limit" do
34
+ model Sky, :sum=>:height
35
+ end
36
+ RUBY
37
+ end
38
+ Vanity.playground.metrics
39
+ Sky.create! :height=>4
40
+ Sky.create! :height=>2
41
+ assert_equal 6, Vanity::Metric.data(metric(:sky_is_limit)).last.last
42
+ end
43
+
44
+ test "record average" do
45
+ Sky.aggregates
46
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
47
+ f.write <<-RUBY
48
+ metric "Sky is limit" do
49
+ model Sky, :average=>:height
50
+ end
51
+ RUBY
52
+ end
53
+ Vanity.playground.metrics
54
+ Sky.create! :height=>4
55
+ Sky.create! :height=>2
56
+ assert_equal 3, Vanity::Metric.data(metric(:sky_is_limit)).last.last
57
+ end
58
+
59
+ test "record minimum" do
60
+ Sky.aggregates
61
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
62
+ f.write <<-RUBY
63
+ metric "Sky is limit" do
64
+ model Sky, :minimum=>:height
65
+ end
66
+ RUBY
67
+ end
68
+ Vanity.playground.metrics
69
+ Sky.create! :height=>4
70
+ Sky.create! :height=>2
71
+ assert_equal 2, Vanity::Metric.data(metric(:sky_is_limit)).last.last
72
+ end
73
+
74
+ test "record maximum" do
75
+ Sky.aggregates
76
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
77
+ f.write <<-RUBY
78
+ metric "Sky is limit" do
79
+ model Sky, :maximum=>:height
80
+ end
81
+ RUBY
82
+ end
83
+ Vanity.playground.metrics
84
+ Sky.create! :height=>4
85
+ Sky.create! :height=>2
86
+ assert_equal 4, Vanity::Metric.data(metric(:sky_is_limit)).last.last
87
+ end
88
+
89
+ test "with conditions" do
90
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
91
+ f.write <<-RUBY
92
+ metric "Sky is limit" do
93
+ model Sky, :sum=>:height, :conditions=>["height > 4"]
94
+ end
95
+ RUBY
96
+ end
97
+ Vanity.playground.metrics
98
+ high_skies = 0
99
+ metric(:sky_is_limit).hook do |metric_id, timestamp, height|
100
+ assert height > 4
101
+ high_skies += height
102
+ end
103
+ [nil,5,3,6].each do |height|
104
+ Sky.create! :height=>height
105
+ end
106
+ assert_equal 11, Vanity::Metric.data(metric(:sky_is_limit)).sum(&:last)
107
+ assert_equal 11, high_skies
108
+ end
109
+
110
+ test "with scope" do
111
+ Sky.aggregates
112
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
113
+ f.write <<-RUBY
114
+ metric "Sky is limit" do
115
+ model Sky.high
116
+ end
117
+ RUBY
118
+ end
119
+ Vanity.playground.metrics
120
+ total = 0
121
+ metric(:sky_is_limit).hook do |metric_id, timestamp, count|
122
+ total += count
123
+ end
124
+ Sky.create! :height=>4
125
+ Sky.create! :height=>2
126
+ assert_equal 1, Vanity::Metric.data(metric(:sky_is_limit)).last.last
127
+ assert_equal 1, total
128
+ end
129
+
130
+ test "hooks" do
131
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
132
+ f.write <<-RUBY
133
+ metric "Sky is limit" do
134
+ model Sky, :sum=>:height
135
+ end
136
+ RUBY
137
+ end
138
+ Vanity.playground.metrics
139
+ total = 0
140
+ metric(:sky_is_limit).hook do |metric_id, timestamp, count|
141
+ assert_equal :sky_is_limit, metric_id
142
+ assert_in_delta Time.now.to_i, timestamp.to_i, 1
143
+ total += count
144
+ end
145
+ Sky.create! :height=>4
146
+ assert_equal 4, total
147
+ end
148
+
149
+ test "no hooks when metrics disabled" do
150
+ not_collecting!
151
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
152
+ f.write <<-RUBY
153
+ metric "Sky is limit" do
154
+ model Sky, :sum=>:height
155
+ end
156
+ RUBY
157
+ end
158
+ Vanity.playground.metrics
159
+ total = 0
160
+ metric(:sky_is_limit).hook do |metric_id, timestamp, count|
161
+ total += count
162
+ end
163
+ Sky.create! :height=>4
164
+ assert_equal 0, total
165
+ end
166
+
167
+ test "after_create not after_save" do
168
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
169
+ f.write <<-RUBY
170
+ metric "Sky is limit" do
171
+ model Sky
172
+ end
173
+ RUBY
174
+ end
175
+ Vanity.playground.metrics
176
+ once = nil
177
+ metric(:sky_is_limit).hook do
178
+ fail "Metric tracked twice" if once
179
+ once = true
180
+ end
181
+ Sky.create!
182
+ Sky.last.update_attributes :height=>4
183
+ end
184
+
185
+ test "with after_save" do
186
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
187
+ f.write <<-RUBY
188
+ metric "Sky is limit" do
189
+ model Sky, :conditions=>["height > 3"]
190
+ Sky.after_save { |sky| track! if sky.height_changed? && sky.height > 3 }
191
+ end
192
+ RUBY
193
+ end
194
+ Vanity.playground.metrics
195
+ times = 0
196
+ metric(:sky_is_limit).hook do
197
+ times += 1
198
+ end
199
+ Sky.create!
200
+ (1..5).each do |height|
201
+ Sky.last.update_attributes! :height=>height
202
+ end
203
+ assert_equal 2, times
204
+ end
205
+
206
+ test "do it youself" do
207
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
208
+ f.write <<-RUBY
209
+ metric "Sky is limit" do
210
+ Sky.after_save { |sky| track! if sky.height_changed? && sky.height > 3 }
211
+ end
212
+ RUBY
213
+ end
214
+ Vanity.playground.metrics
215
+ (1..5).each do |height|
216
+ Sky.create! :height=>height
217
+ end
218
+ Sky.first.update_attributes! :height=>4
219
+ assert_equal 3, Vanity::Metric.data(metric(:sky_is_limit)).last.last
220
+ end
221
+
222
+ test "last update for new metric" do
223
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
224
+ f.write <<-RUBY
225
+ metric "Sky is limit" do
226
+ model Sky
227
+ end
228
+ RUBY
229
+ end
230
+ assert_nil metric(:sky_is_limit).last_update_at
231
+ end
232
+
233
+ test "last update with records" do
234
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
235
+ f.write <<-RUBY
236
+ metric "Sky is limit" do
237
+ model Sky
238
+ end
239
+ RUBY
240
+ end
241
+ Sky.create! :height=>1
242
+ Timecop.freeze Time.now + 1.day do
243
+ Sky.create! :height=>1
244
+ end
245
+ assert_in_delta metric(:sky_is_limit).last_update_at.to_i, (Time.now + 1.day).to_i, 1
246
+ end
247
+
248
+ teardown do
249
+ Sky.delete_all
250
+ Sky.after_create.clear
251
+ Sky.after_save.clear
252
+ end
253
+ end