bucket 0.0.1
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/Gemfile +3 -0
- data/README +3 -0
- data/Rakefile +50 -0
- data/TODO +52 -0
- data/init.rb +1 -0
- data/lib/bucket.rb +11 -0
- data/lib/bucket/base.rb +38 -0
- data/lib/bucket/frameworks/rails.rb +8 -0
- data/lib/bucket/test.rb +75 -0
- data/spec/lib/bucket_test_spec.rb +93 -0
- data/spec/spec_helper.rb +33 -0
- metadata +73 -0
data/Gemfile
ADDED
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rcov/rcovtask'
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'jeweler'
|
9
|
+
Jeweler::Tasks.new do |s|
|
10
|
+
s.name = "bucket"
|
11
|
+
s.summary = %Q{A/B testing.}
|
12
|
+
s.email = "tyler.kovacs@gmail.com"
|
13
|
+
s.homepage = "http://github.com/tylerkovacs/bucket"
|
14
|
+
s.description = "See README"
|
15
|
+
s.authors = ["tylerkovacs"]
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
Rake::TestTask.new do |t|
|
22
|
+
t.libs << 'lib'
|
23
|
+
t.pattern = 'test/**/*_test.rb'
|
24
|
+
t.verbose = false
|
25
|
+
end
|
26
|
+
|
27
|
+
Rake::RDocTask.new do |rdoc|
|
28
|
+
rdoc.rdoc_dir = 'rdoc'
|
29
|
+
rdoc.title = 'bucket'
|
30
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
31
|
+
rdoc.rdoc_files.include('README*')
|
32
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
33
|
+
end
|
34
|
+
|
35
|
+
Rcov::RcovTask.new do |t|
|
36
|
+
t.libs << 'spec'
|
37
|
+
t.test_files = FileList['spec/**/*_spec.rb']
|
38
|
+
t.verbose = true
|
39
|
+
end
|
40
|
+
|
41
|
+
task :noop do
|
42
|
+
end
|
43
|
+
|
44
|
+
Rake::TestTask.new(:spec => :noop) do |t|
|
45
|
+
t.libs << 'spec'
|
46
|
+
t.test_files = FileList['spec/**/*_spec.rb']
|
47
|
+
t.verbose = true
|
48
|
+
end
|
49
|
+
|
50
|
+
task :default => :test
|
data/TODO
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
Soon
|
2
|
+
- convert from plugin to gem
|
3
|
+
- put on github and rubygems
|
4
|
+
|
5
|
+
- general
|
6
|
+
- missing config directory shouldn't generate an exception at start
|
7
|
+
- measure spec coverage
|
8
|
+
- auto detect Rails and apply config hooks
|
9
|
+
- set controller filters, etc.
|
10
|
+
- apply different Rails config based on Rails version
|
11
|
+
- test under rails 2.3 and 3.0
|
12
|
+
- test under ruby 1.8.6, 1.8.7 and 1.9
|
13
|
+
- documentation
|
14
|
+
- comment source code liberally
|
15
|
+
- review all comments prior to first ship
|
16
|
+
- document all config options (logger, config_path)
|
17
|
+
- bucket::test
|
18
|
+
- invalid test attribute name should log error
|
19
|
+
- or should it just generate an exception?
|
20
|
+
- if so, should it be a custom exception?
|
21
|
+
- option weighting
|
22
|
+
- attribute validation
|
23
|
+
- can't have empty options
|
24
|
+
- option weights must add up to 100
|
25
|
+
- users have persistent assignment to a test
|
26
|
+
- users can participate once or multiple times
|
27
|
+
- ability to define tests within the view for quick deployment
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
config file syntax options
|
32
|
+
|
33
|
+
bucket_test do
|
34
|
+
name 'test 1'
|
35
|
+
options [1, 2, 3]
|
36
|
+
end
|
37
|
+
|
38
|
+
bucket_test do
|
39
|
+
name 'test 2'
|
40
|
+
options {
|
41
|
+
1 => {:weight => 10},
|
42
|
+
2 => {:weight => 50},
|
43
|
+
3 => {:weight => 40}
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
bucket_test do
|
48
|
+
name 'test 2'
|
49
|
+
options [1, 2, 3]
|
50
|
+
weights {1 => 10, 2 => 50, 3 => 40}
|
51
|
+
end
|
52
|
+
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bucket'
|
data/lib/bucket.rb
ADDED
data/lib/bucket/base.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class Bucket
|
4
|
+
module Base
|
5
|
+
@@config_path = File.join("config", "bucket")
|
6
|
+
@@logger = Logger.new(STDOUT)
|
7
|
+
|
8
|
+
ACCESSOR_NAMES = [:logger, :config_path]
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
|
13
|
+
ACCESSOR_NAMES.each do |accessor_name|
|
14
|
+
base.class_eval <<-EOF
|
15
|
+
def self.#{accessor_name}
|
16
|
+
@@#{accessor_name}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.#{accessor_name}=(value)
|
20
|
+
@@#{accessor_name} = value
|
21
|
+
end
|
22
|
+
EOF
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
def init
|
28
|
+
if !File.exists?(config_path)
|
29
|
+
logger.error("Bucket configuration directory missing: #{config_path}")
|
30
|
+
else
|
31
|
+
Dir.glob(File.join(config_path, "test_*")) do |filename|
|
32
|
+
Test.from_file(filename)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/bucket/test.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
class Bucket
|
2
|
+
class Test
|
3
|
+
# Class variable storing all defined tests.
|
4
|
+
@@tests = {}
|
5
|
+
|
6
|
+
# Bucket::Test DSL
|
7
|
+
#
|
8
|
+
# The Bucket::Test DSL defines tests.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
# bucket_test do
|
12
|
+
# name 'color test'
|
13
|
+
# options ['red', 'green', 'blue']
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Supported Attributes:
|
17
|
+
# name : The name of the test.
|
18
|
+
# options : Test options.
|
19
|
+
ATTRIBUTE_NAMES = [:name, :options]
|
20
|
+
|
21
|
+
# Create get/set methods for all methods supported in the DSL.
|
22
|
+
ATTRIBUTE_NAMES.each do |attribute_name|
|
23
|
+
class_eval <<-EOF
|
24
|
+
def #{attribute_name}(value=nil)
|
25
|
+
if value
|
26
|
+
@attributes['#{attribute_name}'] = value
|
27
|
+
else
|
28
|
+
@attributes['#{attribute_name}']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
EOF
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@attributes = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def choose
|
39
|
+
options[rand(options.length)]
|
40
|
+
end
|
41
|
+
|
42
|
+
def option
|
43
|
+
@option ||= choose
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
def from_file(filename)
|
48
|
+
from_string(File.read(filename))
|
49
|
+
end
|
50
|
+
|
51
|
+
def from_string(data)
|
52
|
+
instance_eval(data)
|
53
|
+
end
|
54
|
+
|
55
|
+
def bucket_test(options={}, &block)
|
56
|
+
Test.add_test(options, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_test(options={}, &block)
|
60
|
+
test = self.new
|
61
|
+
test.instance_eval(&block)
|
62
|
+
@@tests[test.name] = test
|
63
|
+
test
|
64
|
+
end
|
65
|
+
|
66
|
+
def number_of_tests
|
67
|
+
@@tests.length
|
68
|
+
end
|
69
|
+
|
70
|
+
def clear!
|
71
|
+
@@tests.clear
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe Bucket::Test do
|
5
|
+
before(:each) do
|
6
|
+
@definition =<<-EOF
|
7
|
+
bucket_test do
|
8
|
+
name 'test name'
|
9
|
+
options [1, 2, 3]
|
10
|
+
end
|
11
|
+
EOF
|
12
|
+
@test = Bucket::Test.from_string(@definition)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'attributes' do
|
16
|
+
it 'should accept a string for the name attribute' do
|
17
|
+
@test.name 'new test name'
|
18
|
+
@test.name.should == 'new test name'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should accept an array for the options attribute' do
|
22
|
+
@test.options [1, 2, 3, 4]
|
23
|
+
@test.options.should == [1, 2, 3, 4]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'from_string' do
|
28
|
+
it 'should set supported attribute' do
|
29
|
+
@test.name.should == 'test name'
|
30
|
+
@test.options.should == [1, 2, 3]
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should register the new test' do
|
34
|
+
Bucket::Test.number_of_tests.should == 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'from_file' do
|
39
|
+
it 'should read the test definition from a file' do
|
40
|
+
Bucket::Test.clear!
|
41
|
+
Bucket::Test.number_of_tests.should == 0
|
42
|
+
|
43
|
+
Tempfile.open('from_file') do |file|
|
44
|
+
file.write @definition
|
45
|
+
file.fsync
|
46
|
+
|
47
|
+
new_test = Bucket::Test.from_file(file.path)
|
48
|
+
new_test.name.should == 'test name'
|
49
|
+
new_test.options.should == [1, 2, 3]
|
50
|
+
end
|
51
|
+
|
52
|
+
Bucket::Test.number_of_tests.should == 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'clear!' do
|
57
|
+
it 'should remove all registered tests' do
|
58
|
+
Bucket::Test.number_of_tests.should == 1
|
59
|
+
Bucket::Test.clear!
|
60
|
+
Bucket::Test.number_of_tests.should == 0
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'choose' do
|
65
|
+
it 'should choose an option at random' do
|
66
|
+
option = @test.choose
|
67
|
+
option.should_not be_nil
|
68
|
+
@test.options.should include(option)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should choose an option using an even distribution' do
|
72
|
+
frequencies = Hash.new(0)
|
73
|
+
1000.times { frequencies[@test.choose] += 1}
|
74
|
+
|
75
|
+
frequencies.values.each do |val|
|
76
|
+
val.should be_close(333, 150)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'option' do
|
82
|
+
it 'should return the selected option' do
|
83
|
+
option = @test.option
|
84
|
+
option.should_not be_nil
|
85
|
+
@test.options.should include(option)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should not change between calls' do
|
89
|
+
option = @test.option
|
90
|
+
10.times { @test.option.should == option }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'spec'
|
6
|
+
require 'spec/autorun'
|
7
|
+
require 'ruby-debug'
|
8
|
+
|
9
|
+
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
|
10
|
+
# from the project root directory.
|
11
|
+
ENV["RAILS_ENV"] = "test"
|
12
|
+
ENV["BUCKET_ENV"] = "test"
|
13
|
+
|
14
|
+
require File.dirname(__FILE__) + '/../lib/bucket/base'
|
15
|
+
require File.dirname(__FILE__) + '/../lib/bucket/test'
|
16
|
+
require File.dirname(__FILE__) + '/../lib/bucket'
|
17
|
+
|
18
|
+
def load_environment
|
19
|
+
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
|
20
|
+
require 'spec/autorun'
|
21
|
+
require 'spec/rails'
|
22
|
+
require File.expand_path(File.dirname(__FILE__) + "/blueprints")
|
23
|
+
require 'lib/test/common_method'
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Runner.configure do |config|
|
27
|
+
config.before(:each) {
|
28
|
+
Bucket::Test.clear!
|
29
|
+
}
|
30
|
+
|
31
|
+
config.before(:all) { }
|
32
|
+
config.after(:each) { }
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bucket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- tylerkovacs
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-08 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: See README
|
22
|
+
email: tyler.kovacs@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README
|
29
|
+
files:
|
30
|
+
- Gemfile
|
31
|
+
- README
|
32
|
+
- Rakefile
|
33
|
+
- TODO
|
34
|
+
- init.rb
|
35
|
+
- lib/bucket.rb
|
36
|
+
- lib/bucket/base.rb
|
37
|
+
- lib/bucket/frameworks/rails.rb
|
38
|
+
- lib/bucket/test.rb
|
39
|
+
- spec/lib/bucket_test_spec.rb
|
40
|
+
- spec/spec_helper.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/tylerkovacs/bucket
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --charset=UTF-8
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.6
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: A/B testing.
|
71
|
+
test_files:
|
72
|
+
- spec/lib/bucket_test_spec.rb
|
73
|
+
- spec/spec_helper.rb
|