ldclient-rb 2.4.1 → 2.5.0
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.
- 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
|