myna_eventmachine 1.0.0 → 1.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.
- data/README.md +38 -4
- data/lib/myna/future.rb +52 -12
- data/lib/myna/myna.rb +10 -6
- data/lib/myna/version.rb +27 -0
- data/lib/myna.rb +26 -0
- data/test/test_future.rb +10 -2
- data/test/test_suggest.rb +3 -3
- metadata +3 -1
data/README.md
CHANGED
@@ -1,16 +1,50 @@
|
|
1
1
|
# Myna Ruby/EventMachine Client
|
2
2
|
|
3
|
-
This is a Ruby client for the v1 Myna API, using EventMachine for asychronous goodness.
|
3
|
+
This is a Ruby client for the v1 [Myna](http://mynaweb.com) API, using EventMachine for asychronous goodness. Tested in Ruby 1.9.2
|
4
4
|
|
5
|
-
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
`gem install myna_eventmachine`
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
You can get a suggestion from Myna without authorizing:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
expt = Myna.experiment('45923780-80ed-47c6-aa46-15e2ae7a0e8c')
|
15
|
+
suggestion = expt.suggest.get
|
16
|
+
# suggestion has two attributes: choice and token
|
17
|
+
# suggestion.choice is a string, the choice made by Myna
|
18
|
+
# suggestion.token is a string, the token you send back to Myna when you reward
|
19
|
+
puts("Choice is #{suggestion.choice}")
|
20
|
+
expt.reward(suggestion.token, 1.0)
|
21
|
+
```
|
22
|
+
|
23
|
+
To create an experiment, add and delete variants, and so on, you must authorize first:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
myna = Myna.authorize('email', 'password')
|
27
|
+
# Create an experiment
|
28
|
+
expt = myna.create('My funky new experiment').get
|
29
|
+
expt.create_variant('My new variant')
|
30
|
+
expt.create_variant('My other new variant')
|
31
|
+
```
|
32
|
+
|
33
|
+
For more detail, see [the wiki](https://github.com/myna/ruby-eventmachine-client/wiki)
|
6
34
|
|
7
35
|
## Development Notes
|
8
36
|
|
9
37
|
The easiest way to install the dependencies is to install [Bundler](http://gembundler.com/) and then run `bundle install`
|
10
38
|
|
11
|
-
|
39
|
+
Rake commands:
|
40
|
+
|
41
|
+
- `test` to run the tests.
|
42
|
+
- `build` to build a Gem
|
43
|
+
- `install` to install the Gem locally
|
44
|
+
- `release` to push the Gem to RubyGems
|
12
45
|
|
13
46
|
|
14
47
|
## TODO
|
15
48
|
|
16
|
-
Futures need to handle failures, and need to rigourously trap exceptions and propagate them
|
49
|
+
- Futures need to handle failures, and need to rigourously trap exceptions and propagate them.
|
50
|
+
- Some RDoc or equivalent might be useful
|
data/lib/myna/future.rb
CHANGED
@@ -26,45 +26,83 @@ require 'thread'
|
|
26
26
|
|
27
27
|
|
28
28
|
module Future
|
29
|
+
|
30
|
+
def Future.run
|
31
|
+
future = Future.new
|
32
|
+
future.run(yield)
|
33
|
+
future
|
34
|
+
end
|
35
|
+
|
29
36
|
class Future
|
30
37
|
def initialize()
|
31
38
|
@lock = Mutex.new
|
32
39
|
@signal = ConditionVariable.new
|
33
40
|
@value = nil
|
34
|
-
@
|
41
|
+
@result = false # false, :success, or :failure
|
35
42
|
@listeners = []
|
36
43
|
end
|
37
44
|
|
38
|
-
def
|
45
|
+
def set(value, result)
|
39
46
|
@lock.synchronize {
|
40
|
-
if @
|
41
|
-
raise ArgumentError, "This future has already been
|
47
|
+
if @result
|
48
|
+
raise ArgumentError, "This future has already been completed with a value"
|
42
49
|
else
|
43
50
|
@value = value
|
44
|
-
@
|
51
|
+
@result = result
|
45
52
|
@signal.broadcast
|
46
53
|
end
|
47
54
|
}
|
48
|
-
|
49
|
-
|
55
|
+
|
56
|
+
if result == :success
|
57
|
+
@listeners.each do |block|
|
58
|
+
block.call(value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def succeed(value)
|
64
|
+
self.set(value, :success)
|
65
|
+
end
|
66
|
+
|
67
|
+
def fail(value)
|
68
|
+
self.set(value, :failure)
|
69
|
+
end
|
70
|
+
|
71
|
+
def run
|
72
|
+
begin
|
73
|
+
self.succeed(yield)
|
74
|
+
rescue => exn
|
75
|
+
self.fail(exn)
|
50
76
|
end
|
51
77
|
end
|
52
78
|
|
53
79
|
def get
|
80
|
+
def handle_value
|
81
|
+
case @result
|
82
|
+
when :success
|
83
|
+
@value
|
84
|
+
when :failure
|
85
|
+
raise @value
|
86
|
+
else
|
87
|
+
raise ArgumentError, "Attempted to handle_value when result #{@result} is not :success or :failure"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
54
91
|
@lock.synchronize {
|
55
|
-
if
|
92
|
+
if @result
|
93
|
+
handle_value
|
94
|
+
else
|
56
95
|
@signal.wait(@lock)
|
57
96
|
@signal.broadcast
|
97
|
+
handle_value
|
58
98
|
end
|
59
|
-
|
60
|
-
@value
|
61
99
|
}
|
62
100
|
end
|
63
101
|
|
64
102
|
def on_complete(&block)
|
65
103
|
return_value = false
|
66
104
|
@lock.synchronize {
|
67
|
-
if @
|
105
|
+
if @result == :success
|
68
106
|
return_value = true
|
69
107
|
else
|
70
108
|
@listeners.push(block)
|
@@ -82,7 +120,9 @@ module Future
|
|
82
120
|
def map(&block)
|
83
121
|
future = Future.new
|
84
122
|
self.on_complete do |value|
|
85
|
-
future.
|
123
|
+
future.run do
|
124
|
+
block.call(value)
|
125
|
+
end
|
86
126
|
end
|
87
127
|
future
|
88
128
|
end
|
data/lib/myna/myna.rb
CHANGED
@@ -35,14 +35,14 @@ module Myna
|
|
35
35
|
future = Future::Future.new
|
36
36
|
|
37
37
|
if EventMachine::reactor_running?
|
38
|
-
future.
|
38
|
+
future.succeed(true)
|
39
39
|
else
|
40
40
|
Thread.new do
|
41
41
|
EventMachine::run {}
|
42
42
|
end
|
43
43
|
|
44
44
|
EventMachine::next_tick do
|
45
|
-
future.
|
45
|
+
future.succeed(true)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -78,8 +78,8 @@ module Myna
|
|
78
78
|
client = EventMachine::HttpRequest.new(uri)
|
79
79
|
|
80
80
|
http = yield(client)
|
81
|
-
http.errback { future.
|
82
|
-
http.callback { future.
|
81
|
+
http.errback { future.succeed(Response::NetworkError.new) }
|
82
|
+
http.callback { future.succeed(Response.parse(http.response)) }
|
83
83
|
end
|
84
84
|
|
85
85
|
future
|
@@ -118,6 +118,10 @@ module Myna
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
121
|
+
def Myna.experiment(uuid)
|
122
|
+
Myna::Experiment.new(uuid)
|
123
|
+
end
|
124
|
+
|
121
125
|
# The simple API for unauthorized access
|
122
126
|
class Experiment
|
123
127
|
def initialize(uuid, host = UnauthorizedHost)
|
@@ -143,11 +147,11 @@ module Myna
|
|
143
147
|
end
|
144
148
|
|
145
149
|
def Myna.authorize(email, password, host = AuthorizedHost)
|
146
|
-
|
150
|
+
AuthorizedMyna.new(email, password, host)
|
147
151
|
end
|
148
152
|
|
149
153
|
# The more complex API for authorized access
|
150
|
-
class
|
154
|
+
class AuthorizedMyna
|
151
155
|
def initialize(email, password, host = AuthorizedHost)
|
152
156
|
@email = email
|
153
157
|
@password = password
|
data/lib/myna/version.rb
ADDED
@@ -0,0 +1,27 @@
|
|
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
|
+
module Myna
|
26
|
+
VERSION = "1.1.0"
|
27
|
+
end
|
data/lib/myna.rb
ADDED
@@ -0,0 +1,26 @@
|
|
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 'myna/myna'
|
26
|
+
require 'myna/response'
|
data/test/test_future.rb
CHANGED
@@ -33,7 +33,7 @@ describe Future do
|
|
33
33
|
v + 3
|
34
34
|
end
|
35
35
|
|
36
|
-
f1.
|
36
|
+
f1.succeed(1)
|
37
37
|
f2.get.must_equal 4
|
38
38
|
end
|
39
39
|
|
@@ -45,10 +45,18 @@ describe Future do
|
|
45
45
|
|
46
46
|
Thread.new do
|
47
47
|
sleep(1)
|
48
|
-
f1.
|
48
|
+
f1.succeed(1)
|
49
49
|
end
|
50
50
|
|
51
51
|
f2.get.must_equal 4
|
52
52
|
end
|
53
53
|
end
|
54
|
+
|
55
|
+
describe "Future.fail" do
|
56
|
+
it "must cause an exception to be raised on get" do
|
57
|
+
# Not implemented
|
58
|
+
# Also test Future.run and future.run
|
59
|
+
true.must_equal false
|
60
|
+
end
|
61
|
+
end
|
54
62
|
end
|
data/test/test_suggest.rb
CHANGED
@@ -36,13 +36,13 @@ describe Myna do
|
|
36
36
|
|
37
37
|
describe "suggest" do
|
38
38
|
it "must return a suggestion for a valid experiment" do
|
39
|
-
expt = Myna
|
39
|
+
expt = Myna.experiment('45923780-80ed-47c6-aa46-15e2ae7a0e8c')
|
40
40
|
ans = expt.suggest
|
41
41
|
ans.get.must_be_kind_of Response::Suggestion
|
42
42
|
end
|
43
43
|
|
44
44
|
it "must return an error for an invalid experiment" do
|
45
|
-
expt = Myna
|
45
|
+
expt = Myna.experiment('bogus')
|
46
46
|
ans = expt.suggest
|
47
47
|
ans.get.must_be_kind_of Response::ApiError
|
48
48
|
end
|
@@ -50,7 +50,7 @@ describe Myna do
|
|
50
50
|
|
51
51
|
describe "reward" do
|
52
52
|
it "must succeed when given correct token and amount" do
|
53
|
-
expt = Myna
|
53
|
+
expt = Myna.experiment('45923780-80ed-47c6-aa46-15e2ae7a0e8c')
|
54
54
|
suggestion = expt.suggest.get
|
55
55
|
|
56
56
|
ans = expt.reward(suggestion.token, 0.5)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: myna_eventmachine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -22,9 +22,11 @@ files:
|
|
22
22
|
- Gemfile.lock
|
23
23
|
- README.md
|
24
24
|
- Rakefile
|
25
|
+
- lib/myna.rb
|
25
26
|
- lib/myna/future.rb
|
26
27
|
- lib/myna/myna.rb
|
27
28
|
- lib/myna/response.rb
|
29
|
+
- lib/myna/version.rb
|
28
30
|
- myna_eventmachine.gemspec
|
29
31
|
- test/test_authorized.rb
|
30
32
|
- test/test_future.rb
|