coach 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|