bandit 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,7 +12,7 @@ Then, run the following:
12
12
  bundle install
13
13
  rails generate bandit:install
14
14
 
15
- You can then edit the bandit.yml file in your config directory to set your storage and player parameters. Redis, memcache, and memory storage options are available. Memory storage should only be used for testing.
15
+ You can then edit the bandit.yml file in your config directory to set your storage and player parameters. Redis, memcache, dalli, and memory storage options are available (you will need to add either the memcache-client, redis, or dalli gem to your Gemfile). Memory storage should only be used for testing.
16
16
 
17
17
  See the file players.rdoc for information about available players.
18
18
 
@@ -84,6 +84,7 @@ To run tests:
84
84
  rake test_memory
85
85
  rake test_memcache
86
86
  rake test_redis
87
+ rake test_dalli
87
88
 
88
89
  To produce fake data for the past week, first create an experiment definition. Then, run the following rake task:
89
90
 
data/Rakefile CHANGED
@@ -26,6 +26,13 @@ Rake::TestTask.new("test_memcache") { |t|
26
26
  t.verbose = true
27
27
  }
28
28
 
29
+ desc "Run all unit tests with dalli storage"
30
+ Rake::TestTask.new("test_dalli") { |t|
31
+ t.libs << "lib"
32
+ t.test_files = FileList['test/dalli_*.rb']
33
+ t.verbose = true
34
+ }
35
+
29
36
  desc "Run all unit tests with redis storage"
