commute 0.2.0.rc.2 → 0.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.todo +28 -12
  2. data/README.md +0 -1
  3. data/commute.gemspec +4 -5
  4. data/lib/commute/common/basic_auth.rb +10 -9
  5. data/lib/commute/common/caching.rb +208 -0
  6. data/lib/commute/common/chemicals.rb +47 -24
  7. data/lib/commute/common/eventmachine.rb +68 -0
  8. data/lib/commute/common/synchrony.rb +42 -0
  9. data/lib/commute/common/typhoeus.rb +64 -0
  10. data/lib/commute/core/api.rb +42 -29
  11. data/lib/commute/core/builder.rb +4 -15
  12. data/lib/commute/core/context.rb +156 -15
  13. data/lib/commute/core/http.rb +124 -0
  14. data/lib/commute/core/layer.rb +187 -0
  15. data/lib/commute/core/sequence.rb +83 -132
  16. data/lib/commute/core/stack.rb +63 -72
  17. data/lib/commute/core/status.rb +45 -0
  18. data/lib/commute/core/util/event_emitter.rb +58 -0
  19. data/lib/commute/core/util/path.rb +37 -0
  20. data/lib/commute/core/util/stream.rb +141 -0
  21. data/lib/commute/extensions/crud.rb +88 -0
  22. data/lib/commute/extensions/param.rb +20 -0
  23. data/lib/commute/extensions/url.rb +53 -0
  24. data/lib/commute/version.rb +1 -1
  25. data/spec/commute/common/caching_spec.rb +158 -0
  26. data/spec/commute/common/eventmachine_spec.rb +74 -0
  27. data/spec/commute/common/typhoeus_spec.rb +67 -0
  28. data/spec/commute/core/api_spec.rb +3 -1
  29. data/spec/commute/core/builder_spec.rb +8 -8
  30. data/spec/commute/core/http_spec.rb +39 -0
  31. data/spec/commute/core/layer_spec.rb +81 -0
  32. data/spec/commute/core/sequence_spec.rb +36 -150
  33. data/spec/commute/core/stack_spec.rb +33 -83
  34. data/spec/commute/core/util/event_emitter_spec.rb +35 -0
  35. data/spec/commute/core/util/path_spec.rb +29 -0
  36. data/spec/commute/core/util/stream_spec.rb +90 -0
  37. data/spec/commute/extensions/url_spec.rb +76 -0
  38. data/spec/spec_helper.rb +3 -1
  39. metadata +61 -48
  40. data/examples/gist_api.rb +0 -71
  41. data/examples/highrise_task_api.rb +0 -59
  42. data/examples/pastie_api.rb +0 -18
  43. data/lib/commute/aspects/caching.rb +0 -37
  44. data/lib/commute/aspects/crud.rb +0 -41
  45. data/lib/commute/aspects/pagination.rb +0 -16
  46. data/lib/commute/aspects/url.rb +0 -57
  47. data/lib/commute/common/cache.rb +0 -43
  48. data/lib/commute/common/conditional.rb +0 -27
  49. data/lib/commute/common/em-synchrony_adapter.rb +0 -29
  50. data/lib/commute/common/em_http_request_adapter.rb +0 -57
  51. data/lib/commute/common/typhoeus_adapter.rb +0 -40
  52. data/lib/commute/common/xml.rb +0 -7
  53. data/lib/commute/core/commuter.rb +0 -116
  54. data/lib/commute/core/processors/code_status_processor.rb +0 -40
  55. data/lib/commute/core/processors/hook.rb +0 -14
  56. data/lib/commute/core/processors/request_builder.rb +0 -26
  57. data/lib/commute/core/processors/sequencer.rb +0 -46
  58. data/lib/commute/core/request.rb +0 -58
  59. data/lib/commute/core/response.rb +0 -18
  60. data/spec/commute/aspects/caching_spec.rb +0 -12
  61. data/spec/commute/aspects/url_spec.rb +0 -61
  62. data/spec/commute/core/commuter_spec.rb +0 -64
  63. data/spec/commute/core/processors/code_status_processor_spec.rb +0 -5
  64. data/spec/commute/core/processors/hook_spec.rb +0 -25
  65. data/spec/commute/core/processors/request_builder_spec.rb +0 -25
  66. data/spec/commute/core/processors/sequencer_spec.rb +0 -33
