commute 0.2.0.rc.2 → 0.3.0.pre

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.
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