blackbeard 0.0.2.0 → 0.0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +9 -0
- data/Guardfile +8 -0
- data/README.md +162 -20
- data/Rakefile +6 -0
- data/TODO.md +13 -34
- data/blackbeard.gemspec +5 -1
- data/console.rb +3 -0
- data/dashboard/public/bootstrap3-editable/css/bootstrap-editable.css +663 -0
- data/dashboard/public/bootstrap3-editable/img/clear.png +0 -0
- data/dashboard/public/bootstrap3-editable/img/loading.gif +0 -0
- data/dashboard/public/bootstrap3-editable/js/bootstrap-editable.min.js +7 -0
- data/dashboard/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/dashboard/public/fonts/glyphicons-halflings-regular.svg +229 -0
- data/dashboard/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/dashboard/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/dashboard/public/javascripts/bootstrap.min.js +7 -0
- data/dashboard/public/javascripts/jquery-1.10.2.min.js +6 -0
- data/dashboard/public/stylesheets/application.css +28 -0
- data/dashboard/public/stylesheets/bootstrap-theme.css +7 -0
- data/dashboard/public/stylesheets/bootstrap.css +7 -0
- data/dashboard/routes/base.rb +19 -0
- data/dashboard/routes/groups.rb +22 -0
- data/dashboard/routes/home.rb +11 -0
- data/dashboard/routes/metrics.rb +30 -0
- data/dashboard/routes/tests.rb +23 -0
- data/dashboard/views/groups/index.erb +22 -0
- data/dashboard/views/groups/show.erb +58 -0
- data/dashboard/views/index.erb +4 -0
- data/dashboard/views/layout.erb +48 -0
- data/dashboard/views/metrics/_metric_data.erb +59 -0
- data/dashboard/views/metrics/index.erb +23 -0
- data/dashboard/views/metrics/show.erb +73 -0
- data/dashboard/views/tests/index.erb +21 -0
- data/dashboard/views/tests/show.erb +58 -0
- data/lib/blackbeard/configuration.rb +8 -1
- data/lib/blackbeard/configuration_methods.rb +24 -0
- data/lib/blackbeard/context.rb +33 -21
- data/lib/blackbeard/dashboard.rb +17 -21
- data/lib/blackbeard/dashboard_helpers.rb +29 -0
- data/lib/blackbeard/errors.rb +2 -2
- data/lib/blackbeard/group.rb +35 -0
- data/lib/blackbeard/metric.rb +34 -32
- data/lib/blackbeard/metric_data/base.rb +101 -0
- data/lib/blackbeard/metric_data/total.rb +39 -0
- data/lib/blackbeard/metric_data/unique.rb +58 -0
- data/lib/blackbeard/metric_date.rb +11 -0
- data/lib/blackbeard/metric_hour.rb +17 -0
- data/lib/blackbeard/pirate.rb +33 -22
- data/lib/blackbeard/redis_store.rb +46 -2
- data/lib/blackbeard/selected_variation.rb +13 -0
- data/lib/blackbeard/storable.rb +39 -27
- data/lib/blackbeard/storable_attributes.rb +54 -0
- data/lib/blackbeard/storable_has_many.rb +60 -0
- data/lib/blackbeard/storable_has_set.rb +59 -0
- data/lib/blackbeard/test.rb +21 -0
- data/lib/blackbeard/version.rb +1 -1
- data/lib/blackbeard.rb +0 -8
- data/spec/configuration_spec.rb +15 -0
- data/spec/context_spec.rb +94 -19
- data/spec/dashboard/groups_spec.rb +50 -0
- data/spec/dashboard/home_spec.rb +20 -0
- data/spec/dashboard/metrics_spec.rb +57 -0
- data/spec/dashboard/tests_spec.rb +43 -0
- data/spec/group_spec.rb +36 -0
- data/spec/metric_data/base_spec.rb +57 -0
- data/spec/metric_data/total_spec.rb +116 -0
- data/spec/metric_data/unique_spec.rb +91 -0
- data/spec/metric_spec.rb +52 -44
- data/spec/pirate_spec.rb +32 -15
- data/spec/redis_store_spec.rb +121 -0
- data/spec/spec_helper.rb +13 -1
- data/spec/storable_attributes_spec.rb +47 -0
- data/spec/storable_has_many_spec.rb +49 -0
- data/spec/storable_has_set_spec.rb +39 -0
- data/spec/storable_spec.rb +33 -0
- data/spec/test_spec.rb +25 -0
- metadata +133 -17
- data/lib/blackbeard/dashboard/helpers.rb +0 -8
- data/lib/blackbeard/dashboard/views/layout.erb +0 -15
- data/lib/blackbeard/dashboard/views/metrics/index.erb +0 -10
- data/lib/blackbeard/dashboard/views/metrics/show.erb +0 -16
- data/lib/blackbeard/feature.rb +0 -13
- data/lib/blackbeard/metric/total.rb +0 -17
- data/lib/blackbeard/metric/unique.rb +0 -18
- data/spec/dashboard_spec.rb +0 -38
- data/spec/total_metric_spec.rb +0 -65
- data/spec/unique_metric_spec.rb +0 -60
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'blackbeard/metric_data/base'
|
2
|
+
|
3
|
+
module Blackbeard
|
4
|
+
module MetricData
|
5
|
+
class Unique < Base
|
6
|
+
|
7
|
+
DEFAULT_SEGMENT = 'uniques'
|
8
|
+
def add(uid, amount = nil, segment = DEFAULT_SEGMENT)
|
9
|
+
add_at(tz.now, uid, amount, segment)
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_at(time, uid, amount = nil, segment = DEFAULT_SEGMENT)
|
13
|
+
key = key_for_hour(time)
|
14
|
+
segment_key = segment_key(key, segment)
|
15
|
+
|
16
|
+
db.set_add_member(hours_set_key, key)
|
17
|
+
db.set_add_member(key, segment_key)
|
18
|
+
db.set_add_member(segment_key, uid)
|
19
|
+
#TODO: if not today, blow away rollup keys
|
20
|
+
end
|
21
|
+
|
22
|
+
def result_for_hour(time)
|
23
|
+
key = key_for_hour(time)
|
24
|
+
segment_keys = db.set_members(key)
|
25
|
+
result = {}
|
26
|
+
segment_keys.each do |segment_key|
|
27
|
+
segment = segment_key.split(/::/).last
|
28
|
+
result[segment] = db.set_count(segment_key)
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def segment_key(key, segment)
|
34
|
+
"#{key}::#{segment}"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def merge_results(keys)
|
40
|
+
segments = {}
|
41
|
+
keys.each do |key|
|
42
|
+
segment_keys = db.set_members(key)
|
43
|
+
segment_keys.each do |segment_key|
|
44
|
+
segment = segment_key.split(/::/).last
|
45
|
+
segments[segment] ||= []
|
46
|
+
segments[segment].push(segment_key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
merged_results = {}
|
50
|
+
segments.each do |segment, segment_keys|
|
51
|
+
merged_results[segment] = db.set_union_count(segment_keys)
|
52
|
+
end
|
53
|
+
merged_results
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Blackbeard
|
2
|
+
class MetricHour
|
3
|
+
attr_reader :hour, :result
|
4
|
+
|
5
|
+
def initialize(time, result)
|
6
|
+
@hour = round_to_beginning_of_hour(time)
|
7
|
+
@result = result
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def round_to_beginning_of_hour(t)
|
13
|
+
t - ((t.min * 60) + t.sec)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/lib/blackbeard/pirate.rb
CHANGED
@@ -1,47 +1,58 @@
|
|
1
1
|
require "blackbeard/context"
|
2
2
|
require "blackbeard/metric"
|
3
|
-
require "blackbeard/
|
4
|
-
require "blackbeard/
|
5
|
-
require "blackbeard/
|
3
|
+
require "blackbeard/metric_data/unique"
|
4
|
+
require "blackbeard/metric_data/total"
|
5
|
+
require "blackbeard/test"
|
6
6
|
require "blackbeard/errors"
|
7
|
+
require "blackbeard/group"
|
7
8
|
|
8
9
|
module Blackbeard
|
9
10
|
class Pirate
|
10
11
|
def initialize
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@features = {}
|
12
|
+
@metrics = {}
|
13
|
+
@tests = {}
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
@
|
16
|
+
def metric(type, type_id)
|
17
|
+
@metrics["#{type}::#{type_id}"] ||= Metric.new(type, type_id)
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
@
|
20
|
+
def test(id)
|
21
|
+
@tests[id] ||= Test.new(id)
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
24
|
+
def context(*args)
|
25
|
+
Context.new(self, *args)
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
29
|
-
|
28
|
+
def set_context(*args)
|
29
|
+
@set_context = context(*args)
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
@set_context =
|
32
|
+
def clear_context
|
33
|
+
@set_context = nil
|
34
34
|
end
|
35
35
|
|
36
|
-
def add_unique(
|
37
|
-
|
38
|
-
@set_context.add_unique(
|
36
|
+
def add_unique(id)
|
37
|
+
return self unless @set_context
|
38
|
+
@set_context.add_unique(id)
|
39
39
|
end
|
40
40
|
|
41
|
-
def add_total(
|
42
|
-
|
43
|
-
@set_context.add_total(
|
41
|
+
def add_total(id, amount)
|
42
|
+
return self unless @set_context
|
43
|
+
@set_context.add_total(id, amount)
|
44
44
|
end
|
45
45
|
|
46
|
+
def ab_test(id, options)
|
47
|
+
return self unless @set_context
|
48
|
+
@set_context.ab_test(id, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def active?(id)
|
52
|
+
return self unless @set_context
|
53
|
+
@set_context.active?(id)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
46
57
|
end
|
47
58
|
end
|
@@ -14,10 +14,20 @@ module Blackbeard
|
|
14
14
|
redis.keys
|
15
15
|
end
|
16
16
|
|
17
|
+
|
18
|
+
# Hash commands
|
17
19
|
def hash_key_set_if_not_exists(hash_key, field, value)
|
18
20
|
redis.hsetnx(hash_key, field, value)
|
19
21
|
end
|
20
22
|
|
23
|
+
def hash_set(hash_key, field, value)
|
24
|
+
redis.hset(hash_key, field, value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def hash_multi_set(hash_key, hash)
|
28
|
+
redis.mapped_hmset(hash_key, hash) unless hash.empty?
|
29
|
+
end
|
30
|
+
|
21
31
|
def hash_length(hash_key)
|
22
32
|
redis.hlen(hash_key)
|
23
33
|
end
|
@@ -26,24 +36,50 @@ module Blackbeard
|
|
26
36
|
redis.hkeys(hash_key)
|
27
37
|
end
|
28
38
|
|
39
|
+
def hash_get(hash_key, field)
|
40
|
+
redis.hget(hash_key, field)
|
41
|
+
end
|
42
|
+
|
43
|
+
def hash_get_all(hash_key)
|
44
|
+
redis.hgetall(hash_key)
|
45
|
+
end
|
46
|
+
|
47
|
+
def hash_increment_by_float(hash_key, field, float)
|
48
|
+
redis.hincrbyfloat(hash_key, field, float)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Set commands
|
29
52
|
def set_members(set_key)
|
30
53
|
redis.smembers(set_key)
|
31
54
|
end
|
32
55
|
|
56
|
+
def set_remove_member(set_key, member)
|
57
|
+
redis.srem(set_key, member)
|
58
|
+
end
|
59
|
+
|
33
60
|
def set_add_member(set_key, member)
|
34
61
|
redis.sadd(set_key, member)
|
35
62
|
end
|
36
63
|
|
64
|
+
def set_add_members(set_key, *members)
|
65
|
+
redis.sadd(set_key, members.flatten)
|
66
|
+
end
|
67
|
+
|
37
68
|
def set_count(set_key)
|
38
69
|
redis.scard(set_key)
|
39
70
|
end
|
40
71
|
|
72
|
+
def set_union_count(*keys)
|
73
|
+
redis.sunionstore('set_union_count', keys.flatten)
|
74
|
+
end
|
75
|
+
|
76
|
+
# String commands
|
41
77
|
def del(*keys)
|
42
78
|
redis.del(*keys)
|
43
79
|
end
|
44
80
|
|
45
|
-
def increment_by_float(key,
|
46
|
-
redis.incrbyfloat(key,
|
81
|
+
def increment_by_float(key, float)
|
82
|
+
redis.incrbyfloat(key, float)
|
47
83
|
end
|
48
84
|
|
49
85
|
def increment(key)
|
@@ -54,5 +90,13 @@ module Blackbeard
|
|
54
90
|
redis.get(key)
|
55
91
|
end
|
56
92
|
|
93
|
+
def multi_get(*keys)
|
94
|
+
redis.mget(*keys.flatten)
|
95
|
+
end
|
96
|
+
|
97
|
+
def set(key, value)
|
98
|
+
redis.set(key, value)
|
99
|
+
end
|
100
|
+
|
57
101
|
end
|
58
102
|
end
|
data/lib/blackbeard/storable.rb
CHANGED
@@ -1,19 +1,32 @@
|
|
1
|
+
require 'blackbeard/configuration_methods'
|
2
|
+
require 'blackbeard/storable_attributes'
|
3
|
+
require 'blackbeard/storable_has_many'
|
4
|
+
require 'blackbeard/storable_has_set'
|
5
|
+
|
1
6
|
module Blackbeard
|
2
7
|
class Storable
|
3
|
-
|
8
|
+
include ConfigurationMethods
|
9
|
+
include StorableHasMany
|
10
|
+
include StorableHasSet
|
11
|
+
include StorableAttributes
|
4
12
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
13
|
+
class << self
|
14
|
+
def set_master_key(master_key)
|
15
|
+
@master_key = master_key.to_s
|
16
|
+
end
|
9
17
|
|
10
|
-
|
11
|
-
|
18
|
+
def master_key
|
19
|
+
return @master_key if defined? @master_key
|
20
|
+
return self.superclass.master_key if self.superclass.respond_to?(:master_key)
|
21
|
+
raise StorableMasterKeyUndefined, "define master key in the class that inherits from storable"
|
22
|
+
end
|
12
23
|
end
|
13
24
|
|
14
|
-
|
15
|
-
|
16
|
-
|
25
|
+
attr_reader :id
|
26
|
+
|
27
|
+
def initialize(id)
|
28
|
+
@id = id.to_s.downcase
|
29
|
+
db.hash_key_set_if_not_exists(master_key, key, tz.now.to_date)
|
17
30
|
end
|
18
31
|
|
19
32
|
def self.all_keys
|
@@ -24,34 +37,33 @@ module Blackbeard
|
|
24
37
|
db.hash_length(master_key)
|
25
38
|
end
|
26
39
|
|
27
|
-
def self.
|
28
|
-
|
40
|
+
def self.new_from_keys(*keys)
|
41
|
+
keys.flatten.map{ |key| new_from_key(key) }
|
29
42
|
end
|
30
43
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
44
|
+
def self.new_from_key(key)
|
45
|
+
if key =~ /^#{master_key}::(.+)$/
|
46
|
+
new($1)
|
47
|
+
else
|
48
|
+
nil
|
49
|
+
end
|
35
50
|
end
|
36
51
|
|
37
|
-
def
|
38
|
-
|
52
|
+
def self.all
|
53
|
+
all_keys.map{ |key| new_from_key(key) }
|
39
54
|
end
|
40
55
|
|
41
|
-
def
|
42
|
-
self.class.
|
56
|
+
def ==(o)
|
57
|
+
o.class == self.class && o.id == self.id
|
43
58
|
end
|
44
59
|
|
45
|
-
def
|
46
|
-
|
60
|
+
def key
|
61
|
+
"#{master_key}::#{ id }"
|
47
62
|
end
|
48
63
|
|
49
|
-
def
|
50
|
-
self.class.
|
64
|
+
def master_key
|
65
|
+
self.class.master_key
|
51
66
|
end
|
52
67
|
|
53
|
-
def self.tz
|
54
|
-
Blackbeard.tz
|
55
|
-
end
|
56
68
|
end
|
57
69
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Blackbeard
|
2
|
+
module StorableAttributes
|
3
|
+
def self.included(base)
|
4
|
+
base.send :include, InstanceMethods
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def storable_attributes=(x)
|
10
|
+
@storable_attributes = x
|
11
|
+
end
|
12
|
+
|
13
|
+
def storable_attributes
|
14
|
+
return @storable_attributes if defined? @storable_attributes
|
15
|
+
return self.superclass.storable_attributes if self.superclass.respond_to?(:storable_attributes)
|
16
|
+
[]
|
17
|
+
end
|
18
|
+
|
19
|
+
def string_attributes(*attributes)
|
20
|
+
self.storable_attributes += attributes
|
21
|
+
attributes.each do |method_name|
|
22
|
+
method_name = method_name.to_sym
|
23
|
+
send :define_method, method_name do
|
24
|
+
storable_attributes_hash[method_name.to_s]
|
25
|
+
end
|
26
|
+
send :define_method, "#{method_name}=".to_sym do |value|
|
27
|
+
db.hash_set(attributes_hash_key, method_name, value)
|
28
|
+
storable_attributes_hash[method_name.to_s] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module InstanceMethods
|
35
|
+
def update_attributes(tainted_params)
|
36
|
+
attributes = self.class.storable_attributes
|
37
|
+
safe_attributes = tainted_params.keys.select{ |k| attributes.include?(k.to_sym) }
|
38
|
+
safe_attributes.each do |attribute|
|
39
|
+
self.send("#{attribute}=".to_sym, tainted_params[attribute])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def storable_attributes_hash
|
44
|
+
@storable_attributes ||= db.hash_get_all(attributes_hash_key)
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes_hash_key
|
48
|
+
"#{key}::attributes"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Blackbeard
|
2
|
+
module StorableHasMany
|
3
|
+
def self.included(base)
|
4
|
+
base.send :include, InstanceMethods
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def has_many(options = {})
|
14
|
+
options.each_pair do |plural, klass|
|
15
|
+
_has_many(plural, klass)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def _has_many(plural, klass)
|
20
|
+
plural = plural.to_s.downcase
|
21
|
+
singular = klass.name.split('::').last.downcase
|
22
|
+
|
23
|
+
methods = <<-END_OF_RUBY
|
24
|
+
|
25
|
+
def has_#{singular}?(o)
|
26
|
+
#{singular}_ids.include?(o.id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_#{singular}(o)
|
30
|
+
db.set_add_member(#{plural}_set_key, o.key) unless has_#{singular}?(o)
|
31
|
+
\@#{plural} = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_#{singular}(o)
|
35
|
+
db.set_remove_member(#{plural}_set_key, o.key)
|
36
|
+
\@#{plural} = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def #{plural}
|
40
|
+
\@#{plural} ||= #{klass.name}.new_from_keys(#{singular}_keys)
|
41
|
+
end
|
42
|
+
|
43
|
+
def #{singular}_ids
|
44
|
+
#{plural}.map{ |g| g.id }
|
45
|
+
end
|
46
|
+
|
47
|
+
def #{singular}_keys
|
48
|
+
db.set_members(#{plural}_set_key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def #{plural}_set_key
|
52
|
+
key+"::#{plural}"
|
53
|
+
end
|
54
|
+
END_OF_RUBY
|
55
|
+
|
56
|
+
class_eval(methods)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Blackbeard
|
2
|
+
module StorableHasSet
|
3
|
+
def self.included(base)
|
4
|
+
base.send :include, InstanceMethods
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def has_set(options = {})
|
14
|
+
options.each_pair do |plural, singular|
|
15
|
+
_has_set(plural, singular)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def _has_set(plural, singular)
|
20
|
+
plural = plural.to_s.downcase
|
21
|
+
singular = singular.to_s.downcase
|
22
|
+
|
23
|
+
methods = <<-END_OF_RUBY
|
24
|
+
|
25
|
+
def has_#{singular}?(o)
|
26
|
+
#{plural}.include?(o)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_#{singular}(o)
|
30
|
+
db.set_add_member(#{plural}_key, o) unless has_#{singular}?(o)
|
31
|
+
\@#{plural} = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_#{plural}(*args)
|
35
|
+
args.flatten.reject!{|o| has_#{singular}?(o) }
|
36
|
+
db.set_add_members(#{plural}_key, args) unless args.empty?
|
37
|
+
\@#{plural} = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def remove_#{singular}(o)
|
41
|
+
db.set_remove_member(#{plural}_key, o)
|
42
|
+
\@#{plural} = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def #{plural}
|
46
|
+
\@#{plural} ||= db.set_members(#{plural}_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def #{plural}_key
|
50
|
+
key+"::#{plural}"
|
51
|
+
end
|
52
|
+
|
53
|
+
END_OF_RUBY
|
54
|
+
|
55
|
+
class_eval(methods)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'blackbeard/storable'
|
2
|
+
|
3
|
+
module Blackbeard
|
4
|
+
class Test < Storable
|
5
|
+
set_master_key :tests
|
6
|
+
string_attributes :name, :description
|
7
|
+
has_set :variations => :variation
|
8
|
+
|
9
|
+
def select_variation
|
10
|
+
# add :off unless :off, :control, :default, or :inactive
|
11
|
+
:off
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
storable_attributes_hash['name'] || id
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/blackbeard/version.rb
CHANGED
data/lib/blackbeard.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
module Blackbeard
|
4
|
+
describe Configuration do
|
5
|
+
let(:config) { Configuration.new }
|
6
|
+
describe "define_group" do
|
7
|
+
it "should store the block in group_definiations" do
|
8
|
+
config.define_group(:hello) do |user,controller|
|
9
|
+
"world"
|
10
|
+
end
|
11
|
+
config.group_definitions[:hello].call.should == 'world'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|