data/.todo CHANGED
@@ -1,15 +1,31 @@
1
- conditional sequencer (body), routing on constants
2
- conditional processors instead of only sequencers
3
1
  easy transforms, reactivity? When not to add a transform
4
- disabling
5
- groups of commuters (Group and delaying)
6
- queueing
7
- batching/grouping
8
2
  net/http
9
3
  stubbing
10
- gzip
11
- resourcing (commute-resourcing)
12
- Api::Metal
13
- caching
14
- calling processor from within commuter
15
- simpler sequencer internal working
4
+ request error handling (timeouts...)
5
+
6
+ layers:
7
+ OAuth with refreshing (and on_refresh callback)
8
+ Redirect following
9
+ Caching
10
+ Batching
11
+ Page Crusher
12
+
13
+ api method on context to select context
14
+ complete method for quick layer (eg github gist star).
15
+ why not use stream, run, request in layers or in complete system.
16
+
17
+ HOOKS
18
+
19
+ stream flattening [[1,2,3]] => [1,2,3] (flat_map?)
20
+ stream::lazy; enumerable?
21
+
22
+ New relic instrumentation
23
+ Separate Parse/Render
24
+
25
+ move layer specific config to using istead of with
26
+ using [:render, :parse] => {
27
+ template: 'person'
28
+ }
29
+
30
+ param :reload # transform { |r, param| r.query[param] = c[param] }
31
+ cache param blocks
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  # Commute
2
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
3
 
5
4
  Commute helps you to:
6
5
 
data/commute.gemspec CHANGED
@@ -14,17 +14,16 @@ Gem::Specification.new do |s|
14
14
  s.require_paths = ["lib"]
15
15
  s.version = Commute::VERSION
16
16
 
17
- s.add_dependency 'typhoeus', '0.5.0.rc'
18
- s.add_dependency 'em-http-request'
17
+ s.add_development_dependency 'typhoeus'
18
+ s.add_development_dependency 'em-http-request'
19
+ s.add_development_dependency 'yajl-ruby'
19
20
 
20
21
  s.add_development_dependency 'rake'
21
22
  s.add_development_dependency 'mocha'
23
+ s.add_development_dependency 'rb-fsevent'
22
24
  s.add_development_dependency 'guard'
23
25
  s.add_development_dependency 'guard-minitest'
24
26
  s.add_development_dependency 'yard'
25
27
  s.add_development_dependency 'simplecov'
26
28
  s.add_development_dependency 'webmock'
27
-
28
- # For the examples.
29
- s.add_development_dependency 'yajl-ruby'
30
29
  end
@@ -4,16 +4,17 @@ module Commute
4
4
  module Common
5
5
 
6
6
  class BasicAuth
7
- @id = :auth
7
+ @name = :auth
8
8
 
9
- def call commuter, options = {}
10
- commuter.change do |request|
11
- # Ruby's Base64 puts newlines at the end (and every 60 chars)...
12
- # This breaks HTTP. So strip them!
13
- authorization = Base64.encode64("#{options[:username]}:#{options[:password]}}").gsub "\n", ''
14
- request.headers['Authorization'] = "Basic #{authorization}"
15
- request
16
- end
9
+ def call router, request, options = {}
10
+ # Create the Authorization header.
11
+ authorization = Base64.strict_encode64 \
12
+ "#{options[:username]}:#{options[:password]}}"
13
+ # Set the header on the http request.
14
+ request.http.headers['Authorization'] = "Basic #{authorization}"
15
+
16
+ # Send the request, pipe the data, and forward the response.
17
+ request.pipe router.call(request.http, &request.responder)
17
18
  end
