molasses 0.1.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 +7 -0
- data/README.md +99 -0
- data/lib/molasses.rb +182 -0
- data/spec/molasses_spec.rb +279 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 65c1760e6753f23f0b5029971c67d0bd637ec58abe376d8a1a4f151eaa05bb3c
|
4
|
+
data.tar.gz: d1c94afb5eb0be2190b7898599375aa9395a0b986d0c3281e497b9243dfed927
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 88459445d1b41d504aba89102b05d602587fde046c79a92c305e80097da3601c97fae412c8c2f394a68324aaacc887ce43cd9b5361e0b4d6f93466f1cf917b2d
|
7
|
+
data.tar.gz: 364dbb84b69da102d920599d2de04a4371a1e6935b9f8968bc2126b669a25b05294b5db4748f65f28650ad1307717732c4b699ced27ab41a86b7f7f9018f2ef7
|
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<img src="https://raw.githubusercontent.com/molassesapp/molasses-go/main/logo.png" style="margin: 0px auto;" width="200"/></p>
|
3
|
+
|
4
|
+
<h1 align="center">Molasses-Ruby</h1>
|
5
|
+
|
6
|
+
A Ruby SDK for Molasses. It allows you to evaluate user's status for a feature. It also helps simplify logging events for A/B testing.
|
7
|
+
|
8
|
+
Molasses uses polling to check if you have updated features. Once initialized, it takes microseconds to evaluate if a user is active.
|
9
|
+
|
10
|
+
## Install
|
11
|
+
|
12
|
+
```
|
13
|
+
gem install molasses
|
14
|
+
|
15
|
+
bundle add molasses
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
### Initialization
|
21
|
+
|
22
|
+
Start by initializing the client with an `APIKey`. This begins the polling for any feature updates. The updates happen every 15 seconds.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'molasses'
|
26
|
+
|
27
|
+
client = Molasses::Client.new("test_api_key")
|
28
|
+
|
29
|
+
```
|
30
|
+
|
31
|
+
If you decide not to track analytics events (experiment started, experiment success) you can turn them off by setting the `send_events` field to `false`
|
32
|
+
|
33
|
+
```go
|
34
|
+
client = Molasses::Client.new("test_api_key", false)
|
35
|
+
|
36
|
+
```
|
37
|
+
|
38
|
+
### Check if feature is active
|
39
|
+
|
40
|
+
You can call `is_active` with the key name and optionally a user's information. The `id` field is used to determine whether a user is part of a percentage of users. If you have other constraints based on user params you can pass those in the `params` field.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
client.is_active("FOO_TEST", {
|
44
|
+
"id"=>"foo",
|
45
|
+
"params"=>{
|
46
|
+
"isBetaUser"=>"false",
|
47
|
+
"isScaredUser"=>"false"
|
48
|
+
}
|
49
|
+
})
|
50
|
+
|
51
|
+
```
|
52
|
+
|
53
|
+
You can check if a feature is active for a user who is anonymous by just calling `isActive` with the key. You won't be able to do percentage roll outs or track that user's behavior.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
client.is_active("TEST_FEATURE_FOR_USER")
|
57
|
+
```
|
58
|
+
|
59
|
+
### Experiments
|
60
|
+
|
61
|
+
To track whether an experiment was successful you can call `experiment_success`. experiment_success takes the feature's name, any additional parameters for the event and the user.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
client.experiment_success("GOOGLE_SSO",{
|
65
|
+
"version": "v2.3.0"
|
66
|
+
},{
|
67
|
+
"id"=>"foo",
|
68
|
+
"params"=>{
|
69
|
+
"isBetaUser"=>"false",
|
70
|
+
"isScaredUser"=>"false"
|
71
|
+
}
|
72
|
+
})
|
73
|
+
```
|
74
|
+
|
75
|
+
## Example
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
require 'molasses'
|
79
|
+
|
80
|
+
client = Molasses::Client.new("test_api_key")
|
81
|
+
|
82
|
+
if client.is_active('NEW_CHECKOUT') {
|
83
|
+
puts "we are a go"
|
84
|
+
else
|
85
|
+
puts "we are a no go"
|
86
|
+
end
|
87
|
+
foo_test = client.is_active("FOO_TEST", {
|
88
|
+
"id"=>"foo",
|
89
|
+
"params"=>{
|
90
|
+
"isBetaUser"=>"false",
|
91
|
+
"isScaredUser"=>"false"
|
92
|
+
}
|
93
|
+
})
|
94
|
+
if foo_test
|
95
|
+
puts "we are a go"
|
96
|
+
else
|
97
|
+
puts "we are a no go"
|
98
|
+
end
|
99
|
+
```
|
data/lib/molasses.rb
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'workers'
|
2
|
+
require 'faraday'
|
3
|
+
require 'json'
|
4
|
+
require 'zlib'
|
5
|
+
module Molasses
|
6
|
+
class Client
|
7
|
+
def initialize(api_key, send_events=true, base_url="")
|
8
|
+
@api_key = api_key
|
9
|
+
@send_events = send_events
|
10
|
+
if base_url != ""
|
11
|
+
@base_url = base_url
|
12
|
+
else
|
13
|
+
@base_url = 'https://us-central1-molasses-36bff.cloudfunctions.net'
|
14
|
+
end
|
15
|
+
@conn = Faraday.new(
|
16
|
+
url: @base_url,
|
17
|
+
headers: {
|
18
|
+
'Content-Type' => 'application/json',
|
19
|
+
'Authorization' => "Bearer #{@api_key}"
|
20
|
+
}
|
21
|
+
)
|
22
|
+
@feature_cache = {}
|
23
|
+
@initialized = {}
|
24
|
+
fetch_features
|
25
|
+
@timer = Workers::PeriodicTimer.new(15) do
|
26
|
+
fetch_features
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_active(key, user={})
|
31
|
+
unless @initialized
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
|
35
|
+
if @feature_cache.include?(key)
|
36
|
+
feature = @feature_cache[key]
|
37
|
+
result = user_active(feature, user)
|
38
|
+
if @send_events && user && user.include?("id")
|
39
|
+
send_event({
|
40
|
+
"event"=> "experiment_started",
|
41
|
+
"tags"=> user["params"],
|
42
|
+
"userId"=> user["id"],
|
43
|
+
"featureId"=> feature["id"],
|
44
|
+
"featureName"=> key,
|
45
|
+
"testType"=> result ? "experiment" : "control"
|
46
|
+
})
|
47
|
+
end
|
48
|
+
return result
|
49
|
+
else
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def user_active(feature, user)
|
57
|
+
unless feature.include?("active")
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
|
61
|
+
unless user && user.include?("id")
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
|
65
|
+
segment_map = {}
|
66
|
+
for feature_segment in feature["segments"]
|
67
|
+
segment_type = feature_segment["segmentType"]
|
68
|
+
segment_map[segment_type] = feature_segment
|
69
|
+
end
|
70
|
+
|
71
|
+
if segment_map.include?("alwaysControl") and in_segment(user, segment_map["alwaysControl"])
|
72
|
+
return false
|
73
|
+
end
|
74
|
+
if segment_map.include?("alwaysExperiment") and in_segment(user, segment_map["alwaysExperiment"])
|
75
|
+
return true
|
76
|
+
end
|
77
|
+
|
78
|
+
return user_percentage(user["id"], segment_map["everyoneElse"]["percentage"])
|
79
|
+
end
|
80
|
+
|
81
|
+
def user_percentage(id="", percentage = 0)
|
82
|
+
if percentage == 100
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
|
86
|
+
if percentage == 0
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
|
90
|
+
c = Zlib.crc32(id)
|
91
|
+
v = (c % 100).abs
|
92
|
+
return v < percentage
|
93
|
+
end
|
94
|
+
|
95
|
+
def in_segment(user, segment_map)
|
96
|
+
user_constraints = segment_map["userConstraints"]
|
97
|
+
constraints_length = user_constraints.length
|
98
|
+
constraints_to_be_met = segment_map["constraint"] == 'any' ? 1 : constraints_length
|
99
|
+
constraints_met = 0
|
100
|
+
|
101
|
+
for constraint in user_constraints
|
102
|
+
param = constraint["userParam"]
|
103
|
+
param_exists = user["params"].include?(param)
|
104
|
+
user_value = nil
|
105
|
+
if param_exists
|
106
|
+
user_value = user["params"][param]
|
107
|
+
end
|
108
|
+
|
109
|
+
if param == "id"
|
110
|
+
param_exists = true
|
111
|
+
user_value = user["id"]
|
112
|
+
end
|
113
|
+
|
114
|
+
if meets_constraint(user_value, param_exists, constraint)
|
115
|
+
constraints_met = constraints_met + 1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
return constraints_met >= constraints_to_be_met
|
119
|
+
end
|
120
|
+
|
121
|
+
def meets_constraint(user_value, param_exists, constraint)
|
122
|
+
operator = constraint["operator"]
|
123
|
+
if param_exists == false
|
124
|
+
return false
|
125
|
+
end
|
126
|
+
|
127
|
+
case operator
|
128
|
+
when "in"
|
129
|
+
list_values = constraint["values"].split(',')
|
130
|
+
return list_values.include? user_value
|
131
|
+
when "nin"
|
132
|
+
list_values = constraint["values"].split(',')
|
133
|
+
return !list_values.include?(user_value)
|
134
|
+
when "equals"
|
135
|
+
return user_value == constraint["values"]
|
136
|
+
when "doesNotEqual"
|
137
|
+
return user_value != constraint["values"]
|
138
|
+
when "contains"
|
139
|
+
return constraint["values"].include?(user_value)
|
140
|
+
when "doesNotContain"
|
141
|
+
return !constraint["values"].include?(user_value)
|
142
|
+
else
|
143
|
+
return false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
def experiment_success(key, additional_details={}, user=nil)
|
147
|
+
if !@initialized || !@send_events || user == nil || user.include?("id")
|
148
|
+
return false
|
149
|
+
end
|
150
|
+
feature = @feature_cache[key]
|
151
|
+
result = is_active(feature, user)
|
152
|
+
send_event({
|
153
|
+
"event"=> "experiment_success",
|
154
|
+
"tags"=> user["params"].merge(additional_details),
|
155
|
+
"userId"=> user["id"],
|
156
|
+
"featureId"=> feature["id"],
|
157
|
+
"featureName"=> key,
|
158
|
+
"testType"=> result ? "experiment" : "control"
|
159
|
+
})
|
160
|
+
end
|
161
|
+
def send_event(event_options)
|
162
|
+
@conn.post('analytics', event_options.to_json)
|
163
|
+
end
|
164
|
+
|
165
|
+
def fetch_features()
|
166
|
+
response = @conn.get('get-features')
|
167
|
+
if response.status == 200
|
168
|
+
data = JSON.parse(response.body)
|
169
|
+
if data.include?("data") and data["data"].include?("features")
|
170
|
+
features = data["data"]["features"]
|
171
|
+
for feature in features do
|
172
|
+
@feature_cache[feature["key"]] = feature
|
173
|
+
end
|
174
|
+
@initialized = true
|
175
|
+
end
|
176
|
+
else
|
177
|
+
puts "Molasses - #{response.status} - #{response.body}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'molasses'
|
3
|
+
require 'webmock/rspec'
|
4
|
+
responseA = {
|
5
|
+
"data": {
|
6
|
+
"features": [
|
7
|
+
{
|
8
|
+
"active": true,
|
9
|
+
"description": "foo",
|
10
|
+
"key": "FOO_TEST",
|
11
|
+
"segments": [
|
12
|
+
{
|
13
|
+
"constraint": "all",
|
14
|
+
"percentage": 100,
|
15
|
+
"segmentType": "alwaysControl",
|
16
|
+
"userConstraints": [
|
17
|
+
{
|
18
|
+
"userParam": "isScaredUser",
|
19
|
+
"operator": "in",
|
20
|
+
"values": "true,maybe",
|
21
|
+
},
|
22
|
+
],
|
23
|
+
},
|
24
|
+
{
|
25
|
+
"constraint": "all",
|
26
|
+
"percentage": 100,
|
27
|
+
"segmentType": "alwaysExperiment",
|
28
|
+
"userConstraints": [
|
29
|
+
{
|
30
|
+
"userParam": "isBetaUser",
|
31
|
+
"operator": "equals",
|
32
|
+
"values": "true",
|
33
|
+
},
|
34
|
+
],
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"constraint": "all",
|
38
|
+
"percentage": 100,
|
39
|
+
"segmentType": "everyoneElse",
|
40
|
+
"userConstraints": [],
|
41
|
+
},
|
42
|
+
],
|
43
|
+
},
|
44
|
+
],
|
45
|
+
}, }
|
46
|
+
responseB = {
|
47
|
+
"data": {
|
48
|
+
"features": [
|
49
|
+
{
|
50
|
+
"id": "1",
|
51
|
+
"active": true,
|
52
|
+
"description": "foo",
|
53
|
+
"key": "FOO_TEST",
|
54
|
+
"segments": [
|
55
|
+
{
|
56
|
+
"constraint": "all",
|
57
|
+
"percentage": 100,
|
58
|
+
"segmentType": "alwaysControl",
|
59
|
+
"userConstraints": [
|
60
|
+
{
|
61
|
+
"userParam": "isScaredUser",
|
62
|
+
"operator": "nin",
|
63
|
+
"values": "false,maybe",
|
64
|
+
},
|
65
|
+
],
|
66
|
+
},
|
67
|
+
{
|
68
|
+
"constraint": "all",
|
69
|
+
"percentage": 100,
|
70
|
+
"segmentType": "alwaysExperiment",
|
71
|
+
"userConstraints": [
|
72
|
+
{
|
73
|
+
"userParam": "isBetaUser",
|
74
|
+
"operator": "doesNotEqual",
|
75
|
+
"values": "false",
|
76
|
+
},
|
77
|
+
],
|
78
|
+
},
|
79
|
+
{
|
80
|
+
"constraint": "all",
|
81
|
+
"percentage": 100,
|
82
|
+
"segmentType": "everyoneElse",
|
83
|
+
"userConstraints": [],
|
84
|
+
},
|
85
|
+
],
|
86
|
+
},
|
87
|
+
],
|
88
|
+
},
|
89
|
+
}
|
90
|
+
|
91
|
+
responseC = {
|
92
|
+
"data": {
|
93
|
+
"features": [
|
94
|
+
{
|
95
|
+
"id": "1",
|
96
|
+
"active": true,
|
97
|
+
"description": "foo",
|
98
|
+
"key": "FOO_TEST",
|
99
|
+
"segments": [
|
100
|
+
{
|
101
|
+
"percentage": 100,
|
102
|
+
"segmentType": "alwaysControl",
|
103
|
+
"constraint": "all",
|
104
|
+
"userConstraints": [
|
105
|
+
{
|
106
|
+
"userParam": "isScaredUser",
|
107
|
+
"operator": "contains",
|
108
|
+
"values": "scared",
|
109
|
+
},
|
110
|
+
{
|
111
|
+
"userParam": "isDefinitelyScaredUser",
|
112
|
+
"operator": "contains",
|
113
|
+
"values": "scared",
|
114
|
+
},
|
115
|
+
{
|
116
|
+
"userParam": "isMostDefinitelyScaredUser",
|
117
|
+
"operator": "contains",
|
118
|
+
"values": "scared",
|
119
|
+
},
|
120
|
+
],
|
121
|
+
},
|
122
|
+
{
|
123
|
+
"percentage": 100,
|
124
|
+
"segmentType": "alwaysExperiment",
|
125
|
+
"constraint": "any",
|
126
|
+
"userConstraints": [
|
127
|
+
{
|
128
|
+
"userParam": "isBetaUser",
|
129
|
+
"operator": "doesNotContain",
|
130
|
+
"values": "fal",
|
131
|
+
},
|
132
|
+
{
|
133
|
+
"userParam": "isDefinitelyBetaUser",
|
134
|
+
"operator": "doesNotContain",
|
135
|
+
"values": "fal",
|
136
|
+
},
|
137
|
+
],
|
138
|
+
},
|
139
|
+
{
|
140
|
+
"constraint": "all",
|
141
|
+
"percentage": 100,
|
142
|
+
"segmentType": "everyoneElse",
|
143
|
+
"userConstraints": [],
|
144
|
+
},
|
145
|
+
],
|
146
|
+
},
|
147
|
+
],
|
148
|
+
},
|
149
|
+
}
|
150
|
+
|
151
|
+
responseD = {
|
152
|
+
"data": {
|
153
|
+
"features": [
|
154
|
+
{
|
155
|
+
"id": "1",
|
156
|
+
"active": true,
|
157
|
+
"description": "foo",
|
158
|
+
"key": "FOO_TEST",
|
159
|
+
"segments": [],
|
160
|
+
},
|
161
|
+
{
|
162
|
+
"id": "2",
|
163
|
+
"active": false,
|
164
|
+
"description": "foo",
|
165
|
+
"key": "FOO_FALSE_TEST",
|
166
|
+
"segments": [],
|
167
|
+
},
|
168
|
+
{
|
169
|
+
"id": "3",
|
170
|
+
"active": true,
|
171
|
+
"description": "foo",
|
172
|
+
"key": "FOO_50_PERCENT_TEST",
|
173
|
+
"segments": [
|
174
|
+
{
|
175
|
+
"constraint": "all",
|
176
|
+
"segmentType": "everyoneElse",
|
177
|
+
"percentage": 50,
|
178
|
+
"userConstraints": [],
|
179
|
+
},
|
180
|
+
],
|
181
|
+
},
|
182
|
+
{
|
183
|
+
"id": "4",
|
184
|
+
"active": true,
|
185
|
+
"description": "foo",
|
186
|
+
"key": "FOO_0_PERCENT_TEST",
|
187
|
+
"segments": [
|
188
|
+
{
|
189
|
+
"constraint": "all",
|
190
|
+
"segmentType": "everyoneElse",
|
191
|
+
"percentage": 0,
|
192
|
+
"userConstraints": [],
|
193
|
+
},
|
194
|
+
],
|
195
|
+
},
|
196
|
+
{
|
197
|
+
"id": "5",
|
198
|
+
"active": true,
|
199
|
+
"description": "foo",
|
200
|
+
"key": "FOO_ID_TEST",
|
201
|
+
"segments": [
|
202
|
+
{
|
203
|
+
"constraint": "all",
|
204
|
+
"percentage": 100,
|
205
|
+
"segmentType": "alwaysControl",
|
206
|
+
"userConstraints": [
|
207
|
+
{
|
208
|
+
"userParam": "id",
|
209
|
+
"operator": "equals",
|
210
|
+
"values": "123",
|
211
|
+
},
|
212
|
+
],
|
213
|
+
},
|
214
|
+
{
|
215
|
+
"constraint": "all",
|
216
|
+
"segmentType": "everyoneElse",
|
217
|
+
"percentage": 100,
|
218
|
+
"userConstraints": [],
|
219
|
+
},
|
220
|
+
],
|
221
|
+
},
|
222
|
+
],
|
223
|
+
},
|
224
|
+
}
|
225
|
+
|
226
|
+
$client = nil
|
227
|
+
$conn = nil
|
228
|
+
$stubs = nil
|
229
|
+
RSpec.describe Molasses::Client do
|
230
|
+
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
231
|
+
let(:conn) { Faraday.new { |b| b.adapter(:test, stubs) } }
|
232
|
+
let(:client) { Molasses::Client.new("test_api_key", false) }
|
233
|
+
it "can do basic molasses" do
|
234
|
+
stub_request(:get, 'https://us-central1-molasses-36bff.cloudfunctions.net/get-features').
|
235
|
+
to_return(body:responseA.to_json, headers: {
|
236
|
+
'Content-Type'=>'application/json',
|
237
|
+
},)
|
238
|
+
expect(client.is_active("FOO_TEST")).to be_truthy
|
239
|
+
expect(client.is_active("FOO_TEST", {"foo"=>"foo"})).to be_truthy
|
240
|
+
expect(client.is_active("NOT_CHECKOUT")).to be_falsy
|
241
|
+
expect(client.is_active("FOO_TEST", {"id"=>"foo", "params"=>{}})).to be_truthy
|
242
|
+
expect(client.is_active("FOO_TEST", {"id"=>"food", "params"=>{
|
243
|
+
"isScaredUser"=>"true"}})).to be_falsy
|
244
|
+
expect(client.is_active("FOO_TEST", {"id"=>"foodie", "params"=>{
|
245
|
+
"isBetaUser"=>"true"}})).to be_truthy
|
246
|
+
end
|
247
|
+
it "can do advanced molasses" do
|
248
|
+
stub_request(:get, 'https://us-central1-molasses-36bff.cloudfunctions.net/get-features').
|
249
|
+
to_return(body:responseB.to_json, headers: {
|
250
|
+
'Content-Type'=>'application/json',
|
251
|
+
},)
|
252
|
+
|
253
|
+
expect(client.is_active("FOO_TEST")).to be_truthy
|
254
|
+
expect(client.is_active("FOO_TEST", {"foo"=>"foo"})).to be_truthy
|
255
|
+
expect(client.is_active("NOT_CHECKOUT")).to be_falsy
|
256
|
+
expect(client.is_active("FOO_TEST", {"id"=>"foo", "params"=>{
|
257
|
+
"isBetaUser"=>"false", "isScaredUser"=>"false"}})).to be_truthy
|
258
|
+
expect(client.is_active("FOO_TEST", {"id"=>"food", "params"=>{
|
259
|
+
"isScaredUser"=>"true"}})).to be_falsy
|
260
|
+
expect(client.is_active("FOO_TEST", {"id"=>"foodie", "params"=>{
|
261
|
+
"isBetaUser"=>"true"}})).to be_truthy
|
262
|
+
end
|
263
|
+
it "can do even more advanced molasses" do
|
264
|
+
stub_request(:get, 'https://us-central1-molasses-36bff.cloudfunctions.net/get-features').
|
265
|
+
to_return(body:responseC.to_json, headers: {
|
266
|
+
'Content-Type'=>'application/json',
|
267
|
+
},)
|
268
|
+
|
269
|
+
expect(client.is_active("FOO_TEST", {"id"=> "foo", "params" => {
|
270
|
+
"isScaredUser"=> "scared",
|
271
|
+
"isDefinitelyScaredUser"=> "scared",
|
272
|
+
"isMostDefinitelyScaredUser"=> "scared",
|
273
|
+
}})).to be_falsy
|
274
|
+
expect(client.is_active("FOO_TEST", {"id"=>"food", "params"=>{
|
275
|
+
"isDefinitelyBetaUser"=>"true", "isBetaUser"=>"true"}})).to be_truthy
|
276
|
+
expect(client.is_active("FOO_TEST", {"id"=>"foodie", "params"=>{
|
277
|
+
"isBetaUser"=>"true"}})).to be_truthy
|
278
|
+
end
|
279
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: molasses
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Hrisho
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-09-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
description: Ruby SDK for Molasses. Feature flags as a service
|
28
|
+
email: james.hrisho@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- README.md
|
34
|
+
- lib/molasses.rb
|
35
|
+
- spec/molasses_spec.rb
|
36
|
+
homepage: https://molasses.app
|
37
|
+
licenses:
|
38
|
+
- MIT
|
39
|
+
metadata:
|
40
|
+
source_code_uri: https://github.com/molassesapp/molasses-ruby
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubygems_version: 3.0.3
|
57
|
+
signing_key:
|
58
|
+
specification_version: 4
|
59
|
+
summary: Ruby SDK for Molasses. Feature flags as a service
|
60
|
+
test_files: []
|