30
37
  Rake::TestTask.new("test_redis") { |t|
31
38
  t.libs << "lib"
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
- s.add_dependency("rails", ">= 3.0.5")
20
- s.add_dependency("redis", ">= 2.2.2")
19
+ s.add_dependency("rails", ">= 3.2.0")
20
+ s.requirements << "Either redis or memcache gem"
21
21
  s.add_development_dependency('rdoc')
22
22
  end
@@ -13,6 +13,7 @@ require "bandit/storage/base"
13
13
  require "bandit/storage/memory"
14
14
  require "bandit/storage/memcache"
15
15
  require "bandit/storage/redis"
16
+ require "bandit/storage/dalli"
16
17
 
17
18
  require "bandit/extensions/controller_concerns"
18
19
  require "bandit/extensions/array"
@@ -32,7 +33,7 @@ module Bandit
32
33
  config.check!
33
34
  # intern keys in storage config
34
35
  config.storage_config = config.storage_config.inject({}) { |n,o| n[o.first.intern] = o.last; n }
35
- end
36
+ end
36
37
 
37
38
  def self.storage
38
39
  # try using configured storage at least once every 5 minutes until resolved
@@ -53,7 +54,7 @@ module Bandit
53
54
  end
54
55
 
55
56
  def self.get_experiment(name)
56
- exp = Experiment.instances.select { |e| e.name == name }
57
+ exp = Experiment.instances.select { |e| e.name == name }
57
58
  exp.length > 0 ? exp.first : nil
58
59
  end
59
60
 
@@ -4,40 +4,35 @@ module Bandit
4
4
  module ControllerConcerns
5
5
  extend ActiveSupport::Concern
6
6
 
7
- module ClassMethods
7
+ # default convert is a session based conversion
8
+ def bandit_convert!(exp, alt=nil, count=1)
9
+ bandit_session_convert!(exp, alt, count)
8
10
  end
9
11
 
10
- module InstanceMethods
11
- # default convert is a session based conversion
12
- def bandit_convert!(exp, alt=nil, count=1)
13
- bandit_session_convert!(exp, alt, count)
14
- end
12
+ # look mum, no cookies
13
+ def bandit_simple_convert!(exp, alt, count=1)
14
+ Bandit.get_experiment(exp).convert!(alt, count)
15
+ end
15
16
 
16
- # look mum, no cookies
17
- def bandit_simple_convert!(exp, alt, count=1)
17
+ # expects a session cookie, deletes it, will convert again
18
+ def bandit_session_convert!(exp, alt=nil, count=1)
19
+ cookiename = "bandit_#{exp}".intern
20
+ cookiename_converted = "bandit_#{exp}_converted".intern
21
+ alt ||= cookies.signed[cookiename]
22
+ unless alt.nil? or cookies.signed[cookiename_converted]
18
23
  Bandit.get_experiment(exp).convert!(alt, count)
24
+ cookies.delete(cookiename)
19
25
  end
26
+ end
20
27
 
21
- # expects a session cookie, deletes it, will convert again
22
- def bandit_session_convert!(exp, alt=nil, count=1)
23
- cookiename = "bandit_#{exp}".intern
24
- cookiename_converted = "bandit_#{exp}_converted".intern
25
- alt ||= cookies.signed[cookiename]
26
- unless alt.nil? or cookies.signed[cookiename_converted]
27
- Bandit.get_experiment(exp).convert!(alt, count)
28
- cookies.delete(cookiename)
29
- end
30
- end
31
-
32
- # creates a _converted cookie, prevents multiple conversions
33
- def bandit_sticky_convert!(exp, alt=nil, count=1)
34
- cookiename = "bandit_#{exp}".intern
35
- cookiename_converted = "bandit_#{exp}_converted".intern
36
- alt ||= cookies.signed[cookiename]
37
- unless alt.nil? or cookies.signed[cookiename_converted]
38
- cookies.permanent.signed[cookiename_converted] = "true"
39
- Bandit.get_experiment(exp).convert!(alt, count)
40
- end
28
+ # creates a _converted cookie, prevents multiple conversions
29
+ def bandit_sticky_convert!(exp, alt=nil, count=1)
30
+ cookiename = "bandit_#{exp}".intern
31
+ cookiename_converted = "bandit_#{exp}_converted".intern
32
+ alt ||= cookies.signed[cookiename]
33
+ unless alt.nil? or cookies.signed[cookiename_converted]
34
+ cookies.permanent.signed[cookiename_converted] = "true"
35
+ Bandit.get_experiment(exp).convert!(alt, count)
41
36
  end
42
37
  end
43
38
  end
@@ -4,43 +4,38 @@ module Bandit
4
4
  module ViewConcerns
5
5
  extend ActiveSupport::Concern
6
6
 
7
- module ClassMethods
7
+ # default choose is a session based choice
8
+ def bandit_choose(exp)
9
+ bandit_session_choose(exp)
8
10
  end
9
11
 
10
- module InstanceMethods
11
- # default choose is a session based choice
12
- def bandit_choose(exp)
13
- bandit_session_choose(exp)
14
- end
15
-
16
- # always choose something new and increase the participant count
17
- def bandit_simple_choose(exp)
18
- Bandit.get_experiment(exp).choose(nil)
19
- end
12
+ # always choose something new and increase the participant count
13
+ def bandit_simple_choose(exp)
14
+ Bandit.get_experiment(exp).choose(nil)
15
+ end
20
16
 
21
- # stick to one alternative for the entire browser session
22
- def bandit_session_choose(exp)
23
- name = "bandit_#{exp}".intern
24
- # choose url param with preference
25
- value = params[name].nil? ? cookies.signed[name] : params[name]
26
- # choose with default, and set cookie
27
- cookies.signed[name] = Bandit.get_experiment(exp).choose(value)
28
- end
17
+ # stick to one alternative for the entire browser session
18
+ def bandit_session_choose(exp)
19
+ name = "bandit_#{exp}".intern
20
+ # choose url param with preference
21
+ value = params[name].nil? ? cookies.signed[name] : params[name]
22
+ # choose with default, and set cookie
23
+ cookies.signed[name] = Bandit.get_experiment(exp).choose(value)
24
+ end
29
25
 
30
- # stick to one alternative until user deletes cookies or changes browser
31
- def bandit_sticky_choose(exp)
32
- name = "bandit_#{exp}".intern
33
- # choose url param with preference
34
- value = params[name].nil? ? cookies.signed[name] : params[name]
35
- # sticky choice may outlast a given alternative
36
- alternative = if Bandit.get_experiment(exp).alternatives.include?(value)
37
- value
38
- else
39
- Bandit.get_experiment(exp).choose(value)
40
- end
41
- # re-set cookie
42
- cookies.permanent.signed[name] = alternative
43
- end
26
+ # stick to one alternative until user deletes cookies or changes browser
27
+ def bandit_sticky_choose(exp)
28
+ name = "bandit_#{exp}".intern
29
+ # choose url param with preference
30
+ value = params[name].nil? ? cookies.signed[name] : params[name]
31
+ # sticky choice may outlast a given alternative
32
+ alternative = if Bandit.get_experiment(exp).alternatives.include?(value)
33
+ value
34
+ else
35
+ Bandit.get_experiment(exp).choose(value)
36
+ end
37
+ # re-set cookie
38
+ cookies.permanent.signed[name] = alternative
44
39
  end
45
40
  end
46
41
  end
@@ -17,9 +17,10 @@ module Bandit
17
17
  def self.get_storage(name, config)
18
18
  config ||= {}
19
19
 
20
- case name
20
+ case name
21
21
  when :memory then MemoryStorage.new(config)
22
22
  when :memcache then MemCacheStorage.new(config)
23
+ when :dalli then DalliStorage.new(config)
23
24
  when :redis then RedisStorage.new(config)
24
25
  else raise UnknownStorageEngineError, "#{name} not a known storage method"
25
26
  end
@@ -50,17 +51,17 @@ module Bandit
50
51
  raise NotImplementedError
51
52
  end
52
53
 
53
- def incr_participants(experiment, alternative, count=1, date_hour=nil)
54
+ def incr_participants(experiment, alternative, count=1, date_hour=nil)
54
55
  date_hour ||= DateHour.now
55
56
 
56
57
  # initialize first start time for alternative if we haven't inited yet
57
58
  init alt_started_key(experiment, alternative), date_hour.to_i
58
-
59
+
59
60
  # increment total count and per hour count
60
61
  incr part_key(experiment, alternative), count
61
62
  incr part_key(experiment, alternative, date_hour), count
62
63
  end
63
-
64
+
64
65
  def incr_conversions(experiment, alternative, count=1, date_hour=nil)
65
66
  # increment total count and per hour count
66
67
  incr conv_key(experiment, alternative), count
@@ -78,7 +79,7 @@ module Bandit
78
79
  def conversion_count(experiment, alternative, date_hour=nil)
79
80
  get conv_key(experiment, alternative, date_hour)
80
81
  end
81
-
82
+
82
83
  def player_state_set(experiment, player, name, value)
83
84
  set player_state_key(experiment, player, name), value
84
85
  end
@@ -91,7 +92,7 @@ module Bandit
91
92
  secs = get alt_started_key(experiment, alternative), nil
92
93
  secs.nil? ? nil : Time.at(secs).to_date_hour
93
94
  end
94
-
95
+
95
96
  # if date_hour is nil, create key for total
96
97
  # otherwise, create key for hourly based
97
98
  def part_key(exp, alt, date_hour=nil)
@@ -0,0 +1,44 @@
1
+ module Bandit
2
+ class DalliStorage < BaseStorage
3
+ def initialize(config)
4
+ require 'dalli'
5
+ config[:namespace] ||= 'bandit'
6
+ @dalli = Dalli::Client.new(config.fetch(:host, 'localhost:11211'), config)
7
+ end
8
+
9
+ # increment key by count
10
+ def incr(key, count=1)
11
+ # dalli incr is broken just like in memcache-client gem
12
+ with_failure_grace(count) {
13
+ set(key, get(key, 0) + count)
14
+ }
15
+ end
16
+
17
+ # initialize key if not set
18
+ def init(key, value)
19
+ with_failure_grace(value) {
20
+ @dalli.add(key, value)
21
+ }
22
+ end
23
+
24
+ # get key if exists, otherwise 0
25
+ def get(key, default=0)
26
+ with_failure_grace(default) {
27
+ @dalli.get(key) || default
28
+ }
29
+ end
30
+
31
+ # set key with value, regardless of whether it is set or not
32
+ def set(key, value)
33
+ with_failure_grace(value) {
34
+ @dalli.set(key, value)
35
+ }
36
+ end
37
+
38
+ def clear!
39
+ with_failure_grace(nil) {
40
+ @dalli.flush_all
41
+ }
42
+ end
43
+ end
44
+ end
@@ -1,3 +1,3 @@
1
1
  module Bandit
2
- VERSION = "0.0.8"
2
+ VERSION = "0.0.9"
3
3
  end
@@ -0,0 +1,17 @@
1
+ require File.join File.dirname(__FILE__), 'helper'
2
+ require File.join File.dirname(__FILE__), 'storage_test_base'
3
+
4
+ class DalliStorageTest < Test::Unit::TestCase
5
+ include StorageTestBase
6
+ include SetupHelper
7
+
8
+ def setup
9
+ Bandit.setup do |config|
10
+ config.player = "round_robin"
11
+ config.storage = 'dalli'
12
+ config.storage_config = CONFIG['memcache_storage_config']
13
+ end
14
+
15
+ @storage = Bandit.storage
16
+ end
17
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bandit
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 13
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 8
10
- version: 0.0.8
9
+ - 9
10
+ version: 0.0.9
11
11
  platform: ruby
12
12
  authors:
13
13
  - Brian Muller
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-05-10 00:00:00 Z
18
+ date: 2012-05-29 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rails
@@ -25,34 +25,18 @@ dependencies:
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- hash: 13
28
+ hash: 15
29
29
  segments:
30
30
  - 3
31
+ - 2
31
32
  - 0
32
- - 5
33
- version: 3.0.5
33
+ version: 3.2.0
34
34
  type: :runtime
35
35
  version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
37
- name: redis
38
- prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
40
- none: false
41
- requirements:
42
- - - ">="
43
- - !ruby/object:Gem::Version
44
- hash: 3
45
- segments:
46
- - 2
47
- - 2
48
- - 2
49
- version: 2.2.2
50
- type: :runtime
51
- version_requirements: *id002
52
36
  - !ruby/object:Gem::Dependency
53
37
  name: rdoc
54
38
  prerelease: false
55
- requirement: &id003 !ruby/object:Gem::Requirement
39
+ requirement: &id002 !ruby/object:Gem::Requirement
56
40
  none: false
57
41
  requirements:
58
42
  - - ">="
@@ -62,7 +46,7 @@ dependencies:
62
46
  - 0
63
47
  version: "0"
64
48
  type: :development
65
- version_requirements: *id003
49
+ version_requirements: *id002
66
50
  description: Bandit provides a way to do multi-armed bandit optimization of alternatives in a rails website
67
51
  email:
68
52
  - brian.muller@livingsocial.com
@@ -95,6 +79,7 @@ files:
95
79
  - lib/bandit/players/epsilon_greedy.rb
96
80
  - lib/bandit/players/round_robin.rb
97
81
  - lib/bandit/storage/base.rb
82
+ - lib/bandit/storage/dalli.rb
98
83
  - lib/bandit/storage/memcache.rb
99
84
  - lib/bandit/storage/memory.rb
100
85
  - lib/bandit/storage/redis.rb
@@ -124,6 +109,7 @@ files:
124
109
  - lib/generators/bandit/templates/dashboard/view/show.html.erb
125
110
  - players.rdoc
126
111
  - test/config.yml
112
+ - test/dalli_storage_test.rb
127
113
  - test/helper.rb
128
114
  - test/memcache_storage_test.rb
129
115
  - test/memory_storage_test.rb
@@ -156,8 +142,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
142
  segments:
157
143
  - 0
158
144
  version: "0"
159
- requirements: []
160
-
145
+ requirements:
146
+ - Either redis or memcache gem
161
147
  rubyforge_project: bandit
162
148
  rubygems_version: 1.8.17
163
149
  signing_key:
@@ -165,6 +151,7 @@ specification_version: 3
165
151
  summary: Multi-armed bandit testing in rails
166
152
  test_files:
167
153
  - test/config.yml
154
+ - test/dalli_storage_test.rb
168
155
  - test/helper.rb
169
156
  - test/memcache_storage_test.rb
170
157
  - test/memory_storage_test.rb