commute 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +7 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +262 -0
- data/Rakefile +10 -0
- data/commute.gemspec +29 -0
- data/lib/commute/adapters/typhoeus.rb +13 -0
- data/lib/commute/api.rb +86 -0
- data/lib/commute/context.rb +154 -0
- data/lib/commute/layer.rb +16 -0
- data/lib/commute/stack.rb +104 -0
- data/lib/commute/version.rb +3 -0
- data/lib/commute.rb +7 -0
- data/spec/commute/api_spec.rb +97 -0
- data/spec/commute/context_spec.rb +140 -0
- data/spec/commute/layer_spec.rb +22 -0
- data/spec/commute/stack_spec.rb +125 -0
- data/spec/spec_helper.rb +14 -0
- metadata +217 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Mattias Putman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
# Commute
|
2
|
+
[![Build Status](https://secure.travis-ci.org/challengee/commute.png)](http://travis-ci.org/challengee/commute)
|
3
|
+
[![Dependency Status](https://gemnasium.com/challengee/commute.png?travis)](https://gemnasium.com/challengee/commute)
|
4
|
+
|
5
|
+
Commute helps you to:
|
6
|
+
|
7
|
+
* Dynamically build HTTP requests for your favorite HTTP library (currently only Typhoeus).
|
8
|
+
* Easily process request and response bodies.
|
9
|
+
* Execute parallel HTTP requests.
|
10
|
+
|
11
|
+
## Contexts and Api's
|
12
|
+
|
13
|
+
Almost everything in Commute is a context. Contexts represent all the information that Commute needs to build, execute and process your requests. This includes:
|
14
|
+
|
15
|
+
* **Request options**: url, body, url params, headers, ....
|
16
|
+
* **Custom options**: custom named parameters that can be translated in request options.
|
17
|
+
* **Stack options**: used to provide information on how request/response bodies are processed.
|
18
|
+
* **A Stack**: A sequence of layers that a request/response body is pushed through to be processed (these layers user the stack options).
|
19
|
+
* A reference to an **Api** that is responsible for executing the request.
|
20
|
+
|
21
|
+
Api's are nothing more than a set of predefined, named contexts. If you think about it, options and stack layers needed for certain Api calls can be seen as an extension as a context. So these named contexts (aka Api calls) take a context and extend it with the needed information.
|
22
|
+
|
23
|
+
## Stacks
|
24
|
+
|
25
|
+
Stacks are responsible for processing request and response bodies. A stack consists of a request and response `sequence`. The response sequence is generally the inverse of the request sequence.
|
26
|
+
|
27
|
+
A sequence consists of an ordered list of layers. Each layer provides some logic to transform a body according to some options. For example a chemicals layer could parse or render an xml document based on a given template. This would look like this:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class Chemicals < Layer
|
31
|
+
def request body, template
|
32
|
+
template.render body
|
33
|
+
end
|
34
|
+
|
35
|
+
def response body, template
|
36
|
+
template.parse body
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
When a context is built into a request. The body is ran through the request sequence. When the request is completed, the response body is ran through the response sequence.
|
42
|
+
|
43
|
+
## Building Contexts
|
44
|
+
|
45
|
+
### Extending Contexts 101
|
46
|
+
|
47
|
+
Commute is all about building partial contexts and extending them until enough information is present to build a real HTTP request. Basically it works like this:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# Assume there is an Api "GistApi".
|
51
|
+
# This would create a context containing a custom option 'user'
|
52
|
+
# and then one with a context-type header.
|
53
|
+
defunkt_api = GistApi.with(user: 'defunkt')
|
54
|
+
defunkt_xml_api = defunkt_api.with(headers: {
|
55
|
+
'Content-Type' => 'application/xml'
|
56
|
+
})
|
57
|
+
```
|
58
|
+
|
59
|
+
Any further context extensions or api calls on `defunkt_xml_api` would thus be in xml and for the user 'defunkt'.
|
60
|
+
|
61
|
+
For example, you could take an vanilla api, extend it to a context that contains needed authentication parameters, and pass that context through your app acting as an api that does not need authentication. Here lies the power of commute: A context will act as an api with some predefined options and behavior.
|
62
|
+
|
63
|
+
### Default and raw contexts
|
64
|
+
|
65
|
+
Without you knowing, `GistApi.with` extended a special context of the `GistApi` called the `default` context. It contains options that every requests for this api will be needing. When you don't want this you can use the `raw` context.
|
66
|
+
|
67
|
+
Example:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class GistApi < Commute::Api
|
71
|
+
def default
|
72
|
+
# Extends the raw context with some options.
|
73
|
+
# Every request/response will be json.
|
74
|
+
raw.with headers: {
|
75
|
+
'Content-Type' => 'application/json',
|
76
|
+
'Accept' => 'application/json'
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
defunkt = GistApi.with(user: 'defunkt')
|
82
|
+
defunkt.options.inspect
|
83
|
+
# => { user: 'defunkt', headers: {
|
84
|
+
'Content-Type' => 'application/json',
|
85
|
+
'Accept' => 'application/json'
|
86
|
+
}}
|
87
|
+
```
|
88
|
+
|
89
|
+
### Making basic Api calls
|
90
|
+
|
91
|
+
Let's say we want to create an Api call that fetches all gists for a certain user. This call would extend the context by using the custom `:user` option and putting it in the url.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
class GistApi < Commute::Api
|
95
|
+
def default
|
96
|
+
# Extends the raw context with some options.
|
97
|
+
# Every request/response will be json.
|
98
|
+
raw.with headers: {
|
99
|
+
'Content-Type' => 'application/json',
|
100
|
+
'Accept' => 'application/json'
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def for_user context
|
105
|
+
context.with url: "https://api.github.com/users/#{context[:user]}/gists"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# A context for getting all gists from defunkt.
|
110
|
+
gists = GistApi.for_user user: 'defunkt'
|
111
|
+
|
112
|
+
# or, you can play with it. See the potential?
|
113
|
+
gists = GistApi.with(user: 'defunkt').for_user
|
114
|
+
```
|
115
|
+
|
116
|
+
Internally, those last two methods of creating the gists context are the same. When calling an api method from a context, passed arguments are used to create a context to be passed to the api call. That is why the argument of an api call is always `context`. The first method is only a convenient shortcut.
|
117
|
+
|
118
|
+
### A basic stack
|
119
|
+
|
120
|
+
If we would want to parse and encode json for every request and response we could create a layer like this:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
class Json < Commute::Layer
|
124
|
+
# Enode on request.
|
125
|
+
def request body, options
|
126
|
+
Yajl::Encoder.encode body, options
|
127
|
+
end
|
128
|
+
|
129
|
+
# Decode on response.
|
130
|
+
def response body, options
|
131
|
+
Yajl::Parser.parse body, options
|
132
|
+
end
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
And use it in our default context:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
def default
|
140
|
+
# Extends the raw context with some options.
|
141
|
+
# Every request/response will be json.
|
142
|
+
raw.with headers: {
|
143
|
+
'Content-Type' => 'application/json',
|
144
|
+
'Accept' => 'application/json'
|
145
|
+
} do |stack|
|
146
|
+
stack << Json.new(:format)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
When adding a layer to a stack, you can give it a name. This can then later be used in stack altering or as a reference to pass options to the stack.
|
152
|
+
|
153
|
+
### Extending Contexts 201
|
154
|
+
|
155
|
+
We only showed simple context extending using some extra parameters. As said, contexts also define behavior (How does a request/response body have to be processed?) using stacks.
|
156
|
+
|
157
|
+
On extending a context, the stack can be altered.
|
158
|
+
|
159
|
+
Lat's say that in the basic stack, we want to send raw json, but we still want the stack to automatically parse json for the response. We could create a new context that does this:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
raw_requests = GistApi.with { |stack|
|
163
|
+
# Dump the format layer in the request sequence.
|
164
|
+
stack.request.without! :format
|
165
|
+
}
|
166
|
+
```
|
167
|
+
|
168
|
+
Other stack altering methods are:
|
169
|
+
|
170
|
+
* `before!`: inserts a layer before a certain named layer.
|
171
|
+
* `after!`: inserts a layer after a certain named layer.
|
172
|
+
|
173
|
+
### Passing options to the stack
|
174
|
+
|
175
|
+
Naming layers has the advantage that they can be used to pass options to the layer. In the case of the `Json` layer, we could pass options to `yajl`:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
symbolized_api = GistApi.with(format: {
|
179
|
+
symbolize_keys: true
|
180
|
+
})
|
181
|
+
|
182
|
+
# Now all further extensions will return bodies with symbolized keys.
|
183
|
+
```
|
184
|
+
|
185
|
+
### Authorization
|
186
|
+
|
187
|
+
A authorization mechanism can be implemented for an api by implementing the `authorize(context)` method. This does not follow the standard context system because Authorization headers can be time sensitive. The Authorization header is computed just before the requests is actually fired.
|
188
|
+
|
189
|
+
### Hooks
|
190
|
+
|
191
|
+
Commute provides a before (default) and after hook. They can be used to set up default context options/behavior or finishing up a context before it can be built.
|
192
|
+
|
193
|
+
We already showed how to provide `default` contexts. The after hook can be provided by implementing `after(context)`.
|
194
|
+
|
195
|
+
## Building requests and executing them.
|
196
|
+
|
197
|
+
We now know how to build a context, it's time to turn those contexts into requests and firing them onto the internets.
|
198
|
+
|
199
|
+
### Building requests.
|
200
|
+
|
201
|
+
When you build a request from a context, you receive a pure `Typhoeus::Request`. You can do with it what you want, the context system just acts as a builder solution. You can pass a block to the build method that gets called when the request completes. This block gets called with:
|
202
|
+
|
203
|
+
* The pure `Typhoeus::Response` as it was received from typhoeus.
|
204
|
+
* A transformed response body (went through the stack).
|
205
|
+
|
206
|
+
For example:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
request = GistApi.for_user(user: 'defunkt').build do |response, gists|
|
210
|
+
puts gists.count
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
### Executing requests
|
215
|
+
|
216
|
+
Executing requests is done through the `rush` method on a context. It executes the given request, runs the response through the stack and call the callback block.
|
217
|
+
|
218
|
+
Building on the previous example that would go like this:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
GistApi.rush request
|
222
|
+
# => 10
|
223
|
+
```
|
224
|
+
|
225
|
+
Remember that `GistApi.rush` calls the rush method on the `default` context. It would be the same as calling:
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
GistApi.default.rush request
|
229
|
+
```
|
230
|
+
|
231
|
+
The `rush` method can be called on every context, it will be router to the underlaying Api.
|
232
|
+
|
233
|
+
### Queueing requests for parallel execution
|
234
|
+
|
235
|
+
Using the `commute` method you can queue requests for later execution. It simply works like this (Let's add some coolness for fun):
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
# Define a callback.
|
239
|
+
callback = Proc.new { |response, gists|
|
240
|
+
puts gists.count
|
241
|
+
}
|
242
|
+
# Build and queue some requests.
|
243
|
+
['defunkt', 'challengee'].map { |user|
|
244
|
+
GistApi.commute GistApi.for_user(user: user).build
|
245
|
+
}
|
246
|
+
# Execute all requests in parallel
|
247
|
+
GistApi.rush
|
248
|
+
# => 10
|
249
|
+
# => 0
|
250
|
+
```
|
251
|
+
|
252
|
+
### Error handling
|
253
|
+
|
254
|
+
Commute does not provide any help with handling errors. This is done purely through the underlaying HTTP client that is used (here Typhoeus).
|
255
|
+
|
256
|
+
In the callback one can for example check if the requests has timed out by issueing `response.timed_out?`.
|
257
|
+
|
258
|
+
## What's next?
|
259
|
+
|
260
|
+
* I would like an "adapter" system so that `Typhoeus` would just be an adapter.
|
261
|
+
* Adding support for `em-http-request`.
|
262
|
+
* Caching support (As a special adapter?).
|
data/Rakefile
ADDED
data/commute.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/commute/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.authors = ["Mattias Putman"]
|
6
|
+
s.email = ["mattias.putman@gmail.com"]
|
7
|
+
s.description = %q{Handling the HTTP commute like a boss}
|
8
|
+
s.summary = %q{Commute helps you to: 1) Dynamically build HTTP requests for your favorite HTTP library (currently only Typhoeus). 2) Easily process request and response bodies. 3) Execute parallel HTTP requests.}
|
9
|
+
s.homepage = "http://challengee.github.com/commute/"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split($\)
|
12
|
+
s.test_files = s.files.grep(%r{^(spec)/})
|
13
|
+
s.name = "commute"
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
s.version = Commute::VERSION
|
16
|
+
|
17
|
+
s.add_dependency 'typhoeus'
|
18
|
+
|
19
|
+
s.add_development_dependency 'rake'
|
20
|
+
s.add_development_dependency 'mocha'
|
21
|
+
s.add_development_dependency 'guard'
|
22
|
+
s.add_development_dependency 'guard-minitest'
|
23
|
+
s.add_development_dependency 'yard'
|
24
|
+
s.add_development_dependency 'simplecov'
|
25
|
+
s.add_development_dependency 'webmock'
|
26
|
+
|
27
|
+
# For the examples.
|
28
|
+
s.add_development_dependency 'yajl-ruby'
|
29
|
+
end
|
data/lib/commute/api.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
require 'typhoeus'
|
5
|
+
|
6
|
+
require 'commute/context'
|
7
|
+
|
8
|
+
module Commute
|
9
|
+
|
10
|
+
# An Api holds:
|
11
|
+
# * Contexts of defaults.
|
12
|
+
# * Named contexts (= Api calls)
|
13
|
+
#
|
14
|
+
# Every Api is a singleton, it contains no state, only some name configurations (contexts).
|
15
|
+
class Api
|
16
|
+
include Singleton
|
17
|
+
|
18
|
+
# @!attribute queue
|
19
|
+
# @return List of requests waiting for execution.
|
20
|
+
attr_reader :queue
|
21
|
+
|
22
|
+
class << self
|
23
|
+
extend Forwardable
|
24
|
+
|
25
|
+
def_delegators :instance, :default, :raw, :with
|
26
|
+
|
27
|
+
# Call missing methods on the default context.
|
28
|
+
def method_missing name, *args, &block
|
29
|
+
default.send name, *args, &block
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Initializes an Api with a parallel manager.
|
34
|
+
def initialize
|
35
|
+
@queue = []
|
36
|
+
@hydra = ::Typhoeus::Hydra.new
|
37
|
+
end
|
38
|
+
|
39
|
+
# A pretty standard starting point is the `default` context.
|
40
|
+
# An Api class can implement it to provide some default options and stack layers.
|
41
|
+
#
|
42
|
+
# @return [Context] A default context.
|
43
|
+
def default
|
44
|
+
@default ||= raw
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get a raw context without any defaults.
|
48
|
+
#
|
49
|
+
# @return [Context] A raw context for this api.
|
50
|
+
def raw
|
51
|
+
@raw ||= Context.new self, {}, Stack.new
|
52
|
+
end
|
53
|
+
|
54
|
+
# Start scoping on this Api.
|
55
|
+
# Creates a context with provided options and stack.
|
56
|
+
#
|
57
|
+
# @return [Context] The created context.
|
58
|
+
def with options = {}, &stack_mod
|
59
|
+
default.with options, &stack_mod
|
60
|
+
end
|
61
|
+
|
62
|
+
# Queue a request for later parallel execution.
|
63
|
+
#
|
64
|
+
# @param request [Typhoeus::Request] The request to queue.
|
65
|
+
def commute request
|
66
|
+
@queue << request
|
67
|
+
end
|
68
|
+
|
69
|
+
# Executes all requests in the queue in parallel.
|
70
|
+
#
|
71
|
+
# @param request [Typhoeus::Request] Last request to add to the queue.
|
72
|
+
# Shortcut to commute and rush one request in one line.
|
73
|
+
def rush request = nil
|
74
|
+
commute request if request
|
75
|
+
# Authorize each request right before executing
|
76
|
+
@queue.each do |request|
|
77
|
+
request.headers['Authorization'] = authorize(request.context) if respond_to? :authorize
|
78
|
+
@hydra.queue request
|
79
|
+
end
|
80
|
+
# Clear the queue.
|
81
|
+
@queue.clear
|
82
|
+
# Run all requests.
|
83
|
+
@hydra.run
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'commute/stack'
|
4
|
+
require 'commute/adapters/typhoeus'
|
5
|
+
|
6
|
+
module Commute
|
7
|
+
|
8
|
+
# A context represents all what can better define a HTTP request/response trip. This includes:
|
9
|
+
# * An url.
|
10
|
+
# * A partial request/response stack that defines request/response body transformations.
|
11
|
+
# * Partial options for the request, the stack or behavior of certain {Api} calls.
|
12
|
+
#
|
13
|
+
# Contexts work incrementally to provide a full context (all required options and behaviour)
|
14
|
+
# for a HTTP request/response trip.
|
15
|
+
#
|
16
|
+
# Every Context has notion of an underlying api to easily make requests.
|
17
|
+
#
|
18
|
+
# Contexts can be extended through {#with}. This way you can add options/override options,
|
19
|
+
# alter the stack or specify an url.
|
20
|
+
#
|
21
|
+
class Context
|
22
|
+
extend Forwardable
|
23
|
+
|
24
|
+
# @!attribute options
|
25
|
+
# @return The options of the context.
|
26
|
+
attr_accessor :options
|
27
|
+
|
28
|
+
# @!attribute stack
|
29
|
+
# @return The stack of the context.
|
30
|
+
attr_accessor :stack
|
31
|
+
|
32
|
+
# Create a new Context.
|
33
|
+
#
|
34
|
+
# @param api [Api] The underlying api.
|
35
|
+
# @param options [Hash] all kinds of options (for the request, api call and stack layers).
|
36
|
+
# @param stack [Stack] The request/response stack.
|
37
|
+
def initialize api, options, stack
|
38
|
+
@api = api
|
39
|
+
@options = options
|
40
|
+
@stack = stack
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!method [](key)
|
44
|
+
# @param key [Symbol] The key to get from the options.
|
45
|
+
# @return [Object] The value associated with that key.
|
46
|
+
def_delegator :@options, :[], :[]
|
47
|
+
|
48
|
+
# @!method commute(request)
|
49
|
+
# Queues a request for later parallel execution.
|
50
|
+
# @param request [Typhoeus::Request] The request to queue for later execution.
|
51
|
+
def_delegator :@api, :commute, :commute
|
52
|
+
|
53
|
+
# @!method rush
|
54
|
+
# Executes all the requests in the queue of the api.
|
55
|
+
def_delegator :@api, :rush, :rush
|
56
|
+
|
57
|
+
# Create a new context from this context by providing new options and stack modifications.
|
58
|
+
#
|
59
|
+
# @param options [Hash] Options for the new {Context}.
|
60
|
+
#
|
61
|
+
# @yieldparam stack [Stack] The stack in this {Context}.
|
62
|
+
# @yieldreturn [Stack] The modified {Stack}.
|
63
|
+
def with options = {}
|
64
|
+
# Alter the stack if necessary
|
65
|
+
stack = @stack.clone
|
66
|
+
yield(stack) if block_given?
|
67
|
+
# Create a new context.
|
68
|
+
Context.new @api, mix(@options, options), stack
|
69
|
+
end
|
70
|
+
|
71
|
+
# Builds a Typhoeus::Request from the context.
|
72
|
+
#
|
73
|
+
# @todo This should work with some kind of Typhoeus adapter.
|
74
|
+
# @todo Actually the prepare method should only be called right before the request
|
75
|
+
# is sent. We do not take queueing into account here. Timestamps for authorization could
|
76
|
+
# expire. Can be solved by extending Typhoeus::Request with an UnpreparedRequest and
|
77
|
+
# preparing it right before it is sent.
|
78
|
+
#
|
79
|
+
# @yieldparam response [Typhoeus::Response] The response object as we got it from Typhoeus.
|
80
|
+
# @yieldparam result [Object] The result of the body after going through the {Stack}.
|
81
|
+
#
|
82
|
+
# @return [Typhoeus::Request] The built Typhoeus Request.
|
83
|
+
def build &callback
|
84
|
+
# Call after hook if necessary.
|
85
|
+
context = if @api.respond_to? :after
|
86
|
+
@api.after self
|
87
|
+
else
|
88
|
+
self
|
89
|
+
end
|
90
|
+
# Build the real context (with or without after hook).
|
91
|
+
context.build_without_after &callback
|
92
|
+
end
|
93
|
+
|
94
|
+
# Builds the context without calling the after hook.
|
95
|
+
#
|
96
|
+
# @private
|
97
|
+
def build_without_after &callback
|
98
|
+
# Run the request body through the stack.
|
99
|
+
body = self[:body]
|
100
|
+
raw_body = if body && body != ''
|
101
|
+
@stack.run_request body, options
|
102
|
+
else
|
103
|
+
body
|
104
|
+
end
|
105
|
+
# Build the Typhoeus Request.
|
106
|
+
options[:body] = raw_body
|
107
|
+
request = Typhoeus::ContextualRequest.new self, self[:url], options
|
108
|
+
# Attach an on_complete handler that wraps the callback.
|
109
|
+
request.on_complete do |response|
|
110
|
+
# Run the response body through the stack.
|
111
|
+
body = response.body
|
112
|
+
result = if body && body != '' && response.success?
|
113
|
+
@stack.run_response response.body, options
|
114
|
+
else
|
115
|
+
body
|
116
|
+
end
|
117
|
+
# Call the real commute callback.
|
118
|
+
callback.call response, result if callback
|
119
|
+
end
|
120
|
+
# Return the request.
|
121
|
+
request
|
122
|
+
end
|
123
|
+
|
124
|
+
# Any method missing should be a shortcut to an api call with the current context.
|
125
|
+
# The options and the block that are passed are used to create a new context.
|
126
|
+
# This context is then passed to the API call to return another new context.
|
127
|
+
def method_missing name, *args, &block
|
128
|
+
# Extract the options from the method arguments.
|
129
|
+
options = args.first || {}
|
130
|
+
# Create the new context.
|
131
|
+
context = with options, &block
|
132
|
+
# Call the Api. This returns a new context.
|
133
|
+
@api.send name, context
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# Merge a 2-level deep hash of options.
|
139
|
+
#
|
140
|
+
# @param options [Hash] The base options hash.
|
141
|
+
# @param override_options [Hash] The options hash considered as more important.
|
142
|
+
#
|
143
|
+
# @return [Hash] A mixed hash.
|
144
|
+
def mix options, override_options
|
145
|
+
options.merge(override_options) { |key, old_value, new_value|
|
146
|
+
if old_value.kind_of? Hash
|
147
|
+
old_value.merge new_value
|
148
|
+
else
|
149
|
+
new_value
|
150
|
+
end
|
151
|
+
}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Commute
|
2
|
+
|
3
|
+
# @todo more docs and tests.
|
4
|
+
class Layer < Struct.new(:name)
|
5
|
+
|
6
|
+
# Default request forwards to the call method.
|
7
|
+
def request request, options
|
8
|
+
call request, options if respond_to? :call
|
9
|
+
end
|
10
|
+
|
11
|
+
# Default response forwards to the call method.
|
12
|
+
def response response, options
|
13
|
+
call response, options if respond_to? :call
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
require 'commute/layer'
|
5
|
+
|
6
|
+
module Commute
|
7
|
+
|
8
|
+
# A {Stack} is a sequence of layers that define a transfornation of a request/response body.
|
9
|
+
#
|
10
|
+
# Each {Layer} has a optional name. Options of the {Layer} can be passed in the
|
11
|
+
# {Context}'s options under the the equally named key.
|
12
|
+
#
|
13
|
+
# A Stack can both process a request and a response body (that can be either object,
|
14
|
+
# as long as it conforms with the first {Layer}).
|
15
|
+
class Stack
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
# Represents one way of the {Stack}
|
19
|
+
# Delegates methods to the underlying layer array.
|
20
|
+
class Sequence < SimpleDelegator
|
21
|
+
|
22
|
+
# Remove some layers from this sequence.
|
23
|
+
#
|
24
|
+
# @param names [<Symbol>] The names of the layers that must be removed.
|
25
|
+
def without! *names
|
26
|
+
reject! { |layer| names.include? layer.name }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Insert a layer before another layer.
|
30
|
+
#
|
31
|
+
# @param reference [Symbol] The layer to insert before.
|
32
|
+
# @param insert [Layer] The layer to insert.
|
33
|
+
def before! reference, inserting
|
34
|
+
insert(index { |layer| layer.name == reference }, inserting)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Insert a layer after another layer.
|
38
|
+
#
|
39
|
+
# @param reference [Symbol] The layer to insert after.
|
40
|
+
# @param insert [Layer] The layer to insert.
|
41
|
+
def after! reference, insert
|
42
|
+
insert((index { |layer| layer.name == reference }) + 1, insert)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!attribute request
|
47
|
+
# The request {Sequence} of the stack.
|
48
|
+
attr_accessor :request
|
49
|
+
|
50
|
+
# @!attribute response
|
51
|
+
# The response {Sequence} of the stack.
|
52
|
+
attr_accessor :response
|
53
|
+
|
54
|
+
# Create a new {Stack} with a few layers.
|
55
|
+
def initialize *layers
|
56
|
+
@request = Sequence.new layers
|
57
|
+
@response = Sequence.new layers.reverse
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param layer [Layer] The layer to be added to the stack.
|
61
|
+
def << layer
|
62
|
+
@request << layer
|
63
|
+
@response.insert 0, layer
|
64
|
+
end
|
65
|
+
|
66
|
+
# Run a request body through the stack.
|
67
|
+
#
|
68
|
+
# @param body [Object] The request body.
|
69
|
+
# @param options [Hash] Options for the layers.
|
70
|
+
#
|
71
|
+
# @return [Object] the transformed body.
|
72
|
+
def run_request body, options = {}
|
73
|
+
@request.inject(body) do |body, layer|
|
74
|
+
# Run through layer to modfiy the request
|
75
|
+
layer.request body, options[layer.name] || {}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Run a response body through the stack.
|
80
|
+
# We want the response to have the inverse manipulations
|
81
|
+
# so we invert the stack.
|
82
|
+
#
|
83
|
+
# @param body [Object] The request body.
|
84
|
+
# @param options [Hash] Options for the layers.
|
85
|
+
#
|
86
|
+
# @return [Object] the transformed body.
|
87
|
+
def run_response body, options = {}
|
88
|
+
@response.inject(body) do |body, layer|
|
89
|
+
# Run through layer to modfiy the request
|
90
|
+
layer.response body, options[layer.name] || {}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Override the clone method to clone the layers.
|
95
|
+
#
|
96
|
+
# @return A duplicate of the stack.
|
97
|
+
def clone
|
98
|
+
stack = super
|
99
|
+
stack.request = stack.request.clone
|
100
|
+
stack.response = stack.response.clone
|
101
|
+
stack
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/commute.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commute::Api do
|
4
|
+
|
5
|
+
class TestApi < Commute::Api
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:default) { TestApi.with(user: 'mattias') }
|
9
|
+
|
10
|
+
let(:request1) { default.with(url: 'http://www.example.com', method: :get).build }
|
11
|
+
let(:request2) { default.with(url: 'http://www.example.com', method: :post).build }
|
12
|
+
let(:request3) { default.with(url: 'http://www.example.com', method: :delete).build }
|
13
|
+
|
14
|
+
after do
|
15
|
+
TestApi.instance.queue.clear
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#method_missing' do
|
19
|
+
before do
|
20
|
+
TestApi.stubs(:default).returns(default)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should forward calls to the singleton with the default context' do
|
24
|
+
TestApi.instance.expects(:get).with { |c|
|
25
|
+
c.options.must_equal user: 'mattias'
|
26
|
+
}
|
27
|
+
TestApi.get
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should take no default context if the raw context is used' do
|
31
|
+
TestApi.instance.expects(:get).with { |c|
|
32
|
+
c.options.must_equal id: 1
|
33
|
+
}
|
34
|
+
TestApi.raw.get id: 1
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should scope on the default context' do
|
38
|
+
TestApi.instance.expects(:get).with { |c|
|
39
|
+
c.options.must_equal user: 'mattias', id: 1
|
40
|
+
}
|
41
|
+
TestApi.get id: 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#commute' do
|
46
|
+
it 'should add requests to the queue' do
|
47
|
+
default.commute request1
|
48
|
+
default.commute request2
|
49
|
+
TestApi.instance.queue.size.must_equal 2
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#rush' do
|
54
|
+
describe 'no authorize' do
|
55
|
+
before do
|
56
|
+
@stub_get = stub_request(:get, "http://www.example.com")
|
57
|
+
@stub_post = stub_request(:post, "http://www.example.com")
|
58
|
+
@stub_delete = stub_request(:delete, "http://www.example.com")
|
59
|
+
|
60
|
+
default.commute request1
|
61
|
+
default.commute request2
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should clear the queue' do
|
65
|
+
default.rush
|
66
|
+
TestApi.instance.queue.size.must_equal 0
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should run all requests' do
|
70
|
+
default.rush
|
71
|
+
assert_requested(@stub_get)
|
72
|
+
assert_requested(@stub_post)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should make the additional requests given to rush' do
|
76
|
+
default.rush request3
|
77
|
+
assert_requested(@stub_get)
|
78
|
+
assert_requested(@stub_post)
|
79
|
+
assert_requested(@stub_delete)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'authorize method provided' do
|
84
|
+
it 'should authorize if a methode is provided' do
|
85
|
+
request = default.with(url: 'http://www.example.com', method: :get).build
|
86
|
+
TestApi.instance.stubs(:authorize).returns 'Authorized'
|
87
|
+
stub_request(:get, "http://www.example.com")
|
88
|
+
# Actually make the request
|
89
|
+
default.rush request
|
90
|
+
# TestApi.instance.stubs(:authorize).returns('Authorized')
|
91
|
+
assert_requested(:get, "http://www.example.com", :times => 1) { |request|
|
92
|
+
request.headers['Authorization'] = 'Authorized'
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'webmock/minitest'
|
3
|
+
|
4
|
+
describe Commute::Context do
|
5
|
+
|
6
|
+
let(:context) {
|
7
|
+
Commute::Context.new api, options, stack
|
8
|
+
}
|
9
|
+
|
10
|
+
let(:stack) { Commute::Stack.new(stub, stub) }
|
11
|
+
|
12
|
+
let(:api) { stub }
|
13
|
+
|
14
|
+
let(:options) {
|
15
|
+
{ body: 1, two: 2, params: { a: 'a' }}
|
16
|
+
}
|
17
|
+
|
18
|
+
describe '#with' do
|
19
|
+
it 'should keep its current options when the context is incremented' do
|
20
|
+
new_context = context.with body: 'one', params: { a: 'A', b: 'b' }
|
21
|
+
context.options.must_equal body: 1, two: 2, params: { a: 'a' }
|
22
|
+
new_context.options.must_equal body: 'one', two: 2, params: { a: 'A', b: 'b' }
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should not alter the current stack when the context is incremented' do
|
26
|
+
new_context = context.with do |stack|
|
27
|
+
stack << stub
|
28
|
+
end
|
29
|
+
context.stack.request.size.must_equal 2
|
30
|
+
new_context.stack.request.size.must_equal 3
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#[]' do
|
35
|
+
it 'should give the specified option' do
|
36
|
+
context[:body].must_equal 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#build' do
|
41
|
+
let(:stack) { stub_everything }
|
42
|
+
|
43
|
+
it 'should return a typhoeus request with the raw body' do
|
44
|
+
stack.expects(:run_request).with(1, options).returns 2
|
45
|
+
request = context.build
|
46
|
+
request.body.must_equal 2
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'when called with a callback' do
|
50
|
+
let(:options) {
|
51
|
+
{url: 'www.example.com', method: :get, body: 'sent'}
|
52
|
+
}
|
53
|
+
|
54
|
+
before do
|
55
|
+
# Stub the stack
|
56
|
+
stack.stubs(:run_request).with('sent', options).returns('sent transformed')
|
57
|
+
stack.stubs(:run_response).with('returned', options).returns('returned transformed')
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'when the requests succeeds with good status' do
|
61
|
+
it 'should call the callback with the original response and the transformed result' do
|
62
|
+
# Stub the http request.
|
63
|
+
stub_request(:get, 'www.example.com').with(body: 'sent transformed').to_return(body: 'returned')
|
64
|
+
# Build the request with a callback
|
65
|
+
callback = Proc.new {}
|
66
|
+
callback.expects(:call).with { |response, result|
|
67
|
+
response.body.must_equal 'returned'
|
68
|
+
result.must_equal 'returned transformed'
|
69
|
+
}
|
70
|
+
request = context.build &callback
|
71
|
+
# Run the request.
|
72
|
+
Typhoeus::Hydra.hydra.queue request
|
73
|
+
Typhoeus::Hydra.hydra.run
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'when the request succeeds with bad status' do
|
78
|
+
it 'should call the callback with the original response and no result' do
|
79
|
+
# Stub the http request.
|
80
|
+
stub_request(:get, 'www.example.com').with(body: 'sent transformed').to_return(\
|
81
|
+
status: [500, "Internal Server Error"])
|
82
|
+
# Response stack should never be triggered.
|
83
|
+
stack.expects(:run_response).never
|
84
|
+
# Build the request with a callback
|
85
|
+
callback = Proc.new {}
|
86
|
+
callback.expects(:call).with { |response, result|
|
87
|
+
response.code.must_equal 500
|
88
|
+
result.must_equal ''
|
89
|
+
}
|
90
|
+
request = context.build &callback
|
91
|
+
# Run the request.
|
92
|
+
Typhoeus::Hydra.hydra.queue request
|
93
|
+
Typhoeus::Hydra.hydra.run
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe 'when an after hook is configured' do
|
98
|
+
it 'should call it with the context' do
|
99
|
+
api.stubs(:after).with(context).returns(context.with(after: true))
|
100
|
+
request = context.build
|
101
|
+
request.context[:after].must_equal true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '#method_missing' do
|
108
|
+
it 'should call the api with an incremented context when only given options' do
|
109
|
+
api.expects(:test_api_call).with { |context|
|
110
|
+
context[:enhanced].must_equal true
|
111
|
+
}
|
112
|
+
context.test_api_call enhanced: true
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should call the api with an incremented context when given no options' do
|
116
|
+
api.expects(:test_api_call).with { |incr_context|
|
117
|
+
context.options.must_equal context.options
|
118
|
+
}
|
119
|
+
context.test_api_call
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should call the api with an incremented context when given options and a block' do
|
123
|
+
api.expects(:test_api_call).with { |context|
|
124
|
+
context[:enhanced].must_equal true
|
125
|
+
context.stack.request.size.must_equal 3
|
126
|
+
}
|
127
|
+
context.test_api_call enhanced: true do |stack|
|
128
|
+
stack << stub
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should forward commute and rush to the api' do
|
134
|
+
request = stub
|
135
|
+
api.expects(:commute).with(request)
|
136
|
+
api.expects(:rush)
|
137
|
+
context.commute request
|
138
|
+
context.rush
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commute::Layer do
|
4
|
+
|
5
|
+
let(:layer) { Commute::Layer.new }
|
6
|
+
|
7
|
+
describe 'when there is no call method' do
|
8
|
+
it 'should do nothing' do
|
9
|
+
layer.request nil, nil
|
10
|
+
layer.response nil, nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'when there is a call methode' do
|
15
|
+
it 'should call it' do
|
16
|
+
request = stub
|
17
|
+
options = { test: 1 }
|
18
|
+
layer.expects(:call).with(request, options)
|
19
|
+
layer.request request, options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commute::Stack do
|
4
|
+
|
5
|
+
let(:stack) { Commute::Stack.new }
|
6
|
+
|
7
|
+
let(:layer1) { stub.tap { |stub|
|
8
|
+
stub.stubs(:name).returns(:first)
|
9
|
+
}}
|
10
|
+
|
11
|
+
let(:layer2) { stub.tap { |stub|
|
12
|
+
stub.stubs(:name).returns(:second)
|
13
|
+
}}
|
14
|
+
|
15
|
+
let(:layer3) { stub.tap { |stub|
|
16
|
+
stub.stubs(:name).returns(:third)
|
17
|
+
}}
|
18
|
+
|
19
|
+
describe '#size' do
|
20
|
+
it 'should return the size of the stack' do
|
21
|
+
stack.request.size.must_equal 0
|
22
|
+
stack << stub
|
23
|
+
stack << stub
|
24
|
+
stack.request.size.must_equal 2
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#run_request' do
|
29
|
+
before do
|
30
|
+
stack << layer1
|
31
|
+
stack << layer2
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should run the body through all layers' do
|
35
|
+
layer1.expects(:request).with(1, {}).returns(2)
|
36
|
+
layer2.expects(:request).with(2, {}).returns(3)
|
37
|
+
|
38
|
+
stack.run_request(1).must_equal 3
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should route the options to the layers' do
|
42
|
+
layer1.expects(:request).with(1, 'options').returns(2)
|
43
|
+
layer2.expects(:request).with(2, user: 'defunkt')
|
44
|
+
|
45
|
+
stack.run_request 1, first: 'options', second: {
|
46
|
+
user: 'defunkt'
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#run_response' do
|
52
|
+
before do
|
53
|
+
stack << layer1
|
54
|
+
stack << layer2
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should run the body through all layers' do
|
58
|
+
layer2.expects(:response).with(1, {}).returns(2)
|
59
|
+
layer1.expects(:response).with(2, {}).returns(3)
|
60
|
+
|
61
|
+
stack.run_response(1).must_equal 3
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should route the options to the layers' do
|
65
|
+
layer2.expects(:response).with(1, user: 'defunkt').returns(2)
|
66
|
+
layer1.expects(:response).with(2, 'options')
|
67
|
+
|
68
|
+
stack.run_response 1, first: 'options', second: {
|
69
|
+
user: 'defunkt'
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe Commute::Stack::Sequence do
|
75
|
+
|
76
|
+
describe '#without!' do
|
77
|
+
before do
|
78
|
+
stack << layer1
|
79
|
+
stack << layer2
|
80
|
+
stack << layer3
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should remove the correct layers from the stack' do
|
84
|
+
stack.request.without! :second, :third
|
85
|
+
stack.request.size.must_equal 1
|
86
|
+
stack.response.size.must_equal 3
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#before!' do
|
91
|
+
before do
|
92
|
+
stack << layer1
|
93
|
+
stack << layer3
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should insert a layer before another one' do
|
97
|
+
stack.request.before! :third, layer2
|
98
|
+
stack.request[1].must_equal layer2
|
99
|
+
stack.request.size.must_equal 3
|
100
|
+
stack.response.size.must_equal 2
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#after!' do
|
105
|
+
before do
|
106
|
+
stack << layer1
|
107
|
+
stack << layer3
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should insert a layer after another one' do
|
111
|
+
stack.request.after! :first, layer2
|
112
|
+
stack.request[1].must_equal layer2
|
113
|
+
stack.request.size.must_equal 3
|
114
|
+
stack.response.size.must_equal 2
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should insert after the last one' do
|
118
|
+
stack.request.after! :third, layer2
|
119
|
+
stack.request.last.must_equal layer2
|
120
|
+
stack.request.size.must_equal 3
|
121
|
+
stack.response.size.must_equal 2
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: commute
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mattias Putman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: typhoeus
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mocha
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: guard
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: guard-minitest
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: yard
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: simplecov
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: webmock
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: yajl-ruby
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
description: Handling the HTTP commute like a boss
|
159
|
+
email:
|
160
|
+
- mattias.putman@gmail.com
|
161
|
+
executables: []
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files: []
|
164
|
+
files:
|
165
|
+
- .gitignore
|
166
|
+
- .travis.yml
|
167
|
+
- Gemfile
|
168
|
+
- Guardfile
|
169
|
+
- LICENSE
|
170
|
+
- README.md
|
171
|
+
- Rakefile
|
172
|
+
- commute.gemspec
|
173
|
+
- lib/commute.rb
|
174
|
+
- lib/commute/adapters/typhoeus.rb
|
175
|
+
- lib/commute/api.rb
|
176
|
+
- lib/commute/context.rb
|
177
|
+
- lib/commute/layer.rb
|
178
|
+
- lib/commute/stack.rb
|
179
|
+
- lib/commute/version.rb
|
180
|
+
- spec/commute/api_spec.rb
|
181
|
+
- spec/commute/context_spec.rb
|
182
|
+
- spec/commute/layer_spec.rb
|
183
|
+
- spec/commute/stack_spec.rb
|
184
|
+
- spec/spec_helper.rb
|
185
|
+
homepage: http://challengee.github.com/commute/
|
186
|
+
licenses: []
|
187
|
+
post_install_message:
|
188
|
+
rdoc_options: []
|
189
|
+
require_paths:
|
190
|
+
- lib
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
192
|
+
none: false
|
193
|
+
requirements:
|
194
|
+
- - ! '>='
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: '0'
|
197
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
198
|
+
none: false
|
199
|
+
requirements:
|
200
|
+
- - ! '>='
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
requirements: []
|
204
|
+
rubyforge_project:
|
205
|
+
rubygems_version: 1.8.19
|
206
|
+
signing_key:
|
207
|
+
specification_version: 3
|
208
|
+
summary: ! 'Commute helps you to: 1) Dynamically build HTTP requests for your favorite
|
209
|
+
HTTP library (currently only Typhoeus). 2) Easily process request and response bodies.
|
210
|
+
3) Execute parallel HTTP requests.'
|
211
|
+
test_files:
|
212
|
+
- spec/commute/api_spec.rb
|
213
|
+
- spec/commute/context_spec.rb
|
214
|
+
- spec/commute/layer_spec.rb
|
215
|
+
- spec/commute/stack_spec.rb
|
216
|
+
- spec/spec_helper.rb
|
217
|
+
has_rdoc:
|