18
19
  end
19
20
  end
@@ -0,0 +1,208 @@
1
+ require 'commute/core/http'
2
+
3
+ module Commute
4
+ module Common
5
+
6
+ # Internal: Fetches cached responses to make requests faster.
7
+ #
8
+ # Any GET requests that comes in will be fetched in the cache
9
+ # based on its URL.
10
+ #
11
+ # When nothing is found in the cache, a request is made as
12
+ # planned, and the result is stored in the cache (when it is a GET request).
13
+ #
14
+ # When it is found in the cache, there are actually two possibilities.
15
+ # One is if validation is on (:validate option). When it is on, it will
16
+ # detect if the resource has already changed on the server before returning
17
+ # from the cache (using etags).
18
+ # If validation is off, the result from the cache is returned.
19
+ #
20
+ class Caching
21
+ @name = :caching
22
+
23
+ # Internal: Special status that wraps a http status and
24
+ # adds cache indications.
25
+ #
26
+ class CacheStatus < Status
27
+
28
+ # Public: The underlaying http status.
29
+ attr_accessor :http
30
+
31
+ # Public: Whether the response was cached.
32
+ attr_accessor :cached
33
+
34
+ # Public: Whether the cache was updated on this request.
35
+ attr_accessor :updated
36
+
37
+ # Public: Creates a new CacheStatus.
38
+ #
39
+ # http - Underlaying http status, can be nil.
40
+ # cached - Whether the response was cached.
41
+ # updated - Whether the cache was updated on this request.
42
+ #
43
+ def initialize http
44
+ @http = http
45
+ @updated = false
46
+ end
47
+
48
+ # Public: Success or not?
49
+ def success?
50
+ (http? && http.success?) || cached
51
+ end
52
+
53
+ # Public: Whether there was an http request performed.
54
+ def http?
55
+ !http.nil?
56
+ end
57
+ end
58
+
59
+ # Internal: One entry in the cache, contains the data of a
60
+ # resource and the etag it corresponds with.
61
+ class CacheEntry
62
+
63
+ # Public: The value present in the cache.
64
+ attr_reader :value
65
+
66
+ # Public: The key of this cache value.
67
+ attr_reader :key
68
+
69
+ # Initialize a new Entry.
70
+ #
71
+ # cache - The cache to work in.
72
+ # key - The key for this cache entry.
73
+ # value - The contents for this cache entry.
74
+ #
75
+ def initialize cache, key, value, exists = false
76
+ @cache = cache
77
+ @key = key
78
+ @value = value
79
+ @exists = exists
80
+ end
81
+
82
+ # Search for a CacheEntry in a cache.
83
+ #
84
+ # cache - The cache to look in.
85
+ # key - The key to look for.
86
+ #
87
+ # Returns a CacheEntry or nil if none was found.
88
+ def self.lookup cache, key
89
+ value = cache.get(key)
90
+ CacheEntry.new(cache, key, value, !value.nil?)
91
+ end
92
+
93
+ # Stores the entry in the cache.
94
+ def store
95
+ @cache.set @key, @value
96
+ @exists = true
97
+ nil
98
+ end
99
+
100
+ # Updates the entry to a new value
101
+ def update value
102
+ @value = value
103
+ self.store
104
+ end
105
+
106
+ def exists?
107
+ @exists
108
+ end
109
+ end
110
+
111
+ # The caching logic itself.
112
+ #
113
+ # options - Hash of options for caching (default: {}):
114
+ # :cache - A cache (#get, #set).
115
+ # :validate - Whether to check if the cache
116
+ # entry is still valid (default: false).
117
+ #
118
+ def call router, request, options = {}
119
+ # Fetch some options.
120
+ cache, validate = options[:cache], options[:validate] || false
121
+ # Cannot proceed if there is no cache.
122
+ raise 'You need to configure a cache first' unless cache
123
+
124
+ # This cache only works for get request, proceed if something else.
125
+ router.call request and return unless request.http.method == :get
126
+
127
+ # Get the cache entry.
128
+ entry = CacheEntry.lookup(cache, request.http.uri.to_s)
129
+
130
+ # To do when we have something to show.
131
+ on_response = proc do |response, status|
132
+ # Just pipe the response from the net/cache to the response for this request.
133
+ response.pipe request.respond(response, status)
134
+ end
135
+
136
+ # When validation is on, or we have no cache entry, get the latest (request or cache)
137
+ if validate || !entry.exists?
138
+ respond_latest router, entry, request, &on_response
139
+ # When we have something in the cache and we do not want to validate it,
140
+ # Then just return our cache entry.
141
+ else
142
+ respond_from_cache entry, request, &on_response
143
+ end
144
+ end
145
+
146
+ private
147
+
148
+ # Responds with the latest version for a given remote resource. Either from the
149
+ # cache or from the internets. Checks based on etags.
150
+ # Warning: Could modify the given request.
151
+ #
152
+ # stack - The Commute stack.
153
+ # entry - The cache entry.
154
+ # request - The request for the remote resource.
155
+ #
156
+ # Yields a response like you would get from a stack call.
157
+ #
158
+ def respond_latest router, entry, request, &callback
159
+ # Modify the request to be a head request of nothing was found in the cache.
160
+ if entry.exists?
161
+ request.http.headers['If-None-Match'] = entry.value[:etag]
162
+ end
163
+ # Fire the conditional request.
164
+ validate_request = router.call request.http do |response, http_status|
165
+ # If it was not modified, the it is still valid.
166
+ if http_status.not_modified?
167
+ # Respond from cache.
168
+ respond_from_cache entry, request do |response, status|
169
+ callback.call response, status.tap { |s| s.http = http_status }
170
+ end
171
+
172
+ # If it was modified and a success, respond and update the cache.
173
+ elsif http_status.success?
174
+ # Update the cache.
175
+ response.once(:data) do |body|
176
+ entry.update data: body, etag: response.http.etag
177
+ end
178
+ # Respond with wrapped status marked as invalidated.
179
+ callback.call response, CacheStatus.new(http_status).tap { |status|
180
+ status.updated = true
181
+ status.cached = entry.exists?
182
+ }
183
+
184
+ # The response was not a success, that sucks, respond with it anyway.
185
+ else
186
+ callback.call response, CacheStatus.new(http_status).mark(cached: !entry.nil?)
187
+ end
188
+ end
189
+ validate_request.end
190
+ end
191
+
192
+ # Responds with a cached version.
193
+ # Raises an error when the entry is nil.
194
+ #
195
+ # entry - The cache entry.
196
+ #
197
+ # Yields a response like you would get from a stack call.
198
+ #
199
+ def respond_from_cache entry, request, &callback
200
+ raise ArgumentError, "Entry is nil" unless entry
201
+ response = Layer::Response.new Http::Response.new(request)
202
+ callback.call response, CacheStatus.new(nil).tap { |s| s.cached = true }
203
+ # Stream the cached data in one big chunk.
204
+ response.end entry.value[:data]
205
+ end
206
+ end
207
+ end
208
+ end
@@ -2,36 +2,59 @@ require 'chemicals'
2
2
 
