grape-route-helpers 1.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/.gitignore +2 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +102 -0
- data/LICENSE.txt +22 -0
- data/README.md +118 -0
- data/grape-route-helpers.gemspec +21 -0
- data/lib/grape/route_helpers.rb +1 -0
- data/lib/grape-route-helpers/all_routes.rb +15 -0
- data/lib/grape-route-helpers/decorated_route.rb +134 -0
- data/lib/grape-route-helpers/named_route_matcher.rb +28 -0
- data/lib/grape-route-helpers/railtie.rb +9 -0
- data/lib/grape-route-helpers/route_displayer.rb +29 -0
- data/lib/grape-route-helpers/tasks.rb +1 -0
- data/lib/grape-route-helpers/version.rb +4 -0
- data/lib/grape-route-helpers.rb +16 -0
- data/lib/tasks/grape_route_helpers.rake +6 -0
- data/spec/grape_route_helpers/decorated_route_spec.rb +169 -0
- data/spec/grape_route_helpers/named_route_matcher_spec.rb +164 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/route_matcher_helpers.rb +34 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 70edfbe7fe2afd10d2030ffbfec49dc642f57b40
|
4
|
+
data.tar.gz: 86b00458bc103bbd7c953bf4842ccedae8d4f9cd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5c98fd40561face8eb970ce8ac4d7de721835999a137f3ca6f25fd2761ac96160833d2c996f9d497a41202e8d51ea4c2cc7f95b17a13469d53c4f152c4b5dc8b
|
7
|
+
data.tar.gz: d6519d1439e8e86d67469d3cbac343d7f033123dd673a488469e800f3fa2567a152f6225b993a28d719f6ee68ce278ddbd4085a8648c131193fae90a544f82c0
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
grape-route-helpers (1.0.0)
|
5
|
+
activesupport
|
6
|
+
grape
|
7
|
+
rake
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activesupport (4.2.3)
|
13
|
+
i18n (~> 0.7)
|
14
|
+
json (~> 1.7, >= 1.7.7)
|
15
|
+
minitest (~> 5.1)
|
16
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
17
|
+
tzinfo (~> 1.1)
|
18
|
+
ast (2.0.0)
|
19
|
+
astrolabe (1.3.0)
|
20
|
+
parser (>= 2.2.0.pre.3, < 3.0)
|
21
|
+
axiom-types (0.1.1)
|
22
|
+
descendants_tracker (~> 0.0.4)
|
23
|
+
ice_nine (~> 0.11.0)
|
24
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
25
|
+
builder (3.2.2)
|
26
|
+
coderay (1.1.0)
|
27
|
+
coercible (1.0.0)
|
28
|
+
descendants_tracker (~> 0.0.1)
|
29
|
+
descendants_tracker (0.0.4)
|
30
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
31
|
+
diff-lcs (1.2.5)
|
32
|
+
equalizer (0.0.11)
|
33
|
+
grape (0.12.0)
|
34
|
+
activesupport
|
35
|
+
builder
|
36
|
+
hashie (>= 2.1.0)
|
37
|
+
multi_json (>= 1.3.2)
|
38
|
+
multi_xml (>= 0.5.2)
|
39
|
+
rack (>= 1.3.0)
|
40
|
+
rack-accept
|
41
|
+
rack-mount
|
42
|
+
virtus (>= 1.0.0)
|
43
|
+
hashie (3.4.2)
|
44
|
+
i18n (0.7.0)
|
45
|
+
ice_nine (0.11.1)
|
46
|
+
json (1.8.3)
|
47
|
+
method_source (0.8.2)
|
48
|
+
minitest (5.7.0)
|
49
|
+
multi_json (1.11.1)
|
50
|
+
multi_xml (0.5.5)
|
51
|
+
parser (2.3.0.pre.2)
|
52
|
+
ast (>= 1.1, < 3.0)
|
53
|
+
powerpack (0.1.1)
|
54
|
+
pry (0.10.1)
|
55
|
+
coderay (~> 1.1.0)
|
56
|
+
method_source (~> 0.8.1)
|
57
|
+
slop (~> 3.4)
|
58
|
+
rack (1.6.4)
|
59
|
+
rack-accept (0.4.5)
|
60
|
+
rack (>= 0.4)
|
61
|
+
rack-mount (0.8.3)
|
62
|
+
rack (>= 1.0.0)
|
63
|
+
rainbow (2.0.0)
|
64
|
+
rake (10.4.2)
|
65
|
+
rspec (3.3.0)
|
66
|
+
rspec-core (~> 3.3.0)
|
67
|
+
rspec-expectations (~> 3.3.0)
|
68
|
+
rspec-mocks (~> 3.3.0)
|
69
|
+
rspec-core (3.3.1)
|
70
|
+
rspec-support (~> 3.3.0)
|
71
|
+
rspec-expectations (3.3.0)
|
72
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
73
|
+
rspec-support (~> 3.3.0)
|
74
|
+
rspec-mocks (3.3.1)
|
75
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
76
|
+
rspec-support (~> 3.3.0)
|
77
|
+
rspec-support (3.3.0)
|
78
|
+
rubocop (0.32.1)
|
79
|
+
astrolabe (~> 1.3)
|
80
|
+
parser (>= 2.2.2.5, < 3.0)
|
81
|
+
powerpack (~> 0.1)
|
82
|
+
rainbow (>= 1.99.1, < 3.0)
|
83
|
+
ruby-progressbar (~> 1.4)
|
84
|
+
ruby-progressbar (1.7.5)
|
85
|
+
slop (3.6.0)
|
86
|
+
thread_safe (0.3.5)
|
87
|
+
tzinfo (1.2.2)
|
88
|
+
thread_safe (~> 0.1)
|
89
|
+
virtus (1.0.5)
|
90
|
+
axiom-types (~> 0.1)
|
91
|
+
coercible (~> 1.0)
|
92
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
93
|
+
equalizer (~> 0.0, >= 0.0.9)
|
94
|
+
|
95
|
+
PLATFORMS
|
96
|
+
ruby
|
97
|
+
|
98
|
+
DEPENDENCIES
|
99
|
+
grape-route-helpers!
|
100
|
+
pry
|
101
|
+
rspec
|
102
|
+
rubocop
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Harper Henn
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# grape-route-helpers
|
2
|
+
|
3
|
+
Provides named route helpers for Grape APIs, similar to [Rails' route helpers](http://edgeguides.rubyonrails.org/routing.html#path-and-url-helpers).
|
4
|
+
|
5
|
+
### Installation
|
6
|
+
|
7
|
+
#### Rails
|
8
|
+
|
9
|
+
1.) Add the gem to your Gemfile.
|
10
|
+
|
11
|
+
```bash
|
12
|
+
$ bundle install grape-route-helpers
|
13
|
+
```
|
14
|
+
|
15
|
+
#### Sinatra/Rack
|
16
|
+
|
17
|
+
1.) Add the gem to your Gemfile if you're using Bundler.
|
18
|
+
|
19
|
+
If you're not using Bundler to install/manage dependencies:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ gem install grape-route-helpers
|
23
|
+
```
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# environment setup file
|
27
|
+
require 'grape'
|
28
|
+
require 'grape/route_helpers'
|
29
|
+
```
|
30
|
+
|
31
|
+
2.) Write a rake task called `:environment` that loads the application's environment first. This gem's tasks are dependent on it. You could put this in the root of your project directory:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# Rakefile
|
35
|
+
|
36
|
+
require 'rake'
|
37
|
+
require 'bundler'
|
38
|
+
Bundler.setup
|
39
|
+
require 'grape-route-helpers'
|
40
|
+
require 'grape-route-helpers/tasks'
|
41
|
+
|
42
|
+
desc 'load the Sinatra environment.'
|
43
|
+
task :environment do
|
44
|
+
require File.expand_path('your_app_file', File.dirname(__FILE__))
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
### Usage examples
|
49
|
+
|
50
|
+
To see which methods correspond to which paths, and which options you can pass them:
|
51
|
+
|
52
|
+
```bash
|
53
|
+
$ rake grape:route_helpers
|
54
|
+
```
|
55
|
+
|
56
|
+
Use the methods inside your Grape API actions. Given this example API:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class ExampleAPI < Grape::API
|
60
|
+
version 'v1'
|
61
|
+
prefix 'api'
|
62
|
+
format 'json'
|
63
|
+
|
64
|
+
get 'ping' do
|
65
|
+
'pong'
|
66
|
+
end
|
67
|
+
|
68
|
+
resource :cats do
|
69
|
+
get '/' do
|
70
|
+
%w(cats cats cats)
|
71
|
+
end
|
72
|
+
|
73
|
+
route_param :id do
|
74
|
+
get do
|
75
|
+
'cat'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
route :any, '*anything' do
|
81
|
+
redirect api_v1_cats_path
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
You'd have the following methods available inside your Grape API actions:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
# specifying the version when using Grape's "path" versioning strategy
|
90
|
+
api_v1_ping_path # => '/api/v1/ping'
|
91
|
+
|
92
|
+
# specifying the format
|
93
|
+
api_v1_cats_path(format: 'xml') # => '/api/v1/cats.xml'
|
94
|
+
|
95
|
+
# passing in values required to build a path
|
96
|
+
api_v1_cats_path(id: 1) # => '/api/v1/cats/1'
|
97
|
+
|
98
|
+
# catch-all paths have helpers
|
99
|
+
api_v1_anything_path # => '/api/v1/*anything'
|
100
|
+
```
|
101
|
+
|
102
|
+
### Contributing
|
103
|
+
|
104
|
+
1.) Fork it
|
105
|
+
|
106
|
+
2.) Create your feature branch `(git checkout -b my-new-feature)`
|
107
|
+
|
108
|
+
3.) Write specs for your feature
|
109
|
+
|
110
|
+
4.) Commit your changes `(git commit -am 'Add some feature')`
|
111
|
+
|
112
|
+
5.) Push to the branch `(git push origin my-new-feature)`
|
113
|
+
|
114
|
+
6.) Create a new pull request
|
115
|
+
|
116
|
+
### License
|
117
|
+
|
118
|
+
See LICENSE
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.join(Dir.pwd, 'lib', 'grape-route-helpers', 'version')
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'grape-route-helpers'
|
5
|
+
gem.version = GrapeRouteHelpers::VERSION
|
6
|
+
gem.licenses = ['MIT']
|
7
|
+
gem.summary = 'Route helpers for Grape'
|
8
|
+
gem.description = 'Route helpers for Grape'
|
9
|
+
gem.authors = ['Harper Henn']
|
10
|
+
gem.email = 'harper.henn@legitscript.com'
|
11
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
12
|
+
gem.homepage = 'https://github.com/reprah/grape-route-helpers'
|
13
|
+
|
14
|
+
gem.add_runtime_dependency 'grape'
|
15
|
+
gem.add_runtime_dependency 'activesupport'
|
16
|
+
gem.add_runtime_dependency 'rake'
|
17
|
+
|
18
|
+
gem.add_development_dependency 'pry'
|
19
|
+
gem.add_development_dependency 'rspec'
|
20
|
+
gem.add_development_dependency 'rubocop'
|
21
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'grape-route-helpers'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module GrapeRouteHelpers
|
2
|
+
# methods to extend Grape::API's behavior so it can get a
|
3
|
+
# list of routes from all APIs and decorate them with
|
4
|
+
# the DecoratedRoute class
|
5
|
+
module AllRoutes
|
6
|
+
def decorated_routes
|
7
|
+
# memoize so that construction of decorated routes happens once
|
8
|
+
@decorated_routes ||= all_routes.map { |r| DecoratedRoute.new(r) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def all_routes
|
12
|
+
subclasses.flat_map { |s| s.send(:prepare_routes) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module GrapeRouteHelpers
|
2
|
+
# wrapper around Grape::Route that adds a helper method
|
3
|
+
class DecoratedRoute
|
4
|
+
attr_reader :route, :helper_names, :helper_arguments
|
5
|
+
|
6
|
+
def initialize(route)
|
7
|
+
@route = route
|
8
|
+
@helper_names = []
|
9
|
+
@helper_arguments = required_helper_segments
|
10
|
+
define_path_helpers
|
11
|
+
end
|
12
|
+
|
13
|
+
def define_path_helpers
|
14
|
+
route_versions.each do |version|
|
15
|
+
route_attributes = { version: version }
|
16
|
+
method_name = path_helper_name(route_attributes)
|
17
|
+
@helper_names << method_name
|
18
|
+
define_path_helper(method_name, route_attributes)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def define_path_helper(method_name, route_attributes)
|
23
|
+
method_body = <<-RUBY
|
24
|
+
def #{method_name}(attributes = {})
|
25
|
+
attrs = HashWithIndifferentAccess.new(
|
26
|
+
#{route_attributes}.merge(attributes)
|
27
|
+
)
|
28
|
+
|
29
|
+
content_type = attrs.delete(:format)
|
30
|
+
path = '/' + path_segments_with_values(attrs).join('/')
|
31
|
+
extension = content_type ? '.' + content_type : ''
|
32
|
+
|
33
|
+
path + extension
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
instance_eval method_body
|
37
|
+
end
|
38
|
+
|
39
|
+
def route_versions
|
40
|
+
if route_version
|
41
|
+
route_version.split('|')
|
42
|
+
else
|
43
|
+
[nil]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def path_helper_name(opts = {})
|
48
|
+
segments = path_segments_with_values(opts)
|
49
|
+
|
50
|
+
name = if segments.empty?
|
51
|
+
'root'
|
52
|
+
else
|
53
|
+
segments.join('_')
|
54
|
+
end
|
55
|
+
name + '_path'
|
56
|
+
end
|
57
|
+
|
58
|
+
def segment_to_value(segment, opts = {})
|
59
|
+
options = HashWithIndifferentAccess.new(
|
60
|
+
route_options.merge(opts)
|
61
|
+
)
|
62
|
+
|
63
|
+
if dynamic_segment?(segment)
|
64
|
+
key = segment.slice(1..-1)
|
65
|
+
options[key]
|
66
|
+
else
|
67
|
+
segment
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def path_segments_with_values(opts)
|
72
|
+
segments = path_segments.map { |s| segment_to_value(s, opts) }
|
73
|
+
segments.reject(&:blank?)
|
74
|
+
end
|
75
|
+
|
76
|
+
def path_segments
|
77
|
+
pattern = %r{\(/?\.:?\w+\)|/|\*}
|
78
|
+
route_path.split(pattern).reject(&:blank?)
|
79
|
+
end
|
80
|
+
|
81
|
+
def dynamic_path_segments
|
82
|
+
segments = path_segments.select do |segment|
|
83
|
+
dynamic_segment?(segment)
|
84
|
+
end
|
85
|
+
segments.map { |s| s.slice(1..-1) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def dynamic_segment?(segment)
|
89
|
+
segment.start_with?(':')
|
90
|
+
end
|
91
|
+
|
92
|
+
def required_helper_segments
|
93
|
+
segments_in_options = dynamic_path_segments.select do |segment|
|
94
|
+
route_options[segment.to_sym]
|
95
|
+
end
|
96
|
+
dynamic_path_segments - segments_in_options
|
97
|
+
end
|
98
|
+
|
99
|
+
def optional_segments
|
100
|
+
['format']
|
101
|
+
end
|
102
|
+
|
103
|
+
def uses_segments_in_path_helper?(segments)
|
104
|
+
requested = segments - optional_segments
|
105
|
+
required = required_helper_segments
|
106
|
+
|
107
|
+
if requested.empty? && required.empty?
|
108
|
+
true
|
109
|
+
else
|
110
|
+
requested.all? do |segment|
|
111
|
+
required.include?(segment)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# accessing underlying Grape::Route
|
117
|
+
|
118
|
+
def route_path
|
119
|
+
route.route_path
|
120
|
+
end
|
121
|
+
|
122
|
+
def route_options
|
123
|
+
route.instance_variable_get(:@options)
|
124
|
+
end
|
125
|
+
|
126
|
+
def route_version
|
127
|
+
route.route_version
|
128
|
+
end
|
129
|
+
|
130
|
+
def route_namespace
|
131
|
+
route.route_namespace
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module GrapeRouteHelpers
|
2
|
+
# methods to extend Grape::Endpoint so that calls
|
3
|
+
# to unknown methods will look for a route with a matching
|
4
|
+
# helper function name
|
5
|
+
module NamedRouteMatcher
|
6
|
+
def method_missing(method_id, *arguments)
|
7
|
+
segments = arguments.first || {}
|
8
|
+
|
9
|
+
route = Grape::API.decorated_routes.detect do |r|
|
10
|
+
route_match?(r, method_id, segments)
|
11
|
+
end
|
12
|
+
|
13
|
+
if route
|
14
|
+
route.send(method_id, *arguments)
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def route_match?(route, method_name, segments)
|
21
|
+
return false unless route.respond_to?(method_name)
|
22
|
+
fail ArgumentError,
|
23
|
+
'Helper options must be a hash' unless segments.is_a?(Hash)
|
24
|
+
requested_segments = segments.keys.map(&:to_s)
|
25
|
+
route.uses_segments_in_path_helper?(requested_segments)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GrapeRouteHelpers
|
2
|
+
# class for displaying the path, helper method name,
|
3
|
+
# and required arguments for every Grape::Route.
|
4
|
+
class RouteDisplayer
|
5
|
+
def route_attributes
|
6
|
+
Grape::API.decorated_routes.map do |route|
|
7
|
+
{
|
8
|
+
route_path: route.route_path,
|
9
|
+
helper_names: route.helper_names,
|
10
|
+
helper_arguments: route.helper_arguments
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def display
|
16
|
+
puts("== GRAPE ROUTE HELPERS ==\n\n")
|
17
|
+
route_attributes.each do |attributes|
|
18
|
+
printf("%s: %s\n", 'Path', attributes[:route_path])
|
19
|
+
printf("%s: %s\n",
|
20
|
+
'Helper Method',
|
21
|
+
attributes[:helper_names].join(', '))
|
22
|
+
printf("%s: %s\n",
|
23
|
+
'Arguments',
|
24
|
+
attributes[:helper_arguments].join(', '))
|
25
|
+
puts("\n")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each { |f| load f }
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'grape'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext/class'
|
4
|
+
|
5
|
+
require 'grape-route-helpers/decorated_route'
|
6
|
+
require 'grape-route-helpers/named_route_matcher'
|
7
|
+
require 'grape-route-helpers/all_routes'
|
8
|
+
require 'grape-route-helpers/route_displayer'
|
9
|
+
|
10
|
+
#
|
11
|
+
module GrapeRouteHelpers
|
12
|
+
require 'grape-route-helpers/railtie' if defined?(Rails)
|
13
|
+
end
|
14
|
+
|
15
|
+
Grape::API.extend GrapeRouteHelpers::AllRoutes
|
16
|
+
Grape::Endpoint.send(:include, GrapeRouteHelpers::NamedRouteMatcher)
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GrapeRouteHelpers::DecoratedRoute do
|
4
|
+
let(:api) { Spec::Support::RouteMatcherHelpers.api }
|
5
|
+
|
6
|
+
let(:routes) do
|
7
|
+
api.routes.map do |route|
|
8
|
+
described_class.new(route)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:index_route) do
|
13
|
+
routes.detect { |route| route.route_namespace == '/cats' }
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:show_route) do
|
17
|
+
routes.detect { |route| route.route_namespace == '/cats/:id' }
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:catch_all_route) do
|
21
|
+
routes.detect { |route| route.route_path =~ /\*/ }
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#helper_names' do
|
25
|
+
context 'when an API has multiple versions' do
|
26
|
+
let(:api_versions) { %w(beta alpha v1) }
|
27
|
+
|
28
|
+
before do
|
29
|
+
api.version api_versions
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns the route's helper name for each version" do
|
33
|
+
helper_names = show_route.helper_names
|
34
|
+
expect(helper_names.size).to eq(api_versions.size)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when an API has one version' do
|
39
|
+
it "returns the route's helper name for that version" do
|
40
|
+
helper_name = show_route.helper_names.first
|
41
|
+
expect(helper_name).to eq('api_v1_cats_path')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#helper_arguments' do
|
47
|
+
context 'when no user input is needed to generate the correct path' do
|
48
|
+
it 'returns an empty array' do
|
49
|
+
expect(index_route.helper_arguments).to eq([])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when user input is needed to generate the correct path' do
|
54
|
+
it 'returns an array of required segments' do
|
55
|
+
expect(show_route.helper_arguments).to eq(['id'])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#path_segments_with_values' do
|
61
|
+
context 'when path has dynamic segments' do
|
62
|
+
it 'replaces segments with corresponding values found in @options' do
|
63
|
+
opts = { id: 1 }
|
64
|
+
result = show_route.path_segments_with_values(opts)
|
65
|
+
expect(result).to include(1)
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when options contains string keys' do
|
69
|
+
it 'replaces segments with corresponding values found in the options' do
|
70
|
+
opts = { 'id' => 1 }
|
71
|
+
result = show_route.path_segments_with_values(opts)
|
72
|
+
expect(result).to include(1)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#path_helper_name' do
|
79
|
+
it "returns the name of a route's helper method" do
|
80
|
+
expect(index_route.path_helper_name).to eq('api_v1_cats_path')
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when the path is the root path' do
|
84
|
+
let(:api_with_root) do
|
85
|
+
Class.new(Grape::API) do
|
86
|
+
get '/' do
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
let(:root_route) do
|
92
|
+
grape_route = api_with_root.routes.first
|
93
|
+
described_class.new(grape_route)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns "root_path"' do
|
97
|
+
result = root_route.path_helper_name
|
98
|
+
expect(result).to eq('root_path')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when the path is a catch-all path' do
|
103
|
+
it 'returns a name without the glob star' do
|
104
|
+
result = catch_all_route.path_helper_name
|
105
|
+
expect(result).to eq('api_v1_path_path')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#segment_to_value' do
|
111
|
+
context 'when segment is dynamic' do
|
112
|
+
it 'returns the value the segment corresponds to' do
|
113
|
+
result = index_route.segment_to_value(':version')
|
114
|
+
expect(result).to eq('v1')
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'when segment is found in options' do
|
118
|
+
it 'returns the value found in options' do
|
119
|
+
options = { id: 1 }
|
120
|
+
result = show_route.segment_to_value(':id', options)
|
121
|
+
expect(result).to eq(1)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'when segment is static' do
|
127
|
+
it 'returns the segment' do
|
128
|
+
result = index_route.segment_to_value('api')
|
129
|
+
expect(result).to eq('api')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'path helper method' do
|
135
|
+
context 'when helper does not require arguments' do
|
136
|
+
it 'returns the correct path' do
|
137
|
+
path = index_route.api_v1_cats_path
|
138
|
+
expect(path).to eq('/api/v1/cats')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'when arguments are needed required to construct the right path' do
|
143
|
+
context 'when not missing arguments' do
|
144
|
+
it 'returns the correct path' do
|
145
|
+
path = show_route.api_v1_cats_path(id: 1)
|
146
|
+
expect(path).to eq('/api/v1/cats/1')
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "when a route's API has multiple versions" do
|
152
|
+
before(:each) do
|
153
|
+
api.version %w(v1 v2)
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'returns a path for each version' do
|
157
|
+
expect(index_route.api_v1_cats_path).to eq('/api/v1/cats')
|
158
|
+
expect(index_route.api_v2_cats_path).to eq('/api/v2/cats')
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'when a format is given' do
|
163
|
+
it 'returns the path with a correct extension' do
|
164
|
+
path = show_route.api_v1_cats_path(id: 1, format: 'xml')
|
165
|
+
expect(path).to eq('/api/v1/cats/1.xml')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GrapeRouteHelpers::NamedRouteMatcher do
|
4
|
+
include described_class
|
5
|
+
|
6
|
+
let(:api) { Spec::Support::RouteMatcherHelpers.api }
|
7
|
+
|
8
|
+
let(:routes) do
|
9
|
+
api
|
10
|
+
Grape::API.decorated_routes
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:ping_route) do
|
14
|
+
routes.detect do |route|
|
15
|
+
route.route_path =~ /ping/
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:index_route) do
|
20
|
+
routes.detect do |route|
|
21
|
+
route.route_namespace =~ /cats$/
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:show_route) do
|
26
|
+
routes.detect do |route|
|
27
|
+
route.route_namespace =~ %r{cats/:id}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#route_match?' do
|
32
|
+
context 'when route responds to a method name' do
|
33
|
+
let(:route) { ping_route }
|
34
|
+
let(:method_name) { :api_v1_ping_path }
|
35
|
+
let(:segments) { {} }
|
36
|
+
|
37
|
+
context 'when segments is not a hash' do
|
38
|
+
it 'raises an ArgumentError' do
|
39
|
+
expect do
|
40
|
+
route_match?(route, method_name, 1234)
|
41
|
+
end.to raise_error(ArgumentError)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns true' do
|
46
|
+
is_match = route_match?(route, method_name, segments)
|
47
|
+
expect(is_match).to eq(true)
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when requested segments contains expected options' do
|
51
|
+
let(:segments) { { 'format' => 'xml' } }
|
52
|
+
|
53
|
+
it 'returns true' do
|
54
|
+
is_match = route_match?(route, method_name, segments)
|
55
|
+
expect(is_match).to eq(true)
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'when no dynamic segments are requested' do
|
59
|
+
context 'when the route requires dynamic segments' do
|
60
|
+
let(:route) { show_route }
|
61
|
+
let(:method_name) { :ap1_v1_cats_path }
|
62
|
+
|
63
|
+
it 'returns false' do
|
64
|
+
is_match = route_match?(route, method_name, segments)
|
65
|
+
expect(is_match).to eq(false)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when the route does not require dynamic segments' do
|
70
|
+
it 'returns true' do
|
71
|
+
is_match = route_match?(route, method_name, segments)
|
72
|
+
expect(is_match).to eq(true)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when route requires the requested segments' do
|
78
|
+
let(:route) { show_route }
|
79
|
+
let(:method_name) { :api_v1_cats_path }
|
80
|
+
let(:segments) { { id: 1 } }
|
81
|
+
|
82
|
+
it 'returns true' do
|
83
|
+
is_match = route_match?(route, method_name, segments)
|
84
|
+
expect(is_match).to eq(true)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when route does not require the requested segments' do
|
89
|
+
let(:segments) { { some_option: 'some value' } }
|
90
|
+
|
91
|
+
it 'returns false' do
|
92
|
+
is_match = route_match?(route, method_name, segments)
|
93
|
+
expect(is_match).to eq(false)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when segments contains unexpected options' do
|
99
|
+
let(:segments) { { some_option: 'some value' } }
|
100
|
+
|
101
|
+
it 'returns false' do
|
102
|
+
is_match = route_match?(route, method_name, segments)
|
103
|
+
expect(is_match).to eq(false)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'when route does not respond to a method name' do
|
109
|
+
let(:method_name) { :some_other_path }
|
110
|
+
let(:route) { ping_route }
|
111
|
+
let(:segments) { {} }
|
112
|
+
|
113
|
+
it 'returns false' do
|
114
|
+
is_match = route_match?(route, method_name, segments)
|
115
|
+
expect(is_match).to eq(false)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe '#method_missing' do
|
121
|
+
context 'when method name matches a Grape::Route path helper name' do
|
122
|
+
it 'returns the path for that route object' do
|
123
|
+
api
|
124
|
+
|
125
|
+
path = api_v1_ping_path
|
126
|
+
expect(path).to eq('/api/v1/ping')
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'when argument to the helper is not a hash' do
|
130
|
+
it 'raises an ArgumentError' do
|
131
|
+
api
|
132
|
+
|
133
|
+
expect do
|
134
|
+
api_v1_ping_path(1234)
|
135
|
+
end.to raise_error(ArgumentError)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'when method name does not match a Grape::Route path helper name' do
|
141
|
+
it 'raises a NameError' do
|
142
|
+
api
|
143
|
+
|
144
|
+
expect do
|
145
|
+
some_method_name
|
146
|
+
end.to raise_error(NameError)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'when Grape::Route objects share the same helper name' do
|
152
|
+
context 'when helpers require different segments to generate their path' do
|
153
|
+
it 'uses arguments to infer which route to use' do
|
154
|
+
api
|
155
|
+
|
156
|
+
show_path = api_v1_cats_path('id' => 1)
|
157
|
+
expect(show_path).to eq('/api/v1/cats/1')
|
158
|
+
|
159
|
+
index_path = api_v1_cats_path
|
160
|
+
expect(index_path).to eq('/api/v1/cats')
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Spec
|
2
|
+
module Support
|
3
|
+
# container for methods used in specs
|
4
|
+
module RouteMatcherHelpers
|
5
|
+
def self.api
|
6
|
+
Class.new(Grape::API) do
|
7
|
+
version 'v1'
|
8
|
+
prefix 'api'
|
9
|
+
format 'json'
|
10
|
+
|
11
|
+
get 'ping' do
|
12
|
+
'pong'
|
13
|
+
end
|
14
|
+
|
15
|
+
resource :cats do
|
16
|
+
get '/' do
|
17
|
+
%w(cats cats cats)
|
18
|
+
end
|
19
|
+
|
20
|
+
route_param :id do
|
21
|
+
get do
|
22
|
+
'cat'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
route :any, '*path' do
|
28
|
+
'catch-all route'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grape-route-helpers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Harper Henn
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: grape
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Route helpers for Grape
|
98
|
+
email: harper.henn@legitscript.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- .gitignore
|
104
|
+
- .rubocop.yml
|
105
|
+
- Gemfile
|
106
|
+
- Gemfile.lock
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- grape-route-helpers.gemspec
|
110
|
+
- lib/grape-route-helpers.rb
|
111
|
+
- lib/grape-route-helpers/all_routes.rb
|
112
|
+
- lib/grape-route-helpers/decorated_route.rb
|
113
|
+
- lib/grape-route-helpers/named_route_matcher.rb
|
114
|
+
- lib/grape-route-helpers/railtie.rb
|
115
|
+
- lib/grape-route-helpers/route_displayer.rb
|
116
|
+
- lib/grape-route-helpers/tasks.rb
|
117
|
+
- lib/grape-route-helpers/version.rb
|
118
|
+
- lib/grape/route_helpers.rb
|
119
|
+
- lib/tasks/grape_route_helpers.rake
|
120
|
+
- spec/grape_route_helpers/decorated_route_spec.rb
|
121
|
+
- spec/grape_route_helpers/named_route_matcher_spec.rb
|
122
|
+
- spec/spec_helper.rb
|
123
|
+
- spec/support/route_matcher_helpers.rb
|
124
|
+
homepage: https://github.com/reprah/grape-route-helpers
|
125
|
+
licenses:
|
126
|
+
- MIT
|
127
|
+
metadata: {}
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options: []
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 2.0.14
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: Route helpers for Grape
|
148
|
+
test_files: []
|
149
|
+
has_rdoc:
|