myna_eventmachine 1.0.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.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'eventmachine'
4
+ gem 'em-http-request'
5
+ gem 'json'
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
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+ require 'bundler'
4
+
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ task :default => [:test]
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.test_files = FileList['test/test*.rb']
11
+ t.verbose = false
12
+ end
@@ -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
@@ -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