myna_eventmachine 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/Gemfile.lock +19 -0
- data/README.md +16 -0
- data/Rakefile +12 -0
- data/lib/myna/future.rb +90 -0
- data/lib/myna/myna.rb +230 -0
- data/lib/myna/response.rb +141 -0
- data/myna_eventmachine.gemspec +20 -0
- data/test/test_authorized.rb +78 -0
- data/test/test_future.rb +54 -0
- data/test/test_response.rb +101 -0
- data/test/test_suggest.rb +62 -0
- metadata +61 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.2.6)
|
5
|
+
em-http-request (0.3.0)
|
6
|
+
addressable (>= 2.0.0)
|
7
|
+
escape_utils
|
8
|
+
eventmachine (>= 0.12.9)
|
9
|
+
escape_utils (0.2.4)
|
10
|
+
eventmachine (0.12.10)
|
11
|
+
json (1.6.1)
|
12
|
+
|
13
|
+
PLATFORMS
|
14
|
+
ruby
|
15
|
+
|
16
|
+
DEPENDENCIES
|
17
|
+
em-http-request
|
18
|
+
eventmachine
|
19
|
+
json
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Myna Ruby/EventMachine Client
|
2
|
+
|
3
|
+
This is a Ruby client for the v1 Myna API, using EventMachine for asychronous goodness.
|
4
|
+
|
5
|
+
Tested in Ruby 1.9.2
|
6
|
+
|
7
|
+
## Development Notes
|
8
|
+
|
9
|
+
The easiest way to install the dependencies is to install [Bundler](http://gembundler.com/) and then run `bundle install`
|
10
|
+
|
11
|
+
Run `rake test` to run the tests.
|
12
|
+
|
13
|
+
|
14
|
+
## TODO
|
15
|
+
|
16
|
+
Futures need to handle failures, and need to rigourously trap exceptions and propagate them
|
data/Rakefile
ADDED
data/lib/myna/future.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright 2011 by Untyped Ltd. All Rights Reserved\
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# No portion of this Software shall be used in any application which does not
|
14
|
+
# use the ReportGrid platform to provide some subset of its functionality.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'thread'
|
26
|
+
|
27
|
+
|
28
|
+
module Future
|
29
|
+
class Future
|
30
|
+
def initialize()
|
31
|
+
@lock = Mutex.new
|
32
|
+
@signal = ConditionVariable.new
|
33
|
+
@value = nil
|
34
|
+
@delivered = false
|
35
|
+
@listeners = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def deliver(value)
|
39
|
+
@lock.synchronize {
|
40
|
+
if @delivered
|
41
|
+
raise ArgumentError, "This future has already been delivered a value"
|
42
|
+
else
|
43
|
+
@value = value
|
44
|
+
@delivered = true
|
45
|
+
@signal.broadcast
|
46
|
+
end
|
47
|
+
}
|
48
|
+
@listeners.each do |block|
|
49
|
+
block.call(value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def get
|
54
|
+
@lock.synchronize {
|
55
|
+
if !@delivered
|
56
|
+
@signal.wait(@lock)
|
57
|
+
@signal.broadcast
|
58
|
+
end
|
59
|
+
|
60
|
+
@value
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def on_complete(&block)
|
65
|
+
return_value = false
|
66
|
+
@lock.synchronize {
|
67
|
+
if @delivered
|
68
|
+
return_value = true
|
69
|
+
else
|
70
|
+
@listeners.push(block)
|
71
|
+
end
|
72
|
+
}
|
73
|
+
|
74
|
+
# We can't call the block inside the synchronise block as the
|
75
|
+
# block may itself block on another lock. In this case it won't
|
76
|
+
# exit the synchronise block and we can get deadlock
|
77
|
+
if return_value
|
78
|
+
block.call(@value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def map(&block)
|
83
|
+
future = Future.new
|
84
|
+
self.on_complete do |value|
|
85
|
+
future.deliver(block.call(value))
|
86
|
+
end
|
87
|
+
future
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/myna/myna.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
# Copyright 2011 by Untyped Ltd. All Rights Reserved\
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# No portion of this Software shall be used in any application which does not
|
14
|
+
# use the ReportGrid platform to provide some subset of its functionality.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'eventmachine'
|
26
|
+
require 'em-http-request'
|
27
|
+
require 'json'
|
28
|
+
require 'myna/future'
|
29
|
+
require 'myna/response'
|
30
|
+
|
31
|
+
module Myna
|
32
|
+
# Run EventMachine, if it is not already running. Returns a future.
|
33
|
+
# Block on the future to wait till EventMachine is up
|
34
|
+
def Myna.run()
|
35
|
+
future = Future::Future.new
|
36
|
+
|
37
|
+
if EventMachine::reactor_running?
|
38
|
+
future.deliver(true)
|
39
|
+
else
|
40
|
+
Thread.new do
|
41
|
+
EventMachine::run {}
|
42
|
+
end
|
43
|
+
|
44
|
+
EventMachine::next_tick do
|
45
|
+
future.deliver(true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
future
|
50
|
+
end
|
51
|
+
|
52
|
+
def Myna.stop()
|
53
|
+
EventMachine::stop_event_loop
|
54
|
+
end
|
55
|
+
|
56
|
+
module AuthorizedHost
|
57
|
+
SCHEME = "https"
|
58
|
+
HOST = 'api.mynaweb.com'
|
59
|
+
PORT = 443
|
60
|
+
end
|
61
|
+
|
62
|
+
module UnauthorizedHost
|
63
|
+
SCHEME = "http"
|
64
|
+
HOST = 'api.mynaweb.com'
|
65
|
+
PORT = 80
|
66
|
+
end
|
67
|
+
|
68
|
+
class Api
|
69
|
+
def initialize(api)
|
70
|
+
@api = api
|
71
|
+
end
|
72
|
+
|
73
|
+
def make_request(uri)
|
74
|
+
# puts "Contacting "+uri
|
75
|
+
future = Future::Future.new
|
76
|
+
|
77
|
+
EventMachine::schedule do
|
78
|
+
client = EventMachine::HttpRequest.new(uri)
|
79
|
+
|
80
|
+
http = yield(client)
|
81
|
+
http.errback { future.deliver(Response::NetworkError.new) }
|
82
|
+
http.callback { future.deliver(Response.parse(http.response)) }
|
83
|
+
end
|
84
|
+
|
85
|
+
future
|
86
|
+
end
|
87
|
+
|
88
|
+
def build_uri(path)
|
89
|
+
"#{@api::SCHEME}://#{@api::HOST}:#{@api::PORT}#{path}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def create
|
93
|
+
build_uri("/v1/experiment/new")
|
94
|
+
end
|
95
|
+
|
96
|
+
def suggest(uuid)
|
97
|
+
build_uri("/v1/experiment/#{uuid}/suggest")
|
98
|
+
end
|
99
|
+
|
100
|
+
def reward(uuid)
|
101
|
+
build_uri("/v1/experiment/#{uuid}/reward")
|
102
|
+
end
|
103
|
+
|
104
|
+
def delete(uuid)
|
105
|
+
build_uri("/v1/experiment/#{uuid}/delete")
|
106
|
+
end
|
107
|
+
|
108
|
+
def info(uuid)
|
109
|
+
build_uri("/v1/experiment/#{uuid}/info")
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_variant(uuid)
|
113
|
+
build_uri("/v1/experiment/#{uuid}/new-variant")
|
114
|
+
end
|
115
|
+
|
116
|
+
def delete_variant(uuid)
|
117
|
+
build_uri("/v1/experiment/#{uuid}/delete-variant")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# The simple API for unauthorized access
|
122
|
+
class Experiment
|
123
|
+
def initialize(uuid, host = UnauthorizedHost)
|
124
|
+
@uuid = uuid
|
125
|
+
@host = host
|
126
|
+
@api = Api.new(host)
|
127
|
+
end
|
128
|
+
|
129
|
+
attr_reader :uuid
|
130
|
+
|
131
|
+
def suggest()
|
132
|
+
@api.make_request(@api.suggest(@uuid)) do |client|
|
133
|
+
client.get(:head => {'Accept' => 'application/json'})
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def reward(token, amount = 1.0)
|
138
|
+
@api.make_request(@api.reward(@uuid)) do |client|
|
139
|
+
client.post(:head => {'Accept' => 'application/json'},
|
140
|
+
:body => {:token => token, :amount => amount}.to_json)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def Myna.authorize(email, password, host = AuthorizedHost)
|
146
|
+
ExperimentFactory.new(email, password, host)
|
147
|
+
end
|
148
|
+
|
149
|
+
# The more complex API for authorized access
|
150
|
+
class ExperimentFactory
|
151
|
+
def initialize(email, password, host = AuthorizedHost)
|
152
|
+
@email = email
|
153
|
+
@password = password
|
154
|
+
@host = host
|
155
|
+
@api = Api.new(host)
|
156
|
+
end
|
157
|
+
|
158
|
+
def create(name)
|
159
|
+
future =
|
160
|
+
@api.make_request(@api.create()) do |client|
|
161
|
+
client.post(:head => {
|
162
|
+
'Accept' => 'application/json',
|
163
|
+
'Authorization' => [@email, @password]
|
164
|
+
},
|
165
|
+
:body => {:experiment => name}.to_json)
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
future.map do |response|
|
170
|
+
if response.kind_of? Response::Uuid
|
171
|
+
AuthorizedExperiment.new(response.uuid, @email, @password, @host)
|
172
|
+
else
|
173
|
+
response
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def experiment(uuid)
|
179
|
+
AuthorizedExperiment.new(uuid, @email, @password, @host)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class AuthorizedExperiment < Experiment
|
184
|
+
def initialize(uuid, email, password, host)
|
185
|
+
super(uuid, host)
|
186
|
+
@email = email
|
187
|
+
@password = password
|
188
|
+
end
|
189
|
+
|
190
|
+
def delete
|
191
|
+
@api.make_request(@api.delete(@uuid)) do |client|
|
192
|
+
client.get(:head => {
|
193
|
+
'Accept' => 'application/json',
|
194
|
+
'Authorization' => [@email, @password]
|
195
|
+
})
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def info
|
200
|
+
@api.make_request(@api.info(@uuid)) do |client|
|
201
|
+
client.get(:head => {
|
202
|
+
'Accept' => 'application/json',
|
203
|
+
'Authorization' => [@email, @password]
|
204
|
+
})
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def create_variant(name)
|
209
|
+
@api.make_request(@api.create_variant(@uuid)) do |client|
|
210
|
+
client.post(:head => {
|
211
|
+
'Accept' => 'application/json',
|
212
|
+
'Authorization' => [@email, @password]
|
213
|
+
},
|
214
|
+
:body => {:variant => name}.to_json)
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def delete_variant(name)
|
220
|
+
@api.make_request(@api.delete_variant(@uuid)) do |client|
|
221
|
+
client.post(:head => {
|
222
|
+
'Accept' => 'application/json',
|
223
|
+
'Authorization' => [@email, @password]
|
224
|
+
},
|
225
|
+
:body => {:variant => name}.to_json)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# Copyright 2011 by Untyped Ltd. All Rights Reserved\
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# No portion of this Software shall be used in any application which does not
|
14
|
+
# use the ReportGrid platform to provide some subset of its functionality.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'json'
|
26
|
+
require 'singleton'
|
27
|
+
|
28
|
+
module Response
|
29
|
+
# String -> Response
|
30
|
+
#
|
31
|
+
# Parse a JSON string into a Response object
|
32
|
+
def Response.parse(str)
|
33
|
+
json = JSON.parse str
|
34
|
+
case json['typename']
|
35
|
+
when 'problem'
|
36
|
+
ApiError.from_json(json)
|
37
|
+
when 'ok'
|
38
|
+
Ok.instance
|
39
|
+
when 'suggestion'
|
40
|
+
Suggestion.from_json(json)
|
41
|
+
when 'uuid'
|
42
|
+
Uuid.from_json(json)
|
43
|
+
when 'experiment'
|
44
|
+
Experiment.from_json(json)
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Typename "+json['typename']+" is not a recognized response.", caller
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class Response
|
52
|
+
end
|
53
|
+
|
54
|
+
class MynaError < Response
|
55
|
+
end
|
56
|
+
|
57
|
+
class NetworkError < MynaError
|
58
|
+
end
|
59
|
+
|
60
|
+
class ApiError < MynaError
|
61
|
+
def self.from_json(json)
|
62
|
+
error = ApiError.new(json['subtype'], json['messages'])
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :subtype
|
66
|
+
attr_reader :messages
|
67
|
+
|
68
|
+
def initialize(subtype, messages)
|
69
|
+
@subtype = subtype
|
70
|
+
@messages = messages
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Singleton
|
75
|
+
class Ok < Response
|
76
|
+
include Singleton
|
77
|
+
end
|
78
|
+
|
79
|
+
class Suggestion < Response
|
80
|
+
def self.from_json(json)
|
81
|
+
suggestion = Suggestion.new(json['token'], json['choice'])
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_reader :token
|
85
|
+
attr_reader :choice
|
86
|
+
|
87
|
+
def initialize(token, choice)
|
88
|
+
@token = token
|
89
|
+
@choice = choice
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Uuid < Response
|
94
|
+
def self.from_json(json)
|
95
|
+
uuid = Uuid.new(json['uuid'])
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_reader :uuid
|
99
|
+
|
100
|
+
def initialize(uuid)
|
101
|
+
@uuid = uuid
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Experiment < Response
|
106
|
+
def self.from_json(json)
|
107
|
+
variants = json['variants'].map { |variant| Variant.from_json(variant) }
|
108
|
+
experiment = Experiment.new(json['name'], json['uuid'], json['accountId'], variants)
|
109
|
+
end
|
110
|
+
|
111
|
+
attr_reader :name
|
112
|
+
attr_reader :uuid
|
113
|
+
attr_reader :accountId
|
114
|
+
attr_reader :variants
|
115
|
+
|
116
|
+
def initialize(name, uuid, accountId, variants)
|
117
|
+
@name = name
|
118
|
+
@uuid = uuid
|
119
|
+
@accountId = accountId
|
120
|
+
@variants = variants
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class Variant < Response
|
125
|
+
def self.from_json(json)
|
126
|
+
variant = Variant.new(json['name'], json['views'], json['totalReward'], json['confidenceBound'])
|
127
|
+
end
|
128
|
+
|
129
|
+
attr_reader :name
|
130
|
+
attr_reader :views
|
131
|
+
attr_reader :totalReward
|
132
|
+
attr_reader :confidenceBound
|
133
|
+
|
134
|
+
def initialize(name, views, totalReward, confidenceBound)
|
135
|
+
@name = name
|
136
|
+
@views = views
|
137
|
+
@totalReward = totalReward
|
138
|
+
@confidenceBound = confidenceBound
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "myna/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'myna_eventmachine'
|
7
|
+
s.version = Myna::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.date = '2012-05-11'
|
10
|
+
s.summary = 'Ruby/EventMachine client library for Myna (http://www.mynaweb.com)'
|
11
|
+
s.authors = ["Noel Welsh"]
|
12
|
+
s.description = 'An EventMachine based client for '
|
13
|
+
s.email = ["noel [at] untyped [dot] com"]
|
14
|
+
s.files = ["lib/myna.rb"]
|
15
|
+
s.homepage = 'http://www.mynaweb.com/'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Copyright 2011 by Untyped Ltd. All Rights Reserved\
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# No portion of this Software shall be used in any application which does not
|
14
|
+
# use the ReportGrid platform to provide some subset of its functionality.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'minitest/autorun'
|
26
|
+
require 'myna'
|
27
|
+
|
28
|
+
describe Myna do
|
29
|
+
|
30
|
+
before do
|
31
|
+
Myna.run.get
|
32
|
+
end
|
33
|
+
|
34
|
+
module TestHost
|
35
|
+
SCHEME = "http"
|
36
|
+
HOST = 'localhost'
|
37
|
+
PORT = 8080
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "create and delete" do
|
41
|
+
it "must return an Experiment and delete same experiment" do
|
42
|
+
myna = Myna.authorize("test@example.com", "themostsecretpassword")
|
43
|
+
|
44
|
+
expt = myna.create("dummy experimento").get
|
45
|
+
expt.must_be_kind_of Myna::AuthorizedExperiment
|
46
|
+
|
47
|
+
expt.delete().get.must_be_kind_of Response::Ok
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "create_variant and delete_variant" do
|
52
|
+
it "must create and delete variants" do
|
53
|
+
myna = Myna.authorize("test@example.com", "themostsecretpassword")
|
54
|
+
|
55
|
+
expt = myna.create("dummy experimento").get
|
56
|
+
|
57
|
+
expt.create_variant("An awesome variant").get.must_be_kind_of Response::Ok
|
58
|
+
expt.delete_variant("An awesome variant").get.must_be_kind_of Response::Ok
|
59
|
+
|
60
|
+
expt.delete().get.must_be_kind_of Response::Ok
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "info" do
|
65
|
+
it "must return the expected info" do
|
66
|
+
myna = Myna.authorize("test@example.com", "themostsecretpassword")
|
67
|
+
|
68
|
+
info = myna.experiment('45923780-80ed-47c6-aa46-15e2ae7a0e8c').info().get
|
69
|
+
|
70
|
+
info.must_be_kind_of Response::Experiment
|
71
|
+
info.name.must_equal "test"
|
72
|
+
info.uuid.must_equal '45923780-80ed-47c6-aa46-15e2ae7a0e8c'
|
73
|
+
info.variants.length.must_equal 2
|
74
|
+
info.variants[0].name.must_equal 'variant1'
|
75
|
+
info.variants[1].name.must_equal 'variant2'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/test/test_future.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Copyright 2011 by Untyped Ltd. All Rights Reserved\
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# No portion of this Software shall be used in any application which does not
|
14
|
+
# use the ReportGrid platform to provide some subset of its functionality.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'minitest/autorun'
|
26
|
+
require 'myna/future'
|
27
|
+
|
28
|
+
describe Future do
|
29
|
+
describe "Future.map" do
|
30
|
+
it "must run the given block and complete the returned future" do
|
31
|
+
f1 = Future::Future.new
|
32
|
+
f2 = f1.map do |v|
|
33
|
+
v + 3
|
34
|
+
end
|
35
|
+
|
36
|
+
f1.deliver(1)
|
37
|
+
f2.get.must_equal 4
|
38
|
+
end
|
39
|
+
|
40
|
+
it "must run the given block and complete the returned future from another thread" do
|
41
|
+
f1 = Future::Future.new
|
42
|
+
f2 = f1.map do |v|
|
43
|
+
v + 3
|
44
|
+
end
|
45
|
+
|
46
|
+
Thread.new do
|
47
|
+
sleep(1)
|
48
|
+
f1.deliver(1)
|
49
|
+
end
|
50
|
+
|
51
|
+
f2.get.must_equal 4
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# Copyright 2011 by Untyped Ltd. All Rights Reserved\
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# No portion of this Software shall be used in any application which does not
|
14
|
+
# use the ReportGrid platform to provide some subset of its functionality.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'minitest/autorun'
|
26
|
+
require 'myna/response'
|
27
|
+
|
28
|
+
describe Response do
|
29
|
+
|
30
|
+
describe "parse" do
|
31
|
+
it "must parse an error" do
|
32
|
+
txt = '{"typename":"problem","subtype":400,"messages":{"typename":"message", "info":"Experiment cannot be found"}}'
|
33
|
+
msg = {"typename" => "message", "info" => "Experiment cannot be found"}
|
34
|
+
ans = Response.parse(txt)
|
35
|
+
|
36
|
+
|
37
|
+
ans.must_be_kind_of Response::ApiError
|
38
|
+
ans.subtype.must_equal 400
|
39
|
+
ans.messages.must_equal msg
|
40
|
+
end
|
41
|
+
|
42
|
+
it "must parse ok" do
|
43
|
+
ans = Response.parse('{"typename":"ok"}')
|
44
|
+
|
45
|
+
ans.must_be_kind_of Response::Ok
|
46
|
+
end
|
47
|
+
|
48
|
+
it "must parse a suggestion" do
|
49
|
+
txt = '{"typename":"suggestion","token":"871ec6f8-bea7-4a51-ba48-33bdfea9feb2","choice":"variant2"}'
|
50
|
+
ans = Response.parse(txt)
|
51
|
+
|
52
|
+
ans.must_be_kind_of Response::Suggestion
|
53
|
+
ans.token.must_equal '871ec6f8-bea7-4a51-ba48-33bdfea9feb2'
|
54
|
+
ans.choice.must_equal 'variant2'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "must parse a uuid" do
|
58
|
+
ans = Response.parse('{"typename":"uuid","uuid":"f8d9eb79-3d8f-41b0-9044-605faddd959c"}')
|
59
|
+
|
60
|
+
ans.must_be_kind_of Response::Uuid
|
61
|
+
ans.uuid.must_equal 'f8d9eb79-3d8f-41b0-9044-605faddd959c'
|
62
|
+
end
|
63
|
+
|
64
|
+
it "must parse an experiment" do
|
65
|
+
txt = '{"typename":"experiment",
|
66
|
+
"name":"test",
|
67
|
+
"uuid":"45923780-80ed-47c6-aa46-15e2ae7a0e8c",
|
68
|
+
"accountId":"eb1fb383-4172-422a-b680-8031cf26a23e",
|
69
|
+
"variants":[
|
70
|
+
{"typename":"variant",
|
71
|
+
"name":"variant1",
|
72
|
+
"views":36,
|
73
|
+
"totalReward":1.0,
|
74
|
+
"confidenceBound":0.252904521564191},
|
75
|
+
{"typename":"variant",
|
76
|
+
"name":"variant2",
|
77
|
+
"views":9672,
|
78
|
+
"totalReward":0.0,
|
79
|
+
"confidenceBound":0.015429423531830728}]}'
|
80
|
+
ans = Response.parse(txt)
|
81
|
+
|
82
|
+
ans.must_be_kind_of Response::Experiment
|
83
|
+
ans.name.must_equal "test"
|
84
|
+
ans.uuid.must_equal "45923780-80ed-47c6-aa46-15e2ae7a0e8c"
|
85
|
+
ans.accountId.must_equal "eb1fb383-4172-422a-b680-8031cf26a23e"
|
86
|
+
|
87
|
+
v0 = ans.variants[0]
|
88
|
+
v1 = ans.variants[1]
|
89
|
+
|
90
|
+
v0.name.must_equal "variant1"
|
91
|
+
v0.views.must_equal 36
|
92
|
+
v0.totalReward.must_equal 1.0
|
93
|
+
v0.confidenceBound.must_equal 0.252904521564191
|
94
|
+
|
95
|
+
v1.name.must_equal "variant2"
|
96
|
+
v1.views.must_equal 9672
|
97
|
+
v1.totalReward.must_equal 0.0
|
98
|
+
v1.confidenceBound.must_equal 0.015429423531830728
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Copyright 2011 by Untyped Ltd. All Rights Reserved\
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# No portion of this Software shall be used in any application which does not
|
14
|
+
# use the ReportGrid platform to provide some subset of its functionality.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'minitest/autorun'
|
26
|
+
require 'myna'
|
27
|
+
|
28
|
+
describe Myna do
|
29
|
+
|
30
|
+
before do
|
31
|
+
Myna.run.get
|
32
|
+
end
|
33
|
+
|
34
|
+
after do
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "suggest" do
|
38
|
+
it "must return a suggestion for a valid experiment" do
|
39
|
+
expt = Myna::Experiment.new('45923780-80ed-47c6-aa46-15e2ae7a0e8c')
|
40
|
+
ans = expt.suggest
|
41
|
+
ans.get.must_be_kind_of Response::Suggestion
|
42
|
+
end
|
43
|
+
|
44
|
+
it "must return an error for an invalid experiment" do
|
45
|
+
expt = Myna::Experiment.new('bogus')
|
46
|
+
ans = expt.suggest
|
47
|
+
ans.get.must_be_kind_of Response::ApiError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "reward" do
|
52
|
+
it "must succeed when given correct token and amount" do
|
53
|
+
expt = Myna::Experiment.new('45923780-80ed-47c6-aa46-15e2ae7a0e8c')
|
54
|
+
suggestion = expt.suggest.get
|
55
|
+
|
56
|
+
ans = expt.reward(suggestion.token, 0.5)
|
57
|
+
ans.get
|
58
|
+
ans.get.must_be_kind_of Response::Ok
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: myna_eventmachine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Noel Welsh
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-11 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! 'An EventMachine based client for '
|
15
|
+
email:
|
16
|
+
- noel [at] untyped [dot] com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- Gemfile
|
22
|
+
- Gemfile.lock
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- lib/myna/future.rb
|
26
|
+
- lib/myna/myna.rb
|
27
|
+
- lib/myna/response.rb
|
28
|
+
- myna_eventmachine.gemspec
|
29
|
+
- test/test_authorized.rb
|
30
|
+
- test/test_future.rb
|
31
|
+
- test/test_response.rb
|
32
|
+
- test/test_suggest.rb
|
33
|
+
homepage: http://www.mynaweb.com/
|
34
|
+
licenses: []
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.8.10
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Ruby/EventMachine client library for Myna (http://www.mynaweb.com)
|
57
|
+
test_files:
|
58
|
+
- test/test_authorized.rb
|
59
|
+
- test/test_future.rb
|
60
|
+
- test/test_response.rb
|
61
|
+
- test/test_suggest.rb
|