grape-path-helpers 1.6.3 → 1.7.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +40 -0
- data/Gemfile.lock +1 -1
- data/lib/grape-path-helpers/all_routes.rb +18 -5
- data/lib/grape-path-helpers/named_route_matcher.rb +9 -19
- data/lib/grape-path-helpers/route_displayer.rb +1 -1
- data/lib/grape-path-helpers/version.rb +1 -1
- data/spec/grape_path_helpers/named_route_matcher_spec.rb +44 -154
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a9b0351af6ba5b42ba8b521c7bc83a832d4a20a445c30c4c346874f161ddfba
|
4
|
+
data.tar.gz: abe212bb7ab94afa5d12f738a68b2c7c84b93ec807825d5622022ccacb99c5bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c4341eed5f6e6cbbc0f3fa7c35bca36e0f10129948556f6549a4f384e699aa6e6981f61e4c8657e660a7db6d2acfd4adb0b42558bfa58c9e7f8b337014f4f96
|
7
|
+
data.tar.gz: c996a522cfcfb00b275ab276a9ee3e24280a624fa859e4c434937903de0ad3dc004c50231cf37c5abca9ad467b30d29ec0aff131e6e33a58d88b5d1bed5d3499
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.7.0
|
4
|
+
|
5
|
+
* [Further improve performance of route matching](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/38)
|
6
|
+
|
3
7
|
## 1.6.3
|
4
8
|
|
5
9
|
* [Fix route matcher when method ends in path and arg isn't a Hash](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/35)
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
## Developer Certificate of Origin and License
|
2
|
+
|
3
|
+
By contributing to GitLab B.V., you accept and agree to the following terms and
|
4
|
+
conditions for your present and future contributions submitted to GitLab B.V.
|
5
|
+
Except for the license granted herein to GitLab B.V. and recipients of software
|
6
|
+
distributed by GitLab B.V., you reserve all right, title, and interest in and to
|
7
|
+
your Contributions.
|
8
|
+
|
9
|
+
All contributions are subject to the Developer Certificate of Origin and license set out at [docs.gitlab.com/ce/legal/developer_certificate_of_origin](https://docs.gitlab.com/ce/legal/developer_certificate_of_origin).
|
10
|
+
|
11
|
+
_This notice should stay as the first item in the CONTRIBUTING.md file._
|
12
|
+
|
13
|
+
## Code of conduct
|
14
|
+
|
15
|
+
As contributors and maintainers of this project, we pledge to respect all people
|
16
|
+
who contribute through reporting issues, posting feature requests, updating
|
17
|
+
documentation, submitting pull requests or patches, and other activities.
|
18
|
+
|
19
|
+
We are committed to making participation in this project a harassment-free
|
20
|
+
experience for everyone, regardless of level of experience, gender, gender
|
21
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
22
|
+
body size, race, ethnicity, age, or religion.
|
23
|
+
|
24
|
+
Examples of unacceptable behavior by participants include the use of sexual
|
25
|
+
language or imagery, derogatory comments or personal attacks, trolling, public
|
26
|
+
or private harassment, insults, or other unprofessional conduct.
|
27
|
+
|
28
|
+
Project maintainers have the right and responsibility to remove, edit, or reject
|
29
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
30
|
+
not aligned to this Code of Conduct. Project maintainers who do not follow the
|
31
|
+
Code of Conduct may be removed from the project team.
|
32
|
+
|
33
|
+
This code of conduct applies both within project spaces and in public spaces
|
34
|
+
when an individual is representing the project or its community.
|
35
|
+
|
36
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior can be
|
37
|
+
reported by emailing contact@gitlab.com.
|
38
|
+
|
39
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), version 1.1.0,
|
40
|
+
available at [https://contributor-covenant.org/version/1/1/0/](https://contributor-covenant.org/version/1/1/0/).
|
data/Gemfile.lock
CHANGED
@@ -3,11 +3,24 @@ module GrapePathHelpers
|
|
3
3
|
# list of routes from all APIs and decorate them with
|
4
4
|
# the DecoratedRoute class
|
5
5
|
module AllRoutes
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
def decorated_routes_by_helper_name
|
7
|
+
return @decorated_routes_by_helper_name if @decorated_routes_by_helper_name # rubocop:disable Metrics/LineLength
|
8
|
+
|
9
|
+
routes = {}
|
10
|
+
|
11
|
+
all_routes
|
12
|
+
.map { |r| DecoratedRoute.new(r) }
|
13
|
+
.sort_by { |r| -r.dynamic_path_segments.count }
|
14
|
+
.each do |route|
|
15
|
+
route.helper_names.each do |helper_name|
|
16
|
+
key = helper_name.to_sym
|
17
|
+
|
18
|
+
routes[key] ||= []
|
19
|
+
routes[key] << route
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@decorated_routes_by_helper_name = routes
|
11
24
|
end
|
12
25
|
|
13
26
|
def all_routes
|
@@ -3,21 +3,22 @@ module GrapePathHelpers
|
|
3
3
|
# to unknown methods will look for a route with a matching
|
4
4
|
# helper function name
|
5
5
|
module NamedRouteMatcher
|
6
|
-
def method_missing(
|
7
|
-
|
6
|
+
def method_missing(method_name, *args)
|
7
|
+
possible_routes = Grape::API::Instance
|
8
|
+
.decorated_routes_by_helper_name[method_name]
|
9
|
+
return super unless possible_routes
|
8
10
|
|
9
11
|
segments = args.first || {}
|
10
|
-
|
11
12
|
return super unless segments.is_a?(Hash)
|
12
13
|
|
13
14
|
requested_segments = segments.keys.map(&:to_s)
|
14
15
|
|
15
|
-
route =
|
16
|
-
|
16
|
+
route = possible_routes.detect do |r|
|
17
|
+
r.uses_segments_in_path_helper?(requested_segments)
|
17
18
|
end
|
18
19
|
|
19
20
|
if route
|
20
|
-
route.send(
|
21
|
+
route.send(method_name, *args)
|
21
22
|
else
|
22
23
|
super
|
23
24
|
end
|
@@ -25,19 +26,8 @@ module GrapePathHelpers
|
|
25
26
|
ruby2_keywords(:method_missing)
|
26
27
|
|
27
28
|
def respond_to_missing?(method_name, _include_private = false)
|
28
|
-
|
29
|
-
|
30
|
-
Grape::API::Instance.decorated_routes.detect do |route|
|
31
|
-
return true if route.respond_to?(method_name)
|
32
|
-
end
|
33
|
-
|
34
|
-
super
|
35
|
-
end
|
36
|
-
|
37
|
-
def route_match?(route, method_name, requested_segments)
|
38
|
-
return false unless route.respond_to?(method_name)
|
39
|
-
|
40
|
-
route.uses_segments_in_path_helper?(requested_segments)
|
29
|
+
!Grape::API::Instance.decorated_routes_by_helper_name[method_name].nil? ||
|
30
|
+
super
|
41
31
|
end
|
42
32
|
end
|
43
33
|
end
|
@@ -3,7 +3,7 @@ module GrapePathHelpers
|
|
3
3
|
# and required arguments for every Grape::Route.
|
4
4
|
class RouteDisplayer
|
5
5
|
def route_attributes
|
6
|
-
Grape::API::Instance.decorated_routes.map do |route|
|
6
|
+
Grape::API::Instance.decorated_routes.values.flatten.uniq.map do |route|
|
7
7
|
{
|
8
8
|
route_path: route.route_path,
|
9
9
|
route_method: route.route_method,
|
@@ -4,28 +4,6 @@ require 'spec_helper'
|
|
4
4
|
describe GrapePathHelpers::NamedRouteMatcher do
|
5
5
|
include described_class
|
6
6
|
|
7
|
-
let(:routes) do
|
8
|
-
Grape::API::Instance.decorated_routes
|
9
|
-
end
|
10
|
-
|
11
|
-
let(:ping_route) do
|
12
|
-
routes.detect do |route|
|
13
|
-
route.route_path =~ /ping/ && route.route_version == 'v1'
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
let(:index_route) do
|
18
|
-
routes.detect do |route|
|
19
|
-
route.route_namespace =~ /cats$/
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
let(:show_route) do
|
24
|
-
routes.detect do |route|
|
25
|
-
route.route_namespace =~ %r{cats/:id}
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
7
|
let(:helper_class) do
|
30
8
|
fake_class = Class.new do
|
31
9
|
prepend GrapePathHelpers::NamedRouteMatcher
|
@@ -43,130 +21,46 @@ describe GrapePathHelpers::NamedRouteMatcher do
|
|
43
21
|
end
|
44
22
|
|
45
23
|
describe '#method_missing' do
|
46
|
-
it 'returns super method_missing if the
|
47
|
-
expect(
|
48
|
-
|
49
|
-
expect(helper_class.test_method(:arg1, kwarg1: :kwarg1))
|
50
|
-
.to eq([:test_method, [:arg1], { kwarg1: :kwarg1 }])
|
24
|
+
it 'returns super method_missing if the route does not exist' do
|
25
|
+
expect(helper_class.test_method(id: 1))
|
26
|
+
.to eq([:test_method, [], { id: 1 }])
|
51
27
|
end
|
52
28
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
expect(helper_class.test_method_path(:arg1, kwarg1: :kwarg1))
|
58
|
-
.to eq([:test_method_path, [:arg1], { kwarg1: :kwarg1 }])
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'search for the route if there are no args' do
|
62
|
-
expect(Grape::API::Instance).to receive(:decorated_routes).and_call_original # rubocop:disable Metrics/LineLength
|
63
|
-
|
64
|
-
helper_class.test_method_path
|
65
|
-
end
|
66
|
-
|
67
|
-
it 'search for the route if the first arg is a hash' do
|
68
|
-
expect(Grape::API::Instance).to receive(:decorated_routes).and_call_original # rubocop:disable Metrics/LineLength
|
69
|
-
|
70
|
-
helper_class.test_method_path(id: 5)
|
71
|
-
end
|
29
|
+
it 'returns super method_missing if first arg is not a hash' do
|
30
|
+
expect(helper_class.api_v1_cats_path(:arg1, kwarg1: :kwarg1))
|
31
|
+
.to eq([:api_v1_cats_path, [:arg1], { kwarg1: :kwarg1 }])
|
72
32
|
end
|
73
|
-
end
|
74
|
-
|
75
|
-
describe '#route_match?' do
|
76
|
-
context 'when route responds to a method name' do
|
77
|
-
let(:route) { ping_route }
|
78
|
-
let(:method_name) { :api_v1_ping_path }
|
79
|
-
let(:segments) { {} }
|
80
33
|
|
81
|
-
|
82
|
-
|
83
|
-
expect(
|
34
|
+
context 'when method name matches a Grape::Route path helper name' do
|
35
|
+
it 'returns the path for that route object' do
|
36
|
+
expect(helper_class.api_v1_ping_path).to eq('/api/v1/ping.json')
|
84
37
|
end
|
85
38
|
|
86
|
-
context 'when
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
is_match = route_match?(route, method_name, segments)
|
91
|
-
expect(is_match).to eq(true)
|
92
|
-
end
|
93
|
-
|
94
|
-
context 'when no dynamic segments are requested' do
|
95
|
-
context 'when the route requires dynamic segments' do
|
96
|
-
let(:route) { show_route }
|
97
|
-
let(:method_name) { :ap1_v1_cats_path }
|
98
|
-
|
99
|
-
it 'returns false' do
|
100
|
-
is_match = route_match?(route, method_name, segments)
|
101
|
-
expect(is_match).to eq(false)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
context 'when the route does not require dynamic segments' do
|
106
|
-
it 'returns true' do
|
107
|
-
is_match = route_match?(route, method_name, segments)
|
108
|
-
expect(is_match).to eq(true)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
context 'when route requires the requested segments' do
|
114
|
-
let(:route) { show_route }
|
115
|
-
let(:method_name) { :api_v1_cats_path }
|
116
|
-
let(:segments) { { 'id' => 1 } }
|
117
|
-
|
118
|
-
it 'returns true' do
|
119
|
-
is_match = route_match?(route, method_name, segments)
|
120
|
-
expect(is_match).to eq(true)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
context 'when route does not require the requested segments' do
|
125
|
-
let(:segments) { { 'some_option' => 'some value' } }
|
126
|
-
|
127
|
-
it 'returns false' do
|
128
|
-
is_match = route_match?(route, method_name, segments)
|
129
|
-
expect(is_match).to eq(false)
|
130
|
-
end
|
39
|
+
context 'when route contains dynamic segments' do
|
40
|
+
it 'returns the path for that route object' do
|
41
|
+
expect(helper_class.api_v1_cats_path(id: 5))
|
42
|
+
.to eq('/api/v1/cats/5.json')
|
131
43
|
end
|
132
44
|
end
|
133
45
|
|
134
|
-
context 'when segments
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
is_match = route_match?(route, method_name, segments)
|
139
|
-
expect(is_match).to eq(false)
|
46
|
+
context 'when route requires dynamic segments but none are passed in' do
|
47
|
+
it 'returns super method_missing' do
|
48
|
+
expect(helper_class.api_v1_cats_owners_path)
|
49
|
+
.to eq([:api_v1_cats_owners_path, [], {}])
|
140
50
|
end
|
141
51
|
end
|
142
|
-
end
|
143
52
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
it 'returns false' do
|
150
|
-
is_match = route_match?(route, method_name, segments)
|
151
|
-
expect(is_match).to eq(false)
|
53
|
+
context 'when route has no dynamic segments but some are passed in' do
|
54
|
+
it 'returns super method_missing' do
|
55
|
+
expect(helper_class.api_v1_ping_path(invalid: 'test'))
|
56
|
+
.to eq([:api_v1_ping_path, [], { invalid: 'test' }])
|
57
|
+
end
|
152
58
|
end
|
153
59
|
end
|
154
60
|
end
|
155
61
|
|
156
62
|
describe '#respond_to_missing?' do
|
157
|
-
|
158
|
-
expect(Grape::API::Instance).not_to receive(:decorated_routes) # rubocop:disable Metrics/LineLength
|
159
|
-
|
160
|
-
expect(helper_class.send(:respond_to_missing?, :test)).to eq(false)
|
161
|
-
end
|
162
|
-
|
163
|
-
it 'search for the route if the method ends with path' do
|
164
|
-
expect(Grape::API::Instance).to receive(:decorated_routes).and_call_original # rubocop:disable Metrics/LineLength
|
165
|
-
|
166
|
-
expect(helper_class.send(:respond_to_missing?, :test_path)).to eq(false)
|
167
|
-
end
|
168
|
-
|
169
|
-
context 'when method name with segments matches a Grape::Route path' do
|
63
|
+
context 'when method name matches a Grape::Route path with segments' do
|
170
64
|
let(:method_name) { :api_v1_cats_path }
|
171
65
|
|
172
66
|
it 'returns true' do
|
@@ -174,7 +68,7 @@ describe GrapePathHelpers::NamedRouteMatcher do
|
|
174
68
|
end
|
175
69
|
end
|
176
70
|
|
177
|
-
context 'when method name matches a Grape::Route path
|
71
|
+
context 'when method name matches a Grape::Route path' do
|
178
72
|
let(:method_name) { :api_v1_ping_path }
|
179
73
|
|
180
74
|
it 'returns true' do
|
@@ -182,7 +76,7 @@ describe GrapePathHelpers::NamedRouteMatcher do
|
|
182
76
|
end
|
183
77
|
end
|
184
78
|
|
185
|
-
context 'when method name does not match a Grape::Route path
|
79
|
+
context 'when method name does not match a Grape::Route path' do
|
186
80
|
let(:method_name) { :some_other_path }
|
187
81
|
|
188
82
|
it 'returns false' do
|
@@ -191,48 +85,44 @@ describe GrapePathHelpers::NamedRouteMatcher do
|
|
191
85
|
end
|
192
86
|
end
|
193
87
|
|
194
|
-
describe '#method_missing' do
|
195
|
-
context 'when method name matches a Grape::Route path helper name' do
|
196
|
-
it 'returns the path for that route object' do
|
197
|
-
path = api_v1_ping_path
|
198
|
-
expect(path).to eq('/api/v1/ping.json')
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
context 'when method name does not match a Grape::Route path helper name' do
|
203
|
-
it 'raises a NameError' do
|
204
|
-
expect do
|
205
|
-
some_method_name
|
206
|
-
end.to raise_error(NameError)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
88
|
context 'when Grape::Route objects share the same helper name' do
|
212
89
|
context 'when helpers require different segments to generate their path' do
|
213
90
|
it 'uses arguments to infer which route to use' do
|
214
|
-
show_path = api_v1_cats_path(
|
91
|
+
show_path = helper_class.api_v1_cats_path(
|
92
|
+
'id' => 1
|
93
|
+
)
|
215
94
|
expect(show_path).to eq('/api/v1/cats/1.json')
|
216
95
|
|
217
|
-
index_path = api_v1_cats_path
|
96
|
+
index_path = helper_class.api_v1_cats_path
|
218
97
|
expect(index_path).to eq('/api/v1/cats.json')
|
219
98
|
end
|
220
99
|
|
221
100
|
it 'does not get shadowed by another route with less segments' do
|
222
|
-
show_path = api_v1_cats_owners_path(
|
101
|
+
show_path = helper_class.api_v1_cats_owners_path(
|
102
|
+
'id' => 1
|
103
|
+
)
|
223
104
|
expect(show_path).to eq('/api/v1/cats/1/owners.json')
|
224
105
|
|
225
|
-
show_path = api_v1_cats_owners_path(
|
106
|
+
show_path = helper_class.api_v1_cats_owners_path(
|
107
|
+
'id' => 1,
|
108
|
+
'owner_id' => 1
|
109
|
+
)
|
226
110
|
expect(show_path).to eq('/api/v1/cats/1/owners/1.json')
|
227
111
|
end
|
228
112
|
end
|
229
113
|
|
230
114
|
context 'when query params are passed in' do
|
231
115
|
it 'uses arguments to infer which route to use' do
|
232
|
-
show_path = api_v1_cats_path(
|
116
|
+
show_path = helper_class.api_v1_cats_path(
|
117
|
+
'id' => 1,
|
118
|
+
params: { 'foo' => 'bar' }
|
119
|
+
)
|
120
|
+
|
233
121
|
expect(show_path).to eq('/api/v1/cats/1.json?foo=bar')
|
234
122
|
|
235
|
-
index_path = api_v1_cats_path(
|
123
|
+
index_path = helper_class.api_v1_cats_path(
|
124
|
+
params: { 'foo' => 'bar' }
|
125
|
+
)
|
236
126
|
expect(index_path).to eq('/api/v1/cats.json?foo=bar')
|
237
127
|
end
|
238
128
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape-path-helpers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Drew Blessing
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
12
|
+
date: 2021-08-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -120,6 +120,7 @@ files:
|
|
120
120
|
- ".rubocop.yml"
|
121
121
|
- ".travis.yml"
|
122
122
|
- CHANGELOG.md
|
123
|
+
- CONTRIBUTING.md
|
123
124
|
- Gemfile
|
124
125
|
- Gemfile.lock
|
125
126
|
- LICENSE.txt
|