3
3
  module Commute
4
4
  module Common
5
- class Chemicals
6
- @id = :chemicals
7
5
 
8
- def initialize root = nil
9
- @root = root
10
- @cache = {}
11
- end
6
+ module Chemicals
7
+
8
+ module Chemicals::Base
9
+ TEMPLATE_CACHE = {}
10
+
11
+ def initialize template_root = './'
12
+ @template_root = template_root
13
+ end
12
14
 
13
- def call commuter, path
14
- # Build the path to the template.
15
- path = if @root
16
- "#{@root}/#{path}.xml"
17
- else
18
- "#{path}.xml"
15
+ def template path
16
+ file = File.expand_path "#{path}.xml", @template_root
17
+
18
+ if TEMPLATE_CACHE.has_key? file
19
+ TEMPLATE_CACHE[file]
20
+ else
21
+ TEMPLATE_CACHE[file] = ::Chemicals::Template.new IO.read(file)
22
+ end
19
23
  end
20
- # Create/Cache the Template.
21
- template = if @cache[path]
22
- @cache[path]
23
- else
24
- ::Chemicals::Template.new(IO.read(path)).tap do |t|
25
- @cache[path] = t
24
+ end
25
+
26
+ class Parser
27
+ include Chemicals::Base
28
+ @name = :parse
29
+
30
+ def call router, request, options
31
+ path = options[:path]
32
+
33
+ _request = router.call(request.http) do |_response, status|
34
+ response = request.respond _response.http, status
35
+
36
+ _response.inject('', &:<<).map { |body|
37
+ template(path).parse body
38
+ }.pipe response
26
39
  end
