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 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
- Tested in Ruby 1.9.2
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
- Run `rake test` to run the tests.
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
- @delivered = false
41
+ @result = false # false, :success, or :failure
35
42
  @listeners = []
36
43
  end
37
44
 
38
- def deliver(value)
45
+ def set(value, result)
39
46
  @lock.synchronize {
40
- if @delivered
41
- raise ArgumentError, "This future has already been delivered a value"
47
+ if @result
48
+ raise ArgumentError, "This future has already been completed with a value"
42
49
  else
43
50
  @value = value
44
- @delivered = true
51
+ @result = result
45
52
  @signal.broadcast
46
53
  end
47
54
  }
48
- @listeners.each do |block|
49
- block.call(value)
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 !@delivered
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 @delivered
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.deliver(block.call(value))
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.deliver(true)
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.deliver(true)
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.deliver(Response::NetworkError.new) }
82
- http.callback { future.deliver(Response.parse(http.response)) }
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
- ExperimentFactory.new(email, password, host)
150
+ AuthorizedMyna.new(email, password, host)
147
151
  end
148
152
 
149
153
  # The more complex API for authorized access
150
- class ExperimentFactory
154
+ class AuthorizedMyna
151
155
  def initialize(email, password, host = AuthorizedHost)
152
156
  @email = email
153
157
  @password = password
@@ -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.deliver(1)
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.deliver(1)
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::Experiment.new('45923780-80ed-47c6-aa46-15e2ae7a0e8c')
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::Experiment.new('bogus')
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::Experiment.new('45923780-80ed-47c6-aa46-15e2ae7a0e8c')
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.0.0
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