coach 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +123 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +104 -0
- data/README.md +260 -0
- data/coach.gemspec +28 -0
- data/lib/coach.rb +16 -0
- data/lib/coach/errors.rb +16 -0
- data/lib/coach/handler.rb +82 -0
- data/lib/coach/middleware.rb +96 -0
- data/lib/coach/middleware_item.rb +31 -0
- data/lib/coach/middleware_validator.rb +39 -0
- data/lib/coach/notifications.rb +88 -0
- data/lib/coach/request_benchmark.rb +59 -0
- data/lib/coach/request_serializer.rb +63 -0
- data/lib/coach/router.rb +70 -0
- data/lib/coach/version.rb +3 -0
- data/lib/spec/coach_helper.rb +113 -0
- data/spec/lib/coach/handler_spec.rb +138 -0
- data/spec/lib/coach/middleware_spec.rb +43 -0
- data/spec/lib/coach/middleware_validator_spec.rb +72 -0
- data/spec/lib/coach/notifications_spec.rb +73 -0
- data/spec/lib/coach/request_benchmark_spec.rb +42 -0
- data/spec/lib/coach/request_serializer_spec.rb +38 -0
- data/spec/lib/coach/router_spec.rb +78 -0
- data/spec/spec_helper.rb +14 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b238b46a6cc3ceb9bd972eb675ab185e9782314
|
4
|
+
data.tar.gz: 65d9e3c36df29228fa740e1b745a4888b6e7942c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 68a7271f6e510864ed92949c9b4e9d6edb1ed0212138792717d0ed357c0afd2ff95b35ab42a3915e69cc3b37d9653cba9d9c1bdd3d8c9202473dab482abb35be
|
7
|
+
data.tar.gz: e4ec8fac9cbcbccd75cef7162604f7b521be4a9b902fe41c5625fb47717aff18a55bbc3c9fe28d37eedd94dcf74bd023edf92f52fd1badcab9182fc8bf846c23
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# For all options see https://github.com/bbatsov/rubocop/tree/master/config
|
2
|
+
|
3
|
+
# Limit lines to 90 characters.
|
4
|
+
LineLength:
|
5
|
+
Max: 90
|
6
|
+
|
7
|
+
ClassLength:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
ModuleLength:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
# Avoid methods longer than 30 lines of code
|
14
|
+
MethodLength:
|
15
|
+
CountComments: false # count full line comments?
|
16
|
+
Max: 87
|
17
|
+
|
18
|
+
# Avoid single-line methods.
|
19
|
+
SingleLineMethods:
|
20
|
+
AllowIfMethodIsEmpty: true
|
21
|
+
|
22
|
+
StringLiterals:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
GlobalVars:
|
26
|
+
Enabled: false # We use them Redis + StatsD (though maybe we shouldn't?)
|
27
|
+
|
28
|
+
# Wants underscores in all large numbers. Pain in the ass for things like
|
29
|
+
# unix timestamps.
|
30
|
+
NumericLiterals:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
# Wants you to use the same argument names for every reduce. This seems kinda
|
34
|
+
# naff compared to naming them semantically
|
35
|
+
SingleLineBlockParams:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
Style/SignalException:
|
39
|
+
EnforcedStyle: 'only_raise'
|
40
|
+
|
41
|
+
# Use trailing rather than leading dots on multi-line call chains
|
42
|
+
Style/DotPosition:
|
43
|
+
EnforcedStyle: trailing
|
44
|
+
|
45
|
+
Lint/NestedMethodDefinition:
|
46
|
+
Enabled: false
|
47
|
+
|
48
|
+
Lint/UnusedBlockArgument:
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
Metrics/AbcSize:
|
52
|
+
Max: 61
|
53
|
+
|
54
|
+
Metrics/CyclomaticComplexity:
|
55
|
+
Max: 10
|
56
|
+
|
57
|
+
Metrics/PerceivedComplexity:
|
58
|
+
Max: 10
|
59
|
+
|
60
|
+
Style/AccessorMethodName:
|
61
|
+
Enabled: false
|
62
|
+
|
63
|
+
# Allow non-ASCII characters (e.g. £) in comments
|
64
|
+
Style/AsciiComments:
|
65
|
+
Enabled: false
|
66
|
+
|
67
|
+
# Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle, SupportedLastArgumentHashStyles.
|
68
|
+
Style/AlignHash:
|
69
|
+
Enabled: false
|
70
|
+
|
71
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
|
72
|
+
Style/BlockDelimiters:
|
73
|
+
Enabled: false
|
74
|
+
|
75
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
76
|
+
Style/ClassAndModuleChildren:
|
77
|
+
Enabled: false
|
78
|
+
|
79
|
+
Style/ClosingParenthesisIndentation:
|
80
|
+
Enabled: false
|
81
|
+
|
82
|
+
# Configuration parameters: Keywords.
|
83
|
+
Style/CommentAnnotation:
|
84
|
+
Enabled: false
|
85
|
+
|
86
|
+
Style/Documentation:
|
87
|
+
Enabled: false
|
88
|
+
|
89
|
+
Style/DoubleNegation:
|
90
|
+
Enabled: false
|
91
|
+
|
92
|
+
# Configuration parameters: MinBodyLength.
|
93
|
+
Style/GuardClause:
|
94
|
+
Enabled: false
|
95
|
+
|
96
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
97
|
+
Style/IndentHash:
|
98
|
+
Enabled: false
|
99
|
+
|
100
|
+
Style/Lambda:
|
101
|
+
Enabled: false
|
102
|
+
|
103
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
104
|
+
Style/MultilineOperationIndentation:
|
105
|
+
Enabled: false
|
106
|
+
|
107
|
+
# Configuration parameters: NamePrefix, NamePrefixBlacklist.
|
108
|
+
Style/PredicateName:
|
109
|
+
Enabled: false
|
110
|
+
|
111
|
+
Style/RedundantSelf:
|
112
|
+
Enabled: false
|
113
|
+
|
114
|
+
Style/SingleSpaceBeforeFirstArg:
|
115
|
+
Enabled: false
|
116
|
+
|
117
|
+
# Configuration parameters: MultiSpaceAllowedForOperators.
|
118
|
+
Style/SpaceAroundOperators:
|
119
|
+
Enabled: false
|
120
|
+
|
121
|
+
# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
|
122
|
+
Style/TrivialAccessors:
|
123
|
+
Enabled: false
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
coach (0.0.0)
|
5
|
+
actionpack (~> 4.2)
|
6
|
+
activesupport (~> 4.2)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
actionpack (4.2.1)
|
12
|
+
actionview (= 4.2.1)
|
13
|
+
activesupport (= 4.2.1)
|
14
|
+
rack (~> 1.6)
|
15
|
+
rack-test (~> 0.6.2)
|
16
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
17
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.1)
|
18
|
+
actionview (4.2.1)
|
19
|
+
activesupport (= 4.2.1)
|
20
|
+
builder (~> 3.1)
|
21
|
+
erubis (~> 2.7.0)
|
22
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
23
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.1)
|
24
|
+
activesupport (4.2.1)
|
25
|
+
i18n (~> 0.7)
|
26
|
+
json (~> 1.7, >= 1.7.7)
|
27
|
+
minitest (~> 5.1)
|
28
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
29
|
+
tzinfo (~> 1.1)
|
30
|
+
ast (2.0.0)
|
31
|
+
astrolabe (1.3.0)
|
32
|
+
parser (>= 2.2.0.pre.3, < 3.0)
|
33
|
+
builder (3.2.2)
|
34
|
+
coderay (1.1.0)
|
35
|
+
diff-lcs (1.2.5)
|
36
|
+
erubis (2.7.0)
|
37
|
+
i18n (0.7.0)
|
38
|
+
json (1.8.3)
|
39
|
+
loofah (2.0.2)
|
40
|
+
nokogiri (>= 1.5.9)
|
41
|
+
method_source (0.8.2)
|
42
|
+
mini_portile (0.6.2)
|
43
|
+
minitest (5.7.0)
|
44
|
+
nokogiri (1.6.6.2)
|
45
|
+
mini_portile (~> 0.6.0)
|
46
|
+
parser (2.2.2.5)
|
47
|
+
ast (>= 1.1, < 3.0)
|
48
|
+
powerpack (0.1.1)
|
49
|
+
pry (0.10.1)
|
50
|
+
coderay (~> 1.1.0)
|
51
|
+
method_source (~> 0.8.1)
|
52
|
+
slop (~> 3.4)
|
53
|
+
rack (1.6.1)
|
54
|
+
rack-test (0.6.3)
|
55
|
+
rack (>= 1.0)
|
56
|
+
rails-deprecated_sanitizer (1.0.3)
|
57
|
+
activesupport (>= 4.2.0.alpha)
|
58
|
+
rails-dom-testing (1.0.6)
|
59
|
+
activesupport (>= 4.2.0.beta, < 5.0)
|
60
|
+
nokogiri (~> 1.6.0)
|
61
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
62
|
+
rails-html-sanitizer (1.0.2)
|
63
|
+
loofah (~> 2.0)
|
64
|
+
rainbow (2.0.0)
|
65
|
+
rspec (3.2.0)
|
66
|
+
rspec-core (~> 3.2.0)
|
67
|
+
rspec-expectations (~> 3.2.0)
|
68
|
+
rspec-mocks (~> 3.2.0)
|
69
|
+
rspec-core (3.2.3)
|
70
|
+
rspec-support (~> 3.2.0)
|
71
|
+
rspec-expectations (3.2.1)
|
72
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
73
|
+
rspec-support (~> 3.2.0)
|
74
|
+
rspec-its (1.2.0)
|
75
|
+
rspec-core (>= 3.0.0)
|
76
|
+
rspec-expectations (>= 3.0.0)
|
77
|
+
rspec-mocks (3.2.1)
|
78
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
79
|
+
rspec-support (~> 3.2.0)
|
80
|
+
rspec-support (3.2.2)
|
81
|
+
rubocop (0.32.0)
|
82
|
+
astrolabe (~> 1.3)
|
83
|
+
parser (>= 2.2.2.5, < 3.0)
|
84
|
+
powerpack (~> 0.1)
|
85
|
+
rainbow (>= 1.99.1, < 3.0)
|
86
|
+
ruby-progressbar (~> 1.4)
|
87
|
+
ruby-progressbar (1.7.5)
|
88
|
+
slop (3.6.0)
|
89
|
+
thread_safe (0.3.5)
|
90
|
+
tzinfo (1.2.2)
|
91
|
+
thread_safe (~> 0.1)
|
92
|
+
|
93
|
+
PLATFORMS
|
94
|
+
ruby
|
95
|
+
|
96
|
+
DEPENDENCIES
|
97
|
+
coach!
|
98
|
+
pry
|
99
|
+
rspec (~> 3.2.0)
|
100
|
+
rspec-its (~> 1.2.0)
|
101
|
+
rubocop
|
102
|
+
|
103
|
+
BUNDLED WITH
|
104
|
+
1.10.3
|
data/README.md
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
# Coach
|
2
|
+
For when Rails has you jumping on the tracks...
|
3
|
+
|
4
|
+
Coach improves your controller code by encouraging...
|
5
|
+
|
6
|
+
- **Modularity** - No more tangled `before_filter`'s and interdependent concerns. Build
|
7
|
+
Middleware that does a single job, and does it well.
|
8
|
+
- **Guarantees** - Work with a simple `provide`/`require` interface to guarantee that the
|
9
|
+
data you need in your endpoint is loaded before your app even boots.
|
10
|
+
- **Testability** - Test each middleware in isolation, with effortless mocking of test
|
11
|
+
data and natural RSpec matchers.
|
12
|
+
|
13
|
+
## Simple endpoint
|
14
|
+
|
15
|
+
Lets start by creating a simple endpoint.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
module Routes
|
19
|
+
class Echo < Coach::Middleware
|
20
|
+
def call
|
21
|
+
# All middleware should return rack compliant responses
|
22
|
+
[ 200, {}, [params[:word]] ]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
Any Middlewares have access to the `request` handle, which is an instance of
|
29
|
+
`ActionDispatch::Request`. This also parses the request params, and these are made
|
30
|
+
available inside Middlewares as `params`.
|
31
|
+
|
32
|
+
In an example Rails app, called `Example`, we can mount this route like so...
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
Example::Application.routes.draw do
|
36
|
+
match "/echo/:word",
|
37
|
+
to: Coach::Handler.new(Routes::Echo),
|
38
|
+
via: :get
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
Once booting Rails locally, running `curl -XGET http://localhost:3000/echo/hello` should
|
43
|
+
respond with `'hello'`.
|
44
|
+
|
45
|
+
## Building chains
|
46
|
+
|
47
|
+
Lets try creating a route protected by authentication.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
module Routes
|
51
|
+
class Secret < Coach::Middleware
|
52
|
+
def call
|
53
|
+
unless User.exists?(id: params[:user_id], password: params[:user_password])
|
54
|
+
return [ 401, {}, ['Access denied'] ]
|
55
|
+
end
|
56
|
+
|
57
|
+
[ 200, {}, ['super-secretness'] ]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
The above will verify that a user can be found with the given params, and if it cannot
|
64
|
+
then will respond with a `401`.
|
65
|
+
|
66
|
+
This does what we want it to do, but why should `Secret` know anything about
|
67
|
+
authentication? This complicates `Secret`'s design and prevents reuse of authentication
|
68
|
+
logic.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
module Middleware
|
72
|
+
class Authentication < Coach::Middleware
|
73
|
+
def call
|
74
|
+
unless User.exists?(id: params[:user_id], password: params[:user_password])
|
75
|
+
return [ 401, {}, ['Access denied'] ]
|
76
|
+
end
|
77
|
+
|
78
|
+
next_middleware.call
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
module Routes
|
84
|
+
class Secret < Coach::Middleware
|
85
|
+
uses Middleware::Authentication
|
86
|
+
|
87
|
+
def call
|
88
|
+
[ 200, {}, ['super-secretness'] ]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
Here we detach the authentication logic into it's own middleware. `Secret` now `uses`
|
95
|
+
`Middleware::Authentication`, and will only run if it has been called via
|
96
|
+
`next_middleware.call` from authentication.
|
97
|
+
|
98
|
+
## Passing data through middleware
|
99
|
+
|
100
|
+
Now what happens if you have an endpoint that returns the current auth'd users details? We
|
101
|
+
can maintain the separation of authentication logic and endpoint as below...
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
module Middleware
|
105
|
+
class AuthenticatedUser < Coach::Middleware
|
106
|
+
provides :authenticated_user
|
107
|
+
|
108
|
+
def call
|
109
|
+
user = User.find_by(token: request.headers['Authorization'])
|
110
|
+
return [ 401, {}, ['Access denied'] ] unless user.exists?
|
111
|
+
|
112
|
+
provide(authenticated_user: user)
|
113
|
+
next_middleware.call
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module Routes
|
119
|
+
class Whoami < Coach::Middleware
|
120
|
+
uses AuthenticatedUser
|
121
|
+
requires :authenticated_user
|
122
|
+
|
123
|
+
def call
|
124
|
+
[ 200, {}, [authenticated_user.name] ]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
Each middleware declares what it requires from those that have ran before it, and what it
|
131
|
+
will provide to those that run after. Whenever a middleware chain is mounted, these
|
132
|
+
promises will be verified. In the above, if our `Whoami` middleware had neglected to use
|
133
|
+
`AuthenticatedUser`, then mounting would fail with the error...
|
134
|
+
|
135
|
+
Coach::Errors::MiddlewareDependencyNotMet: AuthenticatedUser requires keys [authenticated_user] that are not provided by the middleware chain
|
136
|
+
|
137
|
+
This static verification eradicates an entire category of errors that stem from implicitly
|
138
|
+
running code before hitting controller methods. It allows you to be confident that the
|
139
|
+
data you require has been loaded, and makes tracing the origin of that data as simple as
|
140
|
+
looking up the chain.
|
141
|
+
|
142
|
+
## Testing
|
143
|
+
|
144
|
+
The basic strategy is to test each middleware in isolation, covering all the edge cases,
|
145
|
+
and then create request specs that cover a happy code path, testing each of the
|
146
|
+
middlewares while they work in sequence.
|
147
|
+
|
148
|
+
Each middleware is encouraged to rely on data passed through the `provide`/`require`
|
149
|
+
syntax exclusively, except in stateful operations (such as database queries). By sticking
|
150
|
+
to this rule, testing becomes as simple as mocking a `context` hash.
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
require 'spec_helper'
|
154
|
+
|
155
|
+
describe "/whoami" do
|
156
|
+
let(:user) { FactoryGirl.create(:user, name: 'Clark Kent', token: 'Kryptonite') }
|
157
|
+
|
158
|
+
context "with correct auth details" do
|
159
|
+
it "responds with user name" do
|
160
|
+
get "/whoami", {}, { 'Authorization' => 'Kryptonite' }
|
161
|
+
expect(response.body).to match(/Clark Kent/)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe Routes::Whoami do
|
167
|
+
subject(:instance) { described_class.new(context) }
|
168
|
+
let(:context) { { authenticated_user: double(name: "Clark Kent") } }
|
169
|
+
|
170
|
+
it { is_expected.to respond_with_body_that_matches(/Clark Kent/) }
|
171
|
+
end
|
172
|
+
|
173
|
+
describe Middleware::AuthenticatedUser do
|
174
|
+
subject(:instance) { described_class.new(context) }
|
175
|
+
let(:context) do
|
176
|
+
{ request: instance_double(ActionDispatch::Request, headers: headers) }
|
177
|
+
end
|
178
|
+
|
179
|
+
let(:user) { FactoryGirl.create(:user, name: 'Clark Kent', token: 'Kryptonite') }
|
180
|
+
|
181
|
+
context "with valid token" do
|
182
|
+
it { is_expected.to call_next_middleware }
|
183
|
+
it { is_expected.to provide(authenticated_user: user) }
|
184
|
+
end
|
185
|
+
|
186
|
+
context "with invalid token" do
|
187
|
+
it { is_expected.to respond_with_status(401) }
|
188
|
+
it { is_expected.to respond_with_body_that_matches(/access denied/i) }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
193
|
+
## Routing
|
194
|
+
|
195
|
+
For routes that represent resource actions, Coach provides some syntactic sugar to
|
196
|
+
allow concise mapping of endpoint to handler.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
router = Coach::Router.new(Example::Application)
|
200
|
+
|
201
|
+
router.draw(Routes::Users,
|
202
|
+
base: "/users",
|
203
|
+
actions: [
|
204
|
+
:index,
|
205
|
+
:show,
|
206
|
+
:create,
|
207
|
+
:update,
|
208
|
+
disable: { method: :post, url: "/:id/actions/disable" }
|
209
|
+
])
|
210
|
+
```
|
211
|
+
|
212
|
+
Default actions that conform to standard REST principles can be easily loaded, with the
|
213
|
+
users resource being mapped to...
|
214
|
+
|
215
|
+
| Method | URL | Description |
|
216
|
+
|--------|------------------------------|------------------------------------------------|
|
217
|
+
| `GET` | `/users` | Index all users |
|
218
|
+
| `GET` | `/users/:id` | Get user by ID |
|
219
|
+
| `POST` | `/users` | Create new user |
|
220
|
+
| `PUT` | `/users/:id` | Update user details |
|
221
|
+
| `POST` | `/users/:id/actions/disable` | Custom action routed to the given path suffix |
|
222
|
+
|
223
|
+
## Rendering
|
224
|
+
|
225
|
+
By now you'll probably agree that the rack response format isn't the nicest way to render
|
226
|
+
responses. Coach comes sans renderer, and for a good reason.
|
227
|
+
|
228
|
+
We initially built a `Coach::Renderer` module, but soon realised that doing so would
|
229
|
+
prevent us from open sourcing. Our `Renderer` was 90% logic specific to the way our APIs
|
230
|
+
function, including handling/formatting of validation errors, logging of unusual events
|
231
|
+
etc.
|
232
|
+
|
233
|
+
What worked well for us is a standalone `Renderer` class that we could require in all our
|
234
|
+
middleware that needed to format responses. This pattern also led to clearer code -
|
235
|
+
consistent with our preference for explicit code, stating `Renderer.new_resource(...)` is
|
236
|
+
instantly more debuggable than an inherited method on all middlewares.
|
237
|
+
|
238
|
+
## Instrumentation
|
239
|
+
|
240
|
+
Coach uses `ActiveSupport::Notifications` to issue events that can be used to profile
|
241
|
+
middleware.
|
242
|
+
|
243
|
+
Information for how to use `ActiveSupport`s notifications can be found
|
244
|
+
[here](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html).
|
245
|
+
|
246
|
+
|
247
|
+
| Event | Arguments |
|
248
|
+
|-------------------------------|------------------------------------------------------- |
|
249
|
+
| `coach.handler.start` | `event(:middleware, :request)` |
|
250
|
+
| `coach.middleware.start` | `event(:middleware, :request)` |
|
251
|
+
| `coach.middleware.finish` | `start`, `finish`, `id`, `event(:middleware, :request)`|
|
252
|
+
| `coach.handler.finish` | `start`, `finish`, `id`, `event(:middleware, :request)`|
|
253
|
+
| `coach.request` | `event` containing request data and benchmarking |
|
254
|
+
|
255
|
+
Of special interest is `coach.request`, which publishes statistics on an entire
|
256
|
+
middleware chain and request. This data is particularly useful for logging, and is our
|
257
|
+
solution to Rails `process_action.action_controller` event emitted on controller requests.
|
258
|
+
|
259
|
+
The benchmarking data includes information on how long each middleware took to process,
|
260
|
+
along with the total duration of the chain.
|