40
+ request.pipe _request
27
41
  end
28
- # Use the template to parse/render.
29
- if commuter.get.body
30
- if commuter.get.body.kind_of?(String) && !commuter.get.body.strip.empty?
31
- commuter.get.body = template.parse(commuter.get.body)
32
- elsif !commuter.get.body.kind_of?(String)
33
- commuter.get.body = template.render(commuter.get.body).to_s
42
+ end
43
+
44
+ class Renderer
45
+ include Chemicals::Base
46
+ @name = :render
47
+
48
+ def call router, request, options
49
+ path = options[:path]
50
+
51
+ _request = router.call(request.http) do |_response, status|
52
+ _response.pipe request.respond(_response.http, status)
34
53
  end
54
+
55
+ request.map { |body|
56
+ template(path).render(body).to_s
57
+ }.pipe _request
35
58
  end
36
59
  end
37
60
  end
@@ -0,0 +1,68 @@
1
+ require 'em-http-request'
2
+
3
+ require 'commute/core/http'
4
+
5
+ module Commute
6
+ module Common
7
+
8
+ # Public: Adapter that uses em-http-request, an Eventmachine based
9
+ # HTTP client, as a underlaying commute engine.
10
+ #
11
+ # This requires all the commute requests to be made within the
12
+ # Eventmachine loop.
13
+ #
14
+ class Eventmachine
15
+ @name = :adapter
16
+
17
+ # Internal: Make a request through Eventmachine.
18
+ def call router, request, options = {}
19
+ # Buffer the request (only streaming response).
20
+ request.buffer.on(:data) do |body|
21
+ response = nil
22
+ # Create a native em-http request.
23
+ em_request = to_request request.http, body.join
24
+
25
+ em_request.headers do
26
+ # Create a Http response.
27
+ http_response = to_response request, em_request.response_header
28
+ # Create a Http::Status (determined from response code).
29
+ status = Http::Status.new(http_response.code)
30
+ # Respond.
31
+ response = request.respond(http_response, status)
32
+ end
33
+
34
+ # Set the data callback.
35
+ em_request.stream do |chunk|
36
+ response.write chunk
37
+ end
38
+
39
+ # Set the end callback.
40
+ em_request.callback do
41
+ response.end
42
+ end
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ # Internal: Converts a Commute requests into an em-http request.
49
+ def to_request request, body
50
+ EventMachine::HttpRequest.new(request.uri.to_s).send request.method, \
51
+ query: request.query,
52
+ head: {
53
+ 'Accept-Encoding' => 'gzip, deflate'
54
+ }.merge!(request.headers),
55
+ body: body
56
+ end
57
+
58
+ # Internal: Converts an em-http response into a Commute response.
59
+ # Note: trims whitespace bodies.
60
+ def to_response request, eventmachine_response
61
+ Http::Response.new(request).tap do |response|
62
+ response.code = eventmachine_response.status
63
+ response.headers = eventmachine_response.raw
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,42 @@
1
+ require "em-synchrony"
2
+
3
+ require 'commute/common/eventmachine'
4
+
5
+ module Commute
6
+ module Common
7
+
8
+ # Public: Adapter that uses em-synchrony (with em-http-request).
9
+ #
10
+ # Requires use of em-synchrony and thus Eventmachine.
11
+ #
12
+ class Synchrony < Eventmachine
13
+ @name = :adapter
14
+
15
+ # Internal: Make a request through em-synchrony.
16
+ def call router, request, options = {}
17
+ # Get the current fiber.
18
+ body = nil
19
+ request.on(:data) { |_body| body = _body }
20
+ # Buffer the request (only streaming response).
21
+ request.on(:end) do |body|
22
+ response = nil
23
+ # Create a native em-http request.
24
+ em_request = to_request request.http, body
25
+
26
+ # Create a Http response.
27
+ http_response = to_response request, em_request.response_header
28
+ # Create a Http::Status (determined from response code).
29
+ status = Http::Status.new(http_response.code)
30
+ # Respond.
31
+ response = request.respond(http_response, status)
32
+
33
+ response.write em_request.response unless \
34
+ em_request.response.strip.empty?
35
+
36
+ # Request is finished, end the response.
37
+ response.end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,64 @@
1
+ require 'typhoeus'
2
+
3
+ require 'commute/core/http'
4
+
5
+ module Commute
6
+ module Common
7
+
8
+ # Internal: Adapter that uses Typhoeus to make requests.
9
+ #
10
+ # Request bodies are entirely buffered before sending.
11
+ # Responses are also completely buffered before they
12
+ # are sent out in one big chunk from this layer.
13
+ #
14
+ class Typhoeus
15
+ @name = :adapter
16
+
17
+ def call router, request, options = {}
18
+ # Here we can only send when the request is buffered (body is needed).
19
+ body = []
20
+ request.buffer.on(:data) { |_body| body = _body }
21
+ request.on(:end) do
22
+ # Build a Typhoeus request from this request.
23
+ # Body should be a String.
24
+ typhoeus_request = to_request(request.http, body.join)
25
+ # Run the request with Hydra.
26
+ hydra = ::Typhoeus::Hydra.new
27
+ hydra.queue typhoeus_request
28
+ hydra.run
29
+ # Build a Http response.
30
+ http_response = to_response(request.http, typhoeus_request.response)
31
+ # Create a Http::Status (determined from response code).
32
+ status = Http::Status.new(http_response.code)
33
+ # Respond with that response and send the data.
34
+ response = request.respond(http_response, status) do |r|
35
+ # Write the response body if there is something besides whitespace.
36
+ r.write typhoeus_request.response.body unless \
37
+ typhoeus_request.response.body.strip.empty? || status.fail?
38
+ end
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ # Internal: Creates a Typhoeus::Request from a Http::Request
45
+ # and body.
46
+ def to_request request, body
47
+ ::Typhoeus::Request.new request.uri.to_s, \
48
+ method: request.method,
49
+ params: request.query || {},
50
+ headers: request.headers,
51
+ body: body,
52
+ accept_encoding: ''
53
+ end
54
+
55
+ # Internal: Creates a Http::Response from a Typhoeus::Response.
56
+ def to_response request, typhoeus_response
57
+ Http::Response.new(request).tap do |response|
58
+ response.code = typhoeus_response.code
59
+ response.headers = typhoeus_response.headers
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,10 +1,5 @@
1
1
  require 'commute/core/context'
