myna_eventmachine 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|