ldclient-rb 2.4.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/ldclient-rb.gemspec +2 -1
- data/lib/ldclient-rb/evaluation.rb +66 -27
- data/lib/ldclient-rb/version.rb +1 -1
- data/spec/evaluation_spec.rb +316 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9261ef0e3f59657592492b3fe391e2096bc6eef2
|
4
|
+
data.tar.gz: 418f0fbd33e9ebc9afd7fff88fe79c08da5ffbed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42f9a2a262c821cc0624bff69226d3ed0ffda35387c5504740b55919f732eaf874c7e2b4db698fe12697131a23a9482788379e21429a099eb94f00912bbaafd7
|
7
|
+
data.tar.gz: 0cb478da375473bb01ca3d733c8b85212a45c60210ad4f36ef49c8817eaadff21669e374ed990a1407ce4b6c51f9deec8fe7d9a5458079872979009359395de5
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,15 @@
|
|
2
2
|
|
3
3
|
All notable changes to the LaunchDarkly Ruby SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
|
4
4
|
|
5
|
+
2.5.0 (2018-02-12)
|
6
|
+
|
7
|
+
## Added
|
8
|
+
- Adds support for a future LaunchDarkly feature, coming soon: semantic version user attributes.
|
9
|
+
|
10
|
+
## Changed
|
11
|
+
- It is now possible to compute rollouts based on an integer attribute of a user, not just a string attribute.
|
12
|
+
|
13
|
+
|
5
14
|
## [2.4.1] - 2018-01-23
|
6
15
|
## Changed
|
7
16
|
- Reduce logging level for missing flags
|
data/ldclient-rb.gemspec
CHANGED
@@ -28,10 +28,11 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_development_dependency "redis", "~> 3.3.5"
|
29
29
|
spec.add_development_dependency "connection_pool", ">= 2.1.2"
|
30
30
|
spec.add_development_dependency "moneta", "~> 1.0.0"
|
31
|
-
|
31
|
+
|
32
32
|
spec.add_runtime_dependency "json", [">= 1.8", "< 3"]
|
33
33
|
spec.add_runtime_dependency "faraday", [">= 0.9", "< 2"]
|
34
34
|
spec.add_runtime_dependency "faraday-http-cache", [">= 1.3.0", "< 3"]
|
35
|
+
spec.add_runtime_dependency "semantic", "~> 1.6.0"
|
35
36
|
spec.add_runtime_dependency "thread_safe", "~> 0.3"
|
36
37
|
spec.add_runtime_dependency "net-http-persistent", "~> 2.9"
|
37
38
|
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0.4"
|
@@ -1,9 +1,57 @@
|
|
1
1
|
require "date"
|
2
|
+
require "semantic"
|
2
3
|
|
3
4
|
module LaunchDarkly
|
4
5
|
module Evaluation
|
5
6
|
BUILTINS = [:key, :ip, :country, :email, :firstName, :lastName, :avatar, :name, :anonymous]
|
6
7
|
|
8
|
+
NUMERIC_VERSION_COMPONENTS_REGEX = Regexp.new("^[0-9.]*")
|
9
|
+
|
10
|
+
DATE_OPERAND = lambda do |v|
|
11
|
+
if v.is_a? String
|
12
|
+
begin
|
13
|
+
DateTime.rfc3339(v).strftime("%Q").to_i
|
14
|
+
rescue => e
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
elsif v.is_a? Numeric
|
18
|
+
v
|
19
|
+
else
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
SEMVER_OPERAND = lambda do |v|
|
25
|
+
if v.is_a? String
|
26
|
+
for _ in 0..2 do
|
27
|
+
begin
|
28
|
+
return Semantic::Version.new(v)
|
29
|
+
rescue ArgumentError
|
30
|
+
v = addZeroVersionComponent(v)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.addZeroVersionComponent(v)
|
38
|
+
NUMERIC_VERSION_COMPONENTS_REGEX.match(v) { |m|
|
39
|
+
m[0] + ".0" + v[m[0].length..-1]
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.comparator(converter)
|
44
|
+
lambda do |a, b|
|
45
|
+
av = converter.call(a)
|
46
|
+
bv = converter.call(b)
|
47
|
+
if !av.nil? && !bv.nil?
|
48
|
+
yield av <=> bv
|
49
|
+
else
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
7
55
|
OPERATORS = {
|
8
56
|
in:
|
9
57
|
lambda do |a, b|
|
@@ -42,33 +90,15 @@ module LaunchDarkly
|
|
42
90
|
(a.is_a? Numeric) && (a >= b)
|
43
91
|
end,
|
44
92
|
before:
|
45
|
-
|
46
|
-
begin
|
47
|
-
if a.is_a? String
|
48
|
-
a = DateTime.rfc3339(a).strftime('%Q').to_i
|
49
|
-
end
|
50
|
-
if b.is_a? String
|
51
|
-
b = DateTime.rfc3339(b).strftime('%Q').to_i
|
52
|
-
end
|
53
|
-
(a.is_a? Numeric) ? a < b : false
|
54
|
-
rescue => e
|
55
|
-
false
|
56
|
-
end
|
57
|
-
end,
|
93
|
+
comparator(DATE_OPERAND) { |n| n < 0 },
|
58
94
|
after:
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
(a.is_a? Numeric) ? a > b : false
|
68
|
-
rescue => e
|
69
|
-
false
|
70
|
-
end
|
71
|
-
end
|
95
|
+
comparator(DATE_OPERAND) { |n| n > 0 },
|
96
|
+
semVerEqual:
|
97
|
+
comparator(SEMVER_OPERAND) { |n| n == 0 },
|
98
|
+
semVerLessThan:
|
99
|
+
comparator(SEMVER_OPERAND) { |n| n < 0 },
|
100
|
+
semVerGreaterThan:
|
101
|
+
comparator(SEMVER_OPERAND) { |n| n > 0 }
|
72
102
|
}
|
73
103
|
|
74
104
|
class EvaluationError < StandardError
|
@@ -223,7 +253,10 @@ module LaunchDarkly
|
|
223
253
|
def bucket_user(user, key, bucket_by, salt)
|
224
254
|
return nil unless user[:key]
|
225
255
|
|
226
|
-
id_hash = user_value(user, bucket_by)
|
256
|
+
id_hash = bucketable_string_value(user_value(user, bucket_by))
|
257
|
+
if id_hash.nil?
|
258
|
+
return 0.0
|
259
|
+
end
|
227
260
|
|
228
261
|
if user[:secondary]
|
229
262
|
id_hash += "." + user[:secondary]
|
@@ -235,6 +268,12 @@ module LaunchDarkly
|
|
235
268
|
hash_val.to_i(16) / Float(0xFFFFFFFFFFFFFFF)
|
236
269
|
end
|
237
270
|
|
271
|
+
def bucketable_string_value(value)
|
272
|
+
return value if value.is_a? String
|
273
|
+
return value.to_s if value.is_a? Integer
|
274
|
+
nil
|
275
|
+
end
|
276
|
+
|
238
277
|
def user_value(user, attribute)
|
239
278
|
attribute = attribute.to_sym
|
240
279
|
|
data/lib/ldclient-rb/version.rb
CHANGED
@@ -0,0 +1,316 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe LaunchDarkly::Evaluation do
|
4
|
+
subject { LaunchDarkly::Evaluation }
|
5
|
+
let(:features) { LaunchDarkly::InMemoryFeatureStore.new }
|
6
|
+
|
7
|
+
include LaunchDarkly::Evaluation
|
8
|
+
|
9
|
+
describe "evaluate" do
|
10
|
+
it "returns off variation if flag is off" do
|
11
|
+
flag = {
|
12
|
+
key: 'feature',
|
13
|
+
on: false,
|
14
|
+
offVariation: 1,
|
15
|
+
fallthrough: { variation: 0 },
|
16
|
+
variations: ['a', 'b', 'c']
|
17
|
+
}
|
18
|
+
user = { key: 'x' }
|
19
|
+
expect(evaluate(flag, user, features)).to eq({value: 'b', events: []})
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns nil if flag is off and off variation is unspecified" do
|
23
|
+
flag = {
|
24
|
+
key: 'feature',
|
25
|
+
on: false,
|
26
|
+
fallthrough: { variation: 0 },
|
27
|
+
variations: ['a', 'b', 'c']
|
28
|
+
}
|
29
|
+
user = { key: 'x' }
|
30
|
+
expect(evaluate(flag, user, features)).to eq({value: nil, events: []})
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns off variation if prerequisite is not found" do
|
34
|
+
flag = {
|
35
|
+
key: 'feature0',
|
36
|
+
on: true,
|
37
|
+
prerequisites: [{key: 'badfeature', variation: 1}],
|
38
|
+
fallthrough: { variation: 0 },
|
39
|
+
offVariation: 1,
|
40
|
+
variations: ['a', 'b', 'c']
|
41
|
+
}
|
42
|
+
user = { key: 'x' }
|
43
|
+
expect(evaluate(flag, user, features)).to eq({value: 'b', events: []})
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns off variation and event if prerequisite is not met" do
|
47
|
+
flag = {
|
48
|
+
key: 'feature0',
|
49
|
+
on: true,
|
50
|
+
prerequisites: [{key: 'feature1', variation: 1}],
|
51
|
+
fallthrough: { variation: 0 },
|
52
|
+
offVariation: 1,
|
53
|
+
variations: ['a', 'b', 'c'],
|
54
|
+
version: 1
|
55
|
+
}
|
56
|
+
flag1 = {
|
57
|
+
key: 'feature1',
|
58
|
+
on: true,
|
59
|
+
fallthrough: { variation: 0 },
|
60
|
+
variations: ['d', 'e'],
|
61
|
+
version: 2
|
62
|
+
}
|
63
|
+
features.upsert('feature1', flag1)
|
64
|
+
user = { key: 'x' }
|
65
|
+
events_should_be = [{kind: 'feature', key: 'feature1', value: 'd', version: 2, prereqOf: 'feature0'}]
|
66
|
+
expect(evaluate(flag, user, features)).to eq({value: 'b', events: events_should_be})
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns fallthrough variation and event if prerequisite is met and there are no rules" do
|
70
|
+
flag = {
|
71
|
+
key: 'feature0',
|
72
|
+
on: true,
|
73
|
+
prerequisites: [{key: 'feature1', variation: 1}],
|
74
|
+
fallthrough: { variation: 0 },
|
75
|
+
offVariation: 1,
|
76
|
+
variations: ['a', 'b', 'c'],
|
77
|
+
version: 1
|
78
|
+
}
|
79
|
+
flag1 = {
|
80
|
+
key: 'feature1',
|
81
|
+
on: true,
|
82
|
+
fallthrough: { variation: 1 },
|
83
|
+
variations: ['d', 'e'],
|
84
|
+
version: 2
|
85
|
+
}
|
86
|
+
features.upsert('feature1', flag1)
|
87
|
+
user = { key: 'x' }
|
88
|
+
events_should_be = [{kind: 'feature', key: 'feature1', value: 'e', version: 2, prereqOf: 'feature0'}]
|
89
|
+
expect(evaluate(flag, user, features)).to eq({value: 'a', events: events_should_be})
|
90
|
+
end
|
91
|
+
|
92
|
+
it "matches user from targets" do
|
93
|
+
flag = {
|
94
|
+
key: 'feature0',
|
95
|
+
on: true,
|
96
|
+
targets: [
|
97
|
+
{ values: [ 'whoever', 'userkey' ], variation: 2 }
|
98
|
+
],
|
99
|
+
fallthrough: { variation: 0 },
|
100
|
+
offVariation: 1,
|
101
|
+
variations: ['a', 'b', 'c']
|
102
|
+
}
|
103
|
+
user = { key: 'userkey' }
|
104
|
+
expect(evaluate(flag, user, features)).to eq({value: 'c', events: []})
|
105
|
+
end
|
106
|
+
|
107
|
+
it "matches user from rules" do
|
108
|
+
flag = {
|
109
|
+
key: 'feature0',
|
110
|
+
on: true,
|
111
|
+
rules: [
|
112
|
+
{
|
113
|
+
clauses: [
|
114
|
+
{
|
115
|
+
attribute: 'key',
|
116
|
+
op: 'in',
|
117
|
+
values: [ 'userkey' ]
|
118
|
+
}
|
119
|
+
],
|
120
|
+
variation: 2
|
121
|
+
}
|
122
|
+
],
|
123
|
+
fallthrough: { variation: 0 },
|
124
|
+
offVariation: 1,
|
125
|
+
variations: ['a', 'b', 'c']
|
126
|
+
}
|
127
|
+
user = { key: 'userkey' }
|
128
|
+
expect(evaluate(flag, user, features)).to eq({value: 'c', events: []})
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "clause_match_user" do
|
133
|
+
it "can match built-in attribute" do
|
134
|
+
user = { key: 'x', name: 'Bob' }
|
135
|
+
clause = { attribute: 'name', op: 'in', values: ['Bob'] }
|
136
|
+
expect(clause_match_user(clause, user)).to be true
|
137
|
+
end
|
138
|
+
|
139
|
+
it "can match custom attribute" do
|
140
|
+
user = { key: 'x', name: 'Bob', custom: { legs: 4 } }
|
141
|
+
clause = { attribute: 'legs', op: 'in', values: [4] }
|
142
|
+
expect(clause_match_user(clause, user)).to be true
|
143
|
+
end
|
144
|
+
|
145
|
+
it "returns false for missing attribute" do
|
146
|
+
user = { key: 'x', name: 'Bob' }
|
147
|
+
clause = { attribute: 'legs', op: 'in', values: [4] }
|
148
|
+
expect(clause_match_user(clause, user)).to be false
|
149
|
+
end
|
150
|
+
|
151
|
+
it "can be negated" do
|
152
|
+
user = { key: 'x', name: 'Bob' }
|
153
|
+
clause = { attribute: 'name', op: 'in', values: ['Bob'] }
|
154
|
+
expect {
|
155
|
+
clause[:negate] = true
|
156
|
+
}.to change {clause_match_user(clause, user)}.from(true).to(false)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "operators" do
|
161
|
+
dateStr1 = "2017-12-06T00:00:00.000-07:00"
|
162
|
+
dateStr2 = "2017-12-06T00:01:01.000-07:00"
|
163
|
+
dateMs1 = 10000000
|
164
|
+
dateMs2 = 10000001
|
165
|
+
invalidDate = "hey what's this?"
|
166
|
+
|
167
|
+
operatorTests = [
|
168
|
+
# numeric comparisons
|
169
|
+
[ :in, 99, 99, true ],
|
170
|
+
[ :in, 99.0001, 99.0001, true ],
|
171
|
+
[ :in, 99, 99.0001, false ],
|
172
|
+
[ :in, 99.0001, 99, false ],
|
173
|
+
[ :lessThan, 99, 99.0001, true ],
|
174
|
+
[ :lessThan, 99.0001, 99, false ],
|
175
|
+
[ :lessThan, 99, 99, false ],
|
176
|
+
[ :lessThanOrEqual, 99, 99.0001, true ],
|
177
|
+
[ :lessThanOrEqual, 99.0001, 99, false ],
|
178
|
+
[ :lessThanOrEqual, 99, 99, true ],
|
179
|
+
[ :greaterThan, 99.0001, 99, true ],
|
180
|
+
[ :greaterThan, 99, 99.0001, false ],
|
181
|
+
[ :greaterThan, 99, 99, false ],
|
182
|
+
[ :greaterThanOrEqual, 99.0001, 99, true ],
|
183
|
+
[ :greaterThanOrEqual, 99, 99.0001, false ],
|
184
|
+
[ :greaterThanOrEqual, 99, 99, true ],
|
185
|
+
|
186
|
+
# string comparisons
|
187
|
+
[ :in, "x", "x", true ],
|
188
|
+
[ :in, "x", "xyz", false ],
|
189
|
+
[ :startsWith, "xyz", "x", true ],
|
190
|
+
[ :startsWith, "x", "xyz", false ],
|
191
|
+
[ :endsWith, "xyz", "z", true ],
|
192
|
+
[ :endsWith, "z", "xyz", false ],
|
193
|
+
[ :contains, "xyz", "y", true ],
|
194
|
+
[ :contains, "y", "xyz", false ],
|
195
|
+
|
196
|
+
# mixed strings and numbers
|
197
|
+
[ :in, "99", 99, false ],
|
198
|
+
[ :in, 99, "99", false ],
|
199
|
+
#[ :contains, "99", 99, false ], # currently throws exception - would return false in Java SDK
|
200
|
+
#[ :startsWith, "99", 99, false ], # currently throws exception - would return false in Java SDK
|
201
|
+
#[ :endsWith, "99", 99, false ] # currently throws exception - would return false in Java SDK
|
202
|
+
[ :lessThanOrEqual, "99", 99, false ],
|
203
|
+
#[ :lessThanOrEqual, 99, "99", false ], # currently throws exception - would return false in Java SDK
|
204
|
+
[ :greaterThanOrEqual, "99", 99, false ],
|
205
|
+
#[ :greaterThanOrEqual, 99, "99", false ], # currently throws exception - would return false in Java SDK
|
206
|
+
|
207
|
+
# regex
|
208
|
+
[ :matches, "hello world", "hello.*rld", true ],
|
209
|
+
[ :matches, "hello world", "hello.*orl", true ],
|
210
|
+
[ :matches, "hello world", "l+", true ],
|
211
|
+
[ :matches, "hello world", "(world|planet)", true ],
|
212
|
+
[ :matches, "hello world", "aloha", false ],
|
213
|
+
#[ :matches, "hello world", "***not a regex", false ] # currently throws exception - same as Java SDK
|
214
|
+
|
215
|
+
# dates
|
216
|
+
[ :before, dateStr1, dateStr2, true ],
|
217
|
+
[ :before, dateMs1, dateMs2, true ],
|
218
|
+
[ :before, dateStr2, dateStr1, false ],
|
219
|
+
[ :before, dateMs2, dateMs1, false ],
|
220
|
+
[ :before, dateStr1, dateStr1, false ],
|
221
|
+
[ :before, dateMs1, dateMs1, false ],
|
222
|
+
[ :before, dateStr1, invalidDate, false ],
|
223
|
+
[ :after, dateStr1, dateStr2, false ],
|
224
|
+
[ :after, dateMs1, dateMs2, false ],
|
225
|
+
[ :after, dateStr2, dateStr1, true ],
|
226
|
+
[ :after, dateMs2, dateMs1, true ],
|
227
|
+
[ :after, dateStr1, dateStr1, false ],
|
228
|
+
[ :after, dateMs1, dateMs1, false ],
|
229
|
+
[ :after, dateStr1, invalidDate, false ],
|
230
|
+
|
231
|
+
# semver
|
232
|
+
[ :semVerEqual, "2.0.1", "2.0.1", true ],
|
233
|
+
[ :semVerEqual, "2.0", "2.0.0", true ],
|
234
|
+
[ :semVerEqual, "2-rc1", "2.0.0-rc1", true ],
|
235
|
+
[ :semVerEqual, "2+build2", "2.0.0+build2", true ],
|
236
|
+
[ :semVerLessThan, "2.0.0", "2.0.1", true ],
|
237
|
+
[ :semVerLessThan, "2.0", "2.0.1", true ],
|
238
|
+
[ :semVerLessThan, "2.0.1", "2.0.0", false ],
|
239
|
+
[ :semVerLessThan, "2.0.1", "2.0", false ],
|
240
|
+
[ :semVerLessThan, "2.0.0-rc", "2.0.0-rc.beta", true ],
|
241
|
+
[ :semVerGreaterThan, "2.0.1", "2.0.0", true ],
|
242
|
+
[ :semVerGreaterThan, "2.0.1", "2.0", true ],
|
243
|
+
[ :semVerGreaterThan, "2.0.0", "2.0.1", false ],
|
244
|
+
[ :semVerGreaterThan, "2.0", "2.0.1", false ],
|
245
|
+
[ :semVerGreaterThan, "2.0.0-rc.1", "2.0.0-rc.0", true ],
|
246
|
+
[ :semVerLessThan, "2.0.1", "xbad%ver", false ],
|
247
|
+
[ :semVerGreaterThan, "2.0.1", "xbad%ver", false ]
|
248
|
+
]
|
249
|
+
|
250
|
+
operatorTests.each do |params|
|
251
|
+
op = params[0]
|
252
|
+
value1 = params[1]
|
253
|
+
value2 = params[2]
|
254
|
+
shouldBe = params[3]
|
255
|
+
it "should return #{shouldBe} for #{value1} #{op} #{value2}" do
|
256
|
+
user = { key: 'x', custom: { foo: value1 } }
|
257
|
+
clause = { attribute: 'foo', op: op, values: [value2] }
|
258
|
+
expect(clause_match_user(clause, user)).to be shouldBe
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe "bucket_user" do
|
264
|
+
it "gets expected bucket values for specific keys" do
|
265
|
+
user = { key: "userKeyA" }
|
266
|
+
bucket = bucket_user(user, "hashKey", "key", "saltyA")
|
267
|
+
expect(bucket).to be_within(0.0000001).of(0.42157587);
|
268
|
+
|
269
|
+
user = { key: "userKeyB" }
|
270
|
+
bucket = bucket_user(user, "hashKey", "key", "saltyA")
|
271
|
+
expect(bucket).to be_within(0.0000001).of(0.6708485);
|
272
|
+
|
273
|
+
user = { key: "userKeyC" }
|
274
|
+
bucket = bucket_user(user, "hashKey", "key", "saltyA")
|
275
|
+
expect(bucket).to be_within(0.0000001).of(0.10343106);
|
276
|
+
end
|
277
|
+
|
278
|
+
it "can bucket by int value (equivalent to string)" do
|
279
|
+
user = {
|
280
|
+
key: "userkey",
|
281
|
+
custom: {
|
282
|
+
stringAttr: "33333",
|
283
|
+
intAttr: 33333
|
284
|
+
}
|
285
|
+
}
|
286
|
+
stringResult = bucket_user(user, "hashKey", "stringAttr", "saltyA")
|
287
|
+
intResult = bucket_user(user, "hashKey", "intAttr", "saltyA")
|
288
|
+
|
289
|
+
expect(intResult).to be_within(0.0000001).of(0.54771423)
|
290
|
+
expect(intResult).to eq(stringResult)
|
291
|
+
end
|
292
|
+
|
293
|
+
it "cannot bucket by float value" do
|
294
|
+
user = {
|
295
|
+
key: "userkey",
|
296
|
+
custom: {
|
297
|
+
floatAttr: 33.5
|
298
|
+
}
|
299
|
+
}
|
300
|
+
result = bucket_user(user, "hashKey", "floatAttr", "saltyA")
|
301
|
+
expect(result).to eq(0.0)
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
it "cannot bucket by bool value" do
|
306
|
+
user = {
|
307
|
+
key: "userkey",
|
308
|
+
custom: {
|
309
|
+
boolAttr: true
|
310
|
+
}
|
311
|
+
}
|
312
|
+
result = bucket_user(user, "hashKey", "boolAttr", "saltyA")
|
313
|
+
expect(result).to eq(0.0)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ldclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LaunchDarkly
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -168,6 +168,20 @@ dependencies:
|
|
168
168
|
- - "<"
|
169
169
|
- !ruby/object:Gem::Version
|
170
170
|
version: '3'
|
171
|
+
- !ruby/object:Gem::Dependency
|
172
|
+
name: semantic
|
173
|
+
requirement: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - "~>"
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: 1.6.0
|
178
|
+
type: :runtime
|
179
|
+
prerelease: false
|
180
|
+
version_requirements: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - "~>"
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: 1.6.0
|
171
185
|
- !ruby/object:Gem::Dependency
|
172
186
|
name: thread_safe
|
173
187
|
requirement: !ruby/object:Gem::Requirement
|
@@ -320,6 +334,7 @@ files:
|
|
320
334
|
- lib/ldclient-rb/version.rb
|
321
335
|
- scripts/release.sh
|
322
336
|
- spec/config_spec.rb
|
337
|
+
- spec/evaluation_spec.rb
|
323
338
|
- spec/event_serializer_spec.rb
|
324
339
|
- spec/feature_store_spec_base.rb
|
325
340
|
- spec/fixtures/feature.json
|
@@ -362,6 +377,7 @@ specification_version: 4
|
|
362
377
|
summary: LaunchDarkly SDK for Ruby
|
363
378
|
test_files:
|
364
379
|
- spec/config_spec.rb
|
380
|
+
- spec/evaluation_spec.rb
|
365
381
|
- spec/event_serializer_spec.rb
|
366
382
|
- spec/feature_store_spec_base.rb
|
367
383
|
- spec/fixtures/feature.json
|