vanity 1.3.0 → 1.4.0.beta

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