2
2
 
3
- require 'commute/core/processors/hook'
4
- require 'commute/core/processors/request_builder'
5
- require 'commute/core/processors/sequencer'
6
- require 'commute/core/processors/code_status_processor'
7
-
8
3
  module Commute
9
4
 
10
5
  # Public: An API is a context that already provides some standard
@@ -45,6 +40,7 @@ module Commute
45
40
  class Api < Builder
46
41
  class << self
47
42
  include Buildable
43
+ include Forwardable
48
44
 
49
45
  # The Builder methods should only be called in the Class itself.
50
46
  # Otherwise, something like TestApi.with ... would modify
@@ -55,13 +51,38 @@ module Commute
55
51
  private *Builder::METHODS
56
52
 
57
53
  # Internal: Memoized base builder.
54
+ #
55
+ # The builder is actually an instance of the Api itself.
56
+ # This means that all methods available on the Api can
57
+ # be used in the base builder to create a base Api context.
58
58
  def builder
59
- @builder ||= Builder.new Context.new(Stack.new {})
59
+ @builder ||= self.new Context.new(Stack.new {})
60
60
  end
61
61
 
62
62
  # Internal: When an Api inherits another Api, it inherits its context.
63
63
  def inherited klass
64
- klass.instance_variable_set :@builder, Builder.new(builder.context)
64
+ klass.instance_variable_set :@builder, klass.new(builder.context)
65
+ end
66
+
67
+ # Internal: On include, define forwarding methods to the builder.
68
+ #
69
+ # Whenever some extension is included, it works for Api instances.
70
+ # However we want to define a default context in the Api class, so
71
+ # the extension methods need to be present the class as wel (and be
72
+ # forwarded to the builder). We define these forwarding methods here.
73
+ #
74
+ def include mod
75
+ # Do a normal include.
76
+ super
77
+ # Define a forwarding method for each extension method.
78
+ mod.const_get(:METHODS).each do |method|
79
+ # Define the method.
80
+ self.instance_eval %{
81
+ def #{method} *args, &block
82
+ builder.#{method} *args, &block
83
+ end
84
+ }
85
+ end
65
86
  end
