kissable 1.0.0a1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5611d750b261b15bec5f0346201da93242760de9
4
+ data.tar.gz: 5ded8116d3087727da8568298be44f2ac955284c
5
+ SHA512:
6
+ metadata.gz: 5a1fd37813bde009fcfec08855c54a00220685cf59603df7fc98927fd9771635de2fc0cc13096808122f0b9bb4a3a6af06ba538b4f6ddc155b61c168e835a5d4
7
+ data.tar.gz: 1c413d7254748ec7e94ed7260973243f171a5175cf4e34e5f6515fd8291c2cc2ec84147fb5af5dac496e997de5355040a36aaf96a62ae77d85944e115b99fd43
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.hound.yml ADDED
@@ -0,0 +1,278 @@
1
+ AccessorMethodName:
2
+ Enabled: true
3
+
4
+ Alias:
5
+ Enabled: false
6
+
7
+ ArrayJoin:
8
+ Enabled: true
9
+
10
+ AsciiComments:
11
+ Enabled: false
12
+
13
+ AsciiIdentifiers:
14
+ Enabled: true
15
+
16
+ Attr:
17
+ Enabled: true
18
+
19
+ BlockNesting:
20
+ Enabled: true
21
+ Max: 3
22
+
23
+ CaseEquality:
24
+ Enabled: true
25
+
26
+ CharacterLiteral:
27
+ Enabled: true
28
+
29
+ ClassLength:
30
+ Enabled: true
31
+ Max: 150
32
+
33
+ ClassVars:
34
+ Enabled: false
35
+
36
+ CollectionMethods:
37
+ PreferredMethods:
38
+ find: detect
39
+ reduce: inject
40
+ collect: map
41
+ find_all: select
42
+
43
+ ColonMethodCall:
44
+ Enabled: true
45
+
46
+ CommentAnnotation:
47
+ Enabled: true
48
+
49
+ CyclomaticComplexity:
50
+ Enabled: true
51
+ Max: 6
52
+
53
+ # Because Ruby 1.8.6
54
+ DeprecatedHashMethods:
55
+ Enabled: false
56
+
57
+ Documentation:
58
+ Enabled: false
59
+
60
+ # TODO: Figure out which one
61
+ DotPosition:
62
+ EnforcedStyle: trailing
63
+
64
+ # LOL. This one will piss off Waffles.
65
+ DoubleNegation:
66
+ Enabled: false
67
+
68
+ EmptyLiteral:
69
+ Enabled: true
70
+
71
+ Encoding:
72
+ Enabled: false
73
+
74
+ EvenOdd:
75
+ Enabled: true
76
+
77
+ FileName:
78
+ Enabled: true
79
+
80
+ FlipFlop:
81
+ Enabled: true
82
+
83
+ # TODO: Discussion?
84
+ # Example: apps/api/routes/billing_plan.rb:73:30
85
+ FormatString:
86
+ Enabled: false
87
+
88
+ GlobalVars:
89
+ Enabled: true
90
+
91
+ HashSyntax:
92
+ EnforcedStyle: hash_rockets
93
+
94
+ # TODO: Discussion?
95
+ IfUnlessModifier:
96
+ Enabled: false
97
+
98
+ IfWithSemicolon:
99
+ Enabled: true
100
+
101
+ # This is a Ruby 1.9 thing.
102
+ Lambda:
103
+ Enabled: false
104
+
105
+ LambdaCall:
106
+ Enabled: true
107
+
108
+ LineEndConcatenation:
109
+ Enabled: true
110
+
111
+ LineLength:
112
+ Max: 80
113
+
114
+ MethodLength:
115
+ Enabled: true
116
+ CountComments: false
117
+ Max: 10
118
+
119
+ ModuleFunction:
120
+ Enabled: true
121
+
122
+ NegatedIf:
123
+ Enabled: true
124
+
125
+ NegatedWhile:
126
+ Enabled: true
127
+
128
+ NilComparison:
129
+ Enabled: true
130
+
131
+ Not:
132
+ Enabled: true
133
+
134
+ NumericLiterals:
135
+ Enabled: false
136
+
137
+ # Nate likes this.
138
+ OneLineConditional:
139
+ Enabled: false
140
+
141
+ OpMethod:
142
+ Enabled: true
143
+
144
+ ParameterLists:
145
+ Enabled: true
146
+ Max: 5
147
+ CountKeywordArgs: true
148
+
149
+ PercentLiteralDelimiters:
150
+ PreferredDelimiters:
151
+ '%': '{}'
152
+
153
+ PerlBackrefs:
154
+ Enabled: true
155
+
156
+ PredicateName:
157
+ NamePrefixBlacklist:
158
+ - is_
159
+
160
+ Proc:
161
+ Enabled: true
162
+
163
+ RaiseArgs:
164
+ Enabled: true
165
+ EnforcedStyle: exploded
166
+
167
+ RegexpLiteral:
168
+ Enabled: true
169
+
170
+ SelfAssignment:
171
+ Enabled: true
172
+
173
+ SingleLineBlockParams:
174
+ Enabled: false
175
+
176
+ SingleLineMethods:
177
+ Enabled: false
178
+
179
+ SignalException:
180
+ Enabled: true
181
+ EnforcedStyle: only_raise
182
+
183
+ SpecialGlobalVars:
184
+ Enabled: false
185
+
186
+ StringLiterals:
187
+ Enabled: false
188
+ EnforcedStyle: double_quotes
189
+
190
+ TrailingComma:
191
+ Enabled: true
192
+
193
+ TrivialAccessors:
194
+ Enabled: true
195
+
196
+ VariableInterpolation:
197
+ Enabled: true
198
+
199
+ WhenThen:
200
+ Enabled: false
201
+
202
+ WhileUntilModifier:
203
+ Enabled: false
204
+
205
+ WordArray:
206
+ Enabled: true
207
+
208
+ # THESE ARE ALL FINISHED.
209
+
210
+ # Lint
211
+
212
+ AmbiguousOperator:
213
+ Enabled: true
214
+
215
+ AmbiguousRegexpLiteral:
216
+ Enabled: true
217
+
218
+ AssignmentInCondition:
219
+ Enabled: true
220
+
221
+ ConditionPosition:
222
+ Enabled: true
223
+
224
+ DeprecatedClassMethods:
225
+ Enabled: true
226
+
227
+ ElseLayout:
228
+ Enabled: true
229
+
230
+ HandleExceptions:
231
+ Enabled: true
232
+
233
+ InvalidCharacterLiteral:
234
+ Enabled: false
235
+
236
+ LiteralInCondition:
237
+ Enabled: true
238
+
239
+ LiteralInInterpolation:
240
+ Enabled: true
241
+
242
+ Loop:
243
+ Enabled: false
244
+
245
+ ParenthesesAsGroupedExpression:
246
+ Enabled: true
247
+
248
+ RequireParentheses:
249
+ Enabled: true
250
+
251
+ UnderscorePrefixedVariableName:
252
+ Enabled: true
253
+
254
+ Void:
255
+ Enabled: false
256
+
257
+ # Rails
258
+
259
+ ActionFilter:
260
+ Enabled: false
261
+
262
+ DefaultScope:
263
+ Enabled: false
264
+
265
+ HasAndBelongsToMany:
266
+ Enabled: false
267
+
268
+ ReadWriteAttribute:
269
+ Enabled: false
270
+
271
+ ScopeArgs:
272
+ Enabled: false
273
+
274
+ Validation:
275
+ Enabled: false
276
+
277
+ Output:
278
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ cache: bundler
3
+
4
+ rvm:
5
+ - 2.0.0
6
+
7
+ script: 'bundle exec rake'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kissable.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ guard 'rspec' do
2
+ # watch /lib/ files
3
+ watch(%r{^lib/(.+).rb$}) do |m|
4
+ "spec/#{m[1]}_spec.rb"
5
+ end
6
+
7
+ # watch /spec/ files
8
+ watch(%r{^spec/(.+).rb$}) do |m|
9
+ "spec/#{m[1]}.rb"
10
+ end
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Brett Hardin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Kissable
2
+
3
+ [![Build Status](https://travis-ci.org/kissmetrics/kissable.png?branch=master)](https://travis-ci.org/kissmetrics/kissable) [![Gem Version](https://badge.fury.io/kissmetrics/kissable.png)](http://badge.fury.io/kissmetrics/kissable) [![Coverage Status](https://coveralls.io/repos/kissmetrics/kissable/badge.png?branch=master)](https://coveralls.io/r/kissmetrics/kissable?branch=master)
4
+
5
+ Kissable is a gem used to create, track, and store information for A/B tests in user cookies.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'kissable'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install kissable
20
+
21
+ ## Usage
22
+
23
+ Kissable enables you to conduct a/b tests contingent on users having cookies enabled. To conduct a test you need to have a name for a test and specify how many groups you want and the ratios of these groups.
24
+
25
+ You instantiate the object with these items.
26
+
27
+ ```ruby
28
+ Kissable::AB.new(testname, groups, ratio)
29
+ ```
30
+
31
+ * `testname` (required) This should be unique per test. It both passes the property to KM tracking as well as helps generate unique user groups for the test.
32
+ * `groups` (optional) This is used to name the groups used in the test. It will accept up to four group names. Defaults to `%w{Original Variant}`.
33
+ * `ratio` (optional) The ratio of how you want users sent to the specified groups. It expects all weightings to equal 100. It defaults to an even weight for all groups in the test.
34
+ * Store the result from `group` and use it to set up a switch in the relevant controller.
35
+
36
+ ```ruby
37
+ # home_controller.rb
38
+ def index
39
+ @ab_test = Kissable::AB.new('top-navigation test')
40
+ @users_ab_group = @ab_test.group(cookies)
41
+
42
+ case @users_ab_group
43
+ when 'Original'
44
+ render 'index'
45
+ else
46
+ render 'index-variant'
47
+ end
48
+ end
49
+ ```
50
+
51
+ * Inside of the individual template files, you can add KISSmetrics tracking code by passing the user's group into the `#tracking_script` instance method.
52
+
53
+ ```ruby
54
+ ab_test = Kissable::AB.new('top-navigation test')
55
+ users_ab_group = ab_test.group(cookies)
56
+ ab_test.tracking_script(users_ab_group)
57
+ ```
58
+
59
+ ### Rails
60
+
61
+ ```ruby
62
+ ab = Kissable::AB.new('some cool test')
63
+ ab.group(cookies)
64
+ ```
65
+
66
+ ### Sinatra
67
+
68
+ Sinatra handles cookies much differently than Rails. Sinatra stores the cookies which were sent in the request object in `request` and the cookies which will be sent back to the user in the `response` object. In order to make this as easy as possible, Kissable uses a Sinatra cookie adapter. This adapter needs to be instantiated with the `request` and `response` objects, then passed to `group`.
69
+
70
+ ```ruby
71
+ sca = Kissable::SinatraCookieAdapter.new(request, response)
72
+
73
+ ab = Kissable::AB.new('some cool test')
74
+ users_ab_group = ab.group(sca)
75
+ ```
76
+
77
+ ### Configuration
78
+
79
+ Kissable allows you some potential configurations.
80
+
81
+ ```ruby
82
+ Kissable.configure do |config|
83
+ config.logger = Logger.new(STDOUT)
84
+ config.domain = '.kissmetrics.com'
85
+ end
86
+ ```
87
+
88
+ ## Contributing
89
+
90
+ 1. Fork it
91
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
92
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
93
+ 4. Push to the branch (`git push origin my-new-feature`)
94
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rspec/core/rake_task'
2
+ require "bundler/gem_tasks"
3
+
4
+ # Default directory to look in is `/specs`
5
+ # Run with `rake spec`
6
+ RSpec::Core::RakeTask.new(:spec) do |task|
7
+ task.rspec_opts = ['--color', '-f', 'd']
8
+ end
9
+
10
+ task :default => :spec
data/kissable.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kissable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "kissable"
8
+ spec.version = Kissable::VERSION
9
+ spec.authors = ["Brett Hardin"]
10
+ spec.email = ["bhardin@kissmetrics.com"]
11
+ spec.description = "Track and identify users via cookie in order to run A/B tests."
12
+ spec.summary = "Track and identify users via cookie in order to run A/B tests."
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "rack"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "rspec-nc"
27
+ spec.add_development_dependency "guard"
28
+ spec.add_development_dependency "guard-rspec"
29
+ spec.add_development_dependency "pry"
30
+ spec.add_development_dependency "pry-remote"
31
+ spec.add_development_dependency "pry-nav"
32
+ spec.add_development_dependency "coveralls"
33
+ end
@@ -0,0 +1,112 @@
1
+ require 'digest/sha1'
2
+
3
+ module Kissable
4
+ class AB
5
+ MAX_GROUP_COUNT = 4
6
+
7
+ attr_reader :groups, :ratios, :test_name
8
+
9
+ def initialize(test_name, groups=nil, ratios=nil)
10
+ @test_name = test_name
11
+
12
+ @groups = groups
13
+ @groups ||= %w{Original Variant}
14
+
15
+ @ratios = ratios
16
+ @ratios ||= [100.0 / @groups.length] * @groups.length
17
+
18
+ validate_groups
19
+ validate_ratios
20
+ end
21
+
22
+ def group(cookies)
23
+ @cookies = cookies
24
+
25
+ abset.each do |i, val|
26
+ return i if val > seed
27
+ end
28
+ end
29
+
30
+ def cookies
31
+ @cookies ||= {}
32
+ end
33
+
34
+ def tracking_script(group)
35
+ "<script>_kmq.push(['set', {'#{test_name}' : '#{group}'}]);</script>"
36
+ end
37
+
38
+ private
39
+
40
+ def cookie_exists?
41
+ cookie && cookie != ''
42
+ end
43
+
44
+ def sha
45
+ @sha ||= Digest::SHA1.hexdigest(test_name).to_i(16)
46
+ end
47
+
48
+ def seed
49
+ @seed ||= (sha ^ ab_cookie_value) % 100
50
+ end
51
+
52
+ def abset
53
+ return @abset if @abset
54
+
55
+ sum = 0
56
+ @abset = {}
57
+ @abset = groups.zip(ratios.map { |i| sum += i })
58
+ end
59
+
60
+ # Simply reads/sets the cookie used for control
61
+ def ab_cookie_value
62
+ if cookie_exists?
63
+ abid = cookie.to_i
64
+ Kissable.configuration.logger.info("Returning User: #{abid}")
65
+ else
66
+ abid = rand(10000000)
67
+ set_cookie(abid)
68
+ end
69
+
70
+ abid
71
+ end
72
+
73
+ def cookie
74
+ cookies[cookie_name.to_s]
75
+ end
76
+
77
+ def set_cookie(cookie_value)
78
+ cookies[cookie_name] = cookie_data(cookie_value)
79
+ end
80
+
81
+ def cookie_data(cookie_value)
82
+ default_values = {
83
+ :value => cookie_value.to_s,
84
+ :path => '/',
85
+ :expires => Time.now + 52 * 604800
86
+ }
87
+
88
+ if Kissable.configuration.domain
89
+ default_values.merge!(:domain => Kissable.configuration.domain)
90
+ end
91
+
92
+ default_values
93
+ end
94
+
95
+ def cookie_name
96
+ 'abid'
97
+ end
98
+
99
+ def validate_groups
100
+ raise ArgumentError, 'A minimium of two groups are required' if groups.length < 2
101
+ raise ArgumentError, "The max number of split groups is #{MAX_GROUP_COUNT}" if groups.length > MAX_GROUP_COUNT
102
+ end
103
+
104
+ def validate_ratios
105
+ unless ratios.length == groups.length
106
+ raise ArgumentError, 'Mismatch with groups and ratios'
107
+ end
108
+ total = ratios.inject(0) { |tot, rate| tot + rate.to_i }
109
+ raise ArgumentError, "Kissable ratios sum to #{total} not 100" unless total == 100
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,12 @@
1
+ require 'logger'
2
+
3
+ module Kissable
4
+ class Configuration
5
+ attr_accessor :logger, :domain
6
+
7
+ def initialize
8
+ @logger = Logger.new(STDOUT)
9
+ @domain = nil
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module Kissable
2
+ class SinatraCookieAdapter
3
+ attr_reader :request, :response
4
+
5
+ def initialize(request, response)
6
+ @request = request
7
+ @response = response
8
+ end
9
+
10
+ def [](cookie_name)
11
+ request.cookies[cookie_name]
12
+ end
13
+
14
+ def []=(cookie_name, cookie_data)
15
+ response.set_cookie(cookie_name, cookie_data)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Kissable
2
+ VERSION = "1.0.0a1"
3
+ end
data/lib/kissable.rb ADDED
@@ -0,0 +1,18 @@
1
+ require "kissable/version"
2
+ require "kissable/configuration"
3
+ require "kissable/ab"
4
+ require "kissable/sinatra_cookie_adapter"
5
+
6
+ module Kissable
7
+ class << self
8
+ attr_accessor :configuration
9
+ end
10
+
11
+ def self.configuration
12
+ @configuration ||= Configuration.new
13
+ end
14
+
15
+ def self.configure
16
+ yield(configuration)
17
+ end
18
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kissable::AB do
4
+ let(:test_name) { "sample_test" }
5
+ let(:groups) { nil }
6
+ let(:ratios) { nil }
7
+ let(:cookies) { {} }
8
+ let(:ab_test) { described_class.new(test_name, groups, ratios) }
9
+
10
+ describe '#initialize' do
11
+ context "when initialized" do
12
+ context "with no groups" do
13
+ it "assigns an Original group" do
14
+ expect(ab_test.groups).to include("Original")
15
+ end
16
+
17
+ it "assigns a Variant group" do
18
+ expect(ab_test.groups).to include("Variant")
19
+ end
20
+ end
21
+
22
+ context "with one group" do
23
+ let(:groups) { ["one"] }
24
+
25
+ it "raises an error" do
26
+ expect{ ab_test }.to raise_error(ArgumentError, 'A minimium of two groups are required')
27
+ end
28
+ end
29
+
30
+ context "with five groups" do
31
+ let(:groups) { ["one", "two", "three", "four", "five"] }
32
+
33
+ it "raises an error" do
34
+ expect{ ab_test }.to raise_error(ArgumentError, "The max number of split groups is 4")
35
+ end
36
+ end
37
+
38
+ context "when ratio is nil" do
39
+ it "sets the ratios evenly" do
40
+ expect(ab_test.ratios).to eq([50.0, 50.0])
41
+ end
42
+ end
43
+
44
+ context "with a mismatch of ratios to groups" do
45
+ let(:ratios) { [100] }
46
+ let(:groups) { ["one", "two"] }
47
+
48
+ it "raises an error" do
49
+ expect{ab_test}.to raise_error(ArgumentError, "Mismatch with groups and ratios")
50
+ end
51
+ end
52
+
53
+ context "when ratios don't add up to 100" do
54
+ let(:ratios) { [90, 5] }
55
+ let(:groups) { %w("one", "two") }
56
+
57
+ it "raises an error" do
58
+ message = "Kissable ratios sum to 95 not 100"
59
+ expect { ab_test }.to raise_error(ArgumentError, message)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '#group' do
66
+ let(:group) { ab_test.group(cookies) }
67
+
68
+ it "is returns Original or Variant" do
69
+ expect(group).to match(/Original|Variant/)
70
+ end
71
+
72
+ context "when cookie exists" do
73
+ before :each do
74
+ ab_test.stub(:cookies).and_return('abid' => 1)
75
+ end
76
+
77
+ it "doesn't change the cookie" do
78
+ expect { group }.to_not change { ab_test.cookies }
79
+ end
80
+ end
81
+
82
+ context "when cookie doesn't exist" do
83
+ it "sets a cookie" do
84
+ expect { group }.to change { ab_test.cookies }.from({})
85
+ end
86
+
87
+ describe "the cookie" do
88
+ let(:cookie) { ab_test.cookies['abid'] }
89
+
90
+ context "when the domain has been configured" do
91
+ let(:domain) { 'someaweseomedomain.com' }
92
+
93
+ it "has the domain key set to the correct value" do
94
+ Kissable.configure do |config|
95
+ config.domain = domain
96
+ end
97
+
98
+ group
99
+ expect(cookie).to include(:domain => domain)
100
+ end
101
+ end
102
+
103
+ context "when the domain hasn't been configured" do
104
+ it "doesn't include a domain key" do
105
+ Kissable.configure do |config|
106
+ config.domain = nil
107
+ end
108
+
109
+ group
110
+ expect(cookie).to_not include(:domain)
111
+ end
112
+ end
113
+
114
+ it "expires" do
115
+ group
116
+ expect(cookie).to include(:expires)
117
+ end
118
+
119
+ it "has a path" do
120
+ group
121
+ expect(cookie).to include(:path => "/")
122
+ end
123
+
124
+ it "contains a value" do
125
+ group
126
+ expect(cookie).to include(:value)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ describe "#tracking_script" do
133
+ let(:group) { 'Original' }
134
+ let(:tracking_script) { ab_test.tracking_script(group) }
135
+
136
+ it "returns an embeddable script" do
137
+ expect(tracking_script).to match(/^<script>.*<\/script>$/)
138
+ end
139
+
140
+ it "contains the _kmq push event" do
141
+ expect(tracking_script).to include("_kmq.push")
142
+ end
143
+
144
+ it "sets the property to the id passed" do
145
+ set_js_cookie_data = "['set', {'#{test_name}' : 'Original'}]"
146
+ expect(tracking_script).to include(set_js_cookie_data)
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kissable::Configuration do
4
+ subject(:configuration) { described_class.new }
5
+
6
+ it { should respond_to(:logger) }
7
+ it { should respond_to(:domain) }
8
+
9
+ describe "#logger" do
10
+ it "defaults to Logger" do
11
+ expect(configuration.logger.class).to eq(Logger)
12
+ end
13
+ end
14
+
15
+ describe "#domain" do
16
+ it "defaults to nil" do
17
+ expect(configuration.domain).to eq(nil)
18
+ end
19
+ end
20
+
21
+ context "when Logger is written to" do
22
+ it "doesn't have an error" do
23
+ expect(Kissable.configuration.logger.info("test")).to_not raise_error
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kissable::SinatraCookieAdapter do
4
+ let(:response) { double('response') }
5
+ let(:request) { double('request') }
6
+ let(:name) { 'name' }
7
+ let(:value) { 'value' }
8
+ subject(:cookie_adapter) { Kissable::SinatraCookieAdapter.new(request, response) }
9
+
10
+ describe "#initialize" do
11
+ it { should respond_to(:request) }
12
+ it { should respond_to(:response) }
13
+ end
14
+
15
+ describe "#[]" do
16
+ it "calls request.cookies with cookie_name" do
17
+ request.should_receive(:cookies).and_return({})
18
+ cookie_adapter[name]
19
+ end
20
+
21
+ it "returns the stored value" do
22
+ request.stub(:cookies).and_return({name => value})
23
+ expect(cookie_adapter[name]).to eq(value)
24
+ end
25
+ end
26
+
27
+ describe "#[]=" do
28
+ it "calls set_cookie on response" do
29
+ response.should_receive(:set_cookie).with(name, value)
30
+ cookie_adapter[name] = value
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kissable do
4
+ let(:test_name) { 'some-test' }
5
+
6
+ describe ".configure" do
7
+ let(:data) { '123' }
8
+
9
+ it "sets the configuration" do
10
+ Kissable.configure do |config|
11
+ config.domain = data
12
+ end
13
+
14
+ expect(Kissable.configuration.domain).to eq(data)
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,5 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'pry'
5
+ require 'kissable'
metadata ADDED
@@ -0,0 +1,223 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kissable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0a1
5
+ platform: ruby
6
+ authors:
7
+ - Brett Hardin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-nc
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: guard-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry-remote
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry-nav
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: coveralls
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: Track and identify users via cookie in order to run A/B tests.
168
+ email:
169
+ - bhardin@kissmetrics.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - .coveralls.yml
175
+ - .gitignore
176
+ - .hound.yml
177
+ - .travis.yml
178
+ - Gemfile
179
+ - Guardfile
180
+ - LICENSE.txt
181
+ - README.md
182
+ - Rakefile
183
+ - kissable.gemspec
184
+ - lib/kissable.rb
185
+ - lib/kissable/ab.rb
186
+ - lib/kissable/configuration.rb
187
+ - lib/kissable/sinatra_cookie_adapter.rb
188
+ - lib/kissable/version.rb
189
+ - spec/kissable/ab_spec.rb
190
+ - spec/kissable/configuration_spec.rb
191
+ - spec/kissable/sinatra_cookie_adapter_spec.rb
192
+ - spec/kissable_spec.rb
193
+ - spec/spec_helper.rb
194
+ homepage: ''
195
+ licenses:
196
+ - MIT
197
+ metadata: {}
198
+ post_install_message:
199
+ rdoc_options: []
200
+ require_paths:
201
+ - lib
202
+ required_ruby_version: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - '>='
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
207
+ required_rubygems_version: !ruby/object:Gem::Requirement
208
+ requirements:
209
+ - - '>'
210
+ - !ruby/object:Gem::Version
211
+ version: 1.3.1
212
+ requirements: []
213
+ rubyforge_project:
214
+ rubygems_version: 2.0.14
215
+ signing_key:
216
+ specification_version: 4
217
+ summary: Track and identify users via cookie in order to run A/B tests.
218
+ test_files:
219
+ - spec/kissable/ab_spec.rb
220
+ - spec/kissable/configuration_spec.rb
221
+ - spec/kissable/sinatra_cookie_adapter_spec.rb
222
+ - spec/kissable_spec.rb
223
+ - spec/spec_helper.rb