grape-path-helpers 1.6.3 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bc6c862d4ba4b9cedabaafaf461382721dd87e45cc7796ff13d9a7c643d1d46
4
- data.tar.gz: d1198670aad0447e4b50821a19e460daced596a51de2ddc0155fd6adea5146aa
3
+ metadata.gz: 4a9b0351af6ba5b42ba8b521c7bc83a832d4a20a445c30c4c346874f161ddfba
4
+ data.tar.gz: abe212bb7ab94afa5d12f738a68b2c7c84b93ec807825d5622022ccacb99c5bd
5
5
  SHA512:
6
- metadata.gz: 729437ba35e3b8d4e2133dc96d11c9b5eca8a40965099acee11c7795f0514248bf7d45a824bc13cfceeb02e31fff0057f1a8ead11bc9e2f6d629af1b17910ed3
7
- data.tar.gz: 520a330a0d892189dc2bd4862a717b41423f7c98794082592a961fdddbdee4342b1dbad0a94a9a4a98dbee2fa05c58bdc4c6ff398cddfbd74ad206622fb09fce
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grape-path-helpers (1.6.3)
4
+ grape-path-helpers (1.7.0)
5
5
  activesupport
6
6
  grape (~> 1.3)
7
7
  rake (> 12)
@@ -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 decorated_routes
7
- # memoize so that construction of decorated routes happens once
8
- @decorated_routes ||= all_routes
9
- .map { |r| DecoratedRoute.new(r) }
10
- .sort_by { |r| -r.dynamic_path_segments.count }
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(method_id, *args)
7
- return super unless method_id.to_s =~ /_path$/
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 = Grape::API::Instance.decorated_routes.detect do |r|
16
- route_match?(r, method_id, requested_segments)
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(method_id, *args)
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
- return super unless method_name =~ /_path$/
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,
@@ -1,4 +1,4 @@
1
1
  # Gem version
2
2
  module GrapePathHelpers
3
- VERSION = '1.6.3'.freeze
3
+ VERSION = '1.7.0'.freeze
4
4
  end
@@ -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 method does not end with path' do
47
- expect(Grape::API::Instance).not_to receive(:decorated_routes)
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
- context 'when method ends with path' do
54
- it 'returns super method_missing if first arg is not a hash' do
55
- expect(Grape::API::Instance).not_to receive(:decorated_routes)
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
- it 'returns true' do
82
- is_match = route_match?(route, method_name, segments)
83
- expect(is_match).to eq(true)
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 requested segments contains expected options' do
87
- let(:segments) { { 'format' => 'xml' } }
88
-
89
- it 'returns true' do
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 contains unexpected options' do
135
- let(:segments) { { 'some_option' => 'some value' } }
136
-
137
- it 'returns false' do
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
- context 'when route does not respond to a method name' do
145
- let(:method_name) { :some_other_path }
146
- let(:route) { ping_route }
147
- let(:segments) { {} }
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
- it 'returns super if the method does not end with path' do
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 helper name' do
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 helper name' do
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('id' => 1)
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('id' => 1)
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('id' => 1, 'owner_id' => 1)
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('id' => 1, params: { 'foo' => 'bar' })
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(params: { 'foo' => 'bar' })
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.6.3
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-05-07 00:00:00.000000000 Z
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