66
87
  end
67
88
 
@@ -79,28 +100,12 @@ module Commute
79
100
  #
80
101
  # Also provides basic on_request, on_response and
81
102
  # on_complete hooks.
82
- using do |stack, main|
83
- main.append RequestBuilder.new
84
- main.append Sequencer.new(:request), as: :request
85
- main.append Sequencer.new(:execution), as: :execution
86
- main.append Sequencer.new(:response), as: :response
87
- main.append CodeStatusProcessor.new, as: :status
88
- main.append Hook.new, as: :on_complete
89
-
90
- stack.sequence(:execution) do
91
- append Proc.new { |c|
92
- Configuration.adapter.call c
93
- }
94
- end
95
-
96
- # sequence(:request) do
97
- # append Scope.new(Sequencer.new(:request_body), :body), as: :body
98
- # end
99
-
100
- # sequence(:response) do
101
- # append Hook.new, as: :on_response
102
- # append Scope.new(Sequencer.new(:response_body), :body), as: :body
103
- # end
103
+ using do
104
+ sequence.append sequence(:request)
105
+ sequence.append sequence(:response)
106
+ sequence.append proc { |router, request, options = {}|
107
+ Configuration.adapter.call router, request, options
108
+ }
104
109
  end
105
110
 
106
111
  # Public: Creates a new Api Builder, starting to build from
@@ -112,5 +117,13 @@ module Commute
112
117
  def initialize context = self.class.builder.context
113
118
  super
114
119
  end
120
+
121
+ # Public: Executes a method only if it exists. Otherwise just
122
+ # returns self.
123
+ #
124
+ # Returns the output of the existing method or self.
125
+ def try method, *args, &block
126
+ self.respond_to?(method) ? self.send(method, *args, &block) : self
127
+ end
115
128
  end
116
129
  end