Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
reprah committed Jun 26, 2015
0 parents commit 8f237b8
Show file tree
Hide file tree
Showing 18 changed files with 730 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.swp
10 changes: 10 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Metrics/ClassLength:
Max: 104
Enabled: false

Metrics/MethodLength:
Max: 21
Enabled: false

Style/FileName:
Enabled: false
22 changes: 22 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
MIT License (MIT)

Copyright (c) 2015 Harper Henn

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# grape-route-helpers

Provides named route helpers for Grape APIs, similar to Rails' route helpers.

### Installation

1.) Add the gem to your Gemfile if you're using Bundler.

```bash
$ bundle install grape-route-helpers
```

Run `gem install grape-route-helpers` if you're not.

2.) Require the gem after you `require 'grape'` in your application setup.

```ruby
require 'grape/route_helpers'
```

### Usage examples

* To see which methods correspond to which paths, and which options you can pass them:

```bash
$ rake grape:route_helpers
```

* Use the methods inside your Grape API actions. Given this example API:

```ruby
class ExampleAPI < Grape::API
version 'v1'
prefix 'api'
format 'json'

get 'ping' do
'pong'
end

resource :cats do
get '/' do
%w(cats cats cats)
end

route_param :id do
get do
'cat'
end
end
end

route :any, '*anything' do
redirect_to api_v1_cats_path
end
end
```

You'd have the following methods available inside your Grape API actions:

```ruby
# specifying the version when using Grape's "path" versioning strategy
api_v1_ping_path # => '/api/v1/ping'

# specifying the format
api_v1_cats_path(format: 'xml') # => '/api/v1/cats.xml'

# passing in values required to build a path
api_v1_cats_path(id: 1) # => '/api/v1/cats/1'

# catch-all paths have helpers
api_v1_anything_path # => '/api/v1/*anything'
```
3 changes: 3 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
$LOAD_PATH.unshift File.expand_path('lib')

require 'grape/route_helpers'
21 changes: 21 additions & 0 deletions grape-route-helpers.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require File.join(Dir.pwd, 'lib', 'grape-route-helpers', 'version')

Gem::Specification.new do |gem|
gem.name = 'grape-route-helpers'
gem.version = GrapeRouteHelpers::VERSION
gem.licenses = ['MIT']
gem.summary = 'Route helpers for Grape'
gem.description = 'Route helpers for Grape'
gem.authors = ['Harper Henn']
gem.email = '[email protected]'
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
gem.homepage = 'https://github.com/reprah/grape-route-helpers'

gem.add_runtime_dependency 'grape'
gem.add_runtime_dependency 'activesupport'
gem.add_runtime_dependency 'rake'

gem.add_development_dependency 'pry'
gem.add_development_dependency 'rspec'
gem.add_development_dependency 'rubocop'
end
11 changes: 11 additions & 0 deletions lib/grape-route-helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'grape'
require 'active_support'
require 'active_support/core_ext/class'

require 'grape-route-helpers/decorated_route'
require 'grape-route-helpers/named_route_matcher'
require 'grape-route-helpers/all_routes'
require 'grape-route-helpers/route_displayer'

Grape::API.extend GrapeRouteHelpers::AllRoutes
Grape::Endpoint.include GrapeRouteHelpers::NamedRouteMatcher
15 changes: 15 additions & 0 deletions lib/grape-route-helpers/all_routes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module GrapeRouteHelpers
# methods to extend Grape::API's behavior so it can get a
# list of routes from all APIs and decorate them with
# the DecoratedRoute class
module AllRoutes
def decorated_routes
# memoize so that construction of decorated routes happens once
@decorated_routes ||= all_routes.map { |r| DecoratedRoute.new(r) }
end

def all_routes
subclasses.flat_map { |s| s.send(:prepare_routes) }
end
end
end
134 changes: 134 additions & 0 deletions lib/grape-route-helpers/decorated_route.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
module GrapeRouteHelpers
# wrapper around Grape::Route that adds a helper method
class DecoratedRoute
attr_reader :route, :helper_names, :helper_arguments

def initialize(route)
@route = route
@helper_names = []
@helper_arguments = required_helper_segments
define_path_helpers
end

def define_path_helpers
route_versions.each do |version|
route_attributes = { version: version }
method_name = path_helper_name(route_attributes)
@helper_names << method_name
define_path_helper(method_name, route_attributes)
end
end

def define_path_helper(method_name, route_attributes)
method_body = <<-RUBY
def #{method_name}(attributes = {})
attrs = HashWithIndifferentAccess.new(
#{route_attributes}.merge(attributes)
)
content_type = attrs.delete(:format)
path = '/' + path_segments_with_values(attrs).join('/')
extension = content_type ? '.' + content_type : ''
path + extension
end
RUBY
instance_eval method_body
end

def route_versions
if route_version
route_version.split('|')
else
[nil]
end
end

def path_helper_name(opts = {})
segments = path_segments_with_values(opts)

name = if segments.empty?
'root'
else
segments.join('_')
end
name + '_path'
end

def segment_to_value(segment, opts = {})
options = HashWithIndifferentAccess.new(
route_options.merge(opts)
)

if dynamic_segment?(segment)
key = segment.slice(1..-1)
options[key]
else
segment
end
end

def path_segments_with_values(opts)
segments = path_segments.map { |s| segment_to_value(s, opts) }
segments.reject(&:blank?)
end

def path_segments
pattern = %r{\(/?\.:format\)|/|\*}
route_path.split(pattern).reject(&:blank?)
end

def dynamic_path_segments
segments = path_segments.select do |segment|
dynamic_segment?(segment)
end
segments.map { |s| s.slice(1..-1) }
end

def dynamic_segment?(segment)
segment.start_with?(':')
end

def required_helper_segments
segments_in_options = dynamic_path_segments.select do |segment|
route_options[segment.to_sym]
end
dynamic_path_segments - segments_in_options
end

def optional_segments
['format']
end

def uses_segments_in_path_helper?(segments)
requested = segments - optional_segments
required = required_helper_segments

if requested.empty? && required.empty?
true
else
requested.all? do |segment|
required.include?(segment)
end
end
end

# accessing underlying Grape::Route

def route_path
route.route_path
end

def route_options
route.instance_variable_get(:@options)
end

def route_version
route.route_version
end

def route_namespace
route.route_namespace
end
end
end
28 changes: 28 additions & 0 deletions lib/grape-route-helpers/named_route_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module GrapeRouteHelpers
# methods to extend Grape::Endpoint so that calls
# to unknown methods will look for a route with a matching
# helper function name
module NamedRouteMatcher
def method_missing(method_id, *arguments)
segments = arguments.first || {}

route = Grape::API.decorated_routes.detect do |r|
route_match?(r, method_id, segments)
end

if route
route.send(method_id, *arguments)
else
super
end
end

def route_match?(route, method_name, segments)
return false unless route.respond_to?(method_name)
fail ArgumentError,
'Helper options must be a hash' unless segments.is_a?(Hash)
requested_segments = segments.keys.map(&:to_s)
route.uses_segments_in_path_helper?(requested_segments)
end
end
end
25 changes: 25 additions & 0 deletions lib/grape-route-helpers/route_displayer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module GrapeRouteHelpers
# class for displaying the path, helper method name,
# and required arguments for every Grape::Route.
class RouteDisplayer
def route_attributes
Grape::API.decorated_routes.map do |route|
{
route_path: route.route_path,
helper_names: route.helper_names,
helper_arguments: route.helper_arguments
}
end
end

def display
puts 'Path, Helper, Arguments'
route_attributes.each do |attributes|
print "#{attributes[:route_path]},"
print "#{attributes[:helper_names]},"
print "#{attributes[:helper_arguments]}"
puts("\n")
end
end
end
end
4 changes: 4 additions & 0 deletions lib/grape-route-helpers/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Gem version
module GrapeRouteHelpers
VERSION = '1.0.0'
end
1 change: 1 addition & 0 deletions lib/grape/route_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require 'grape-route-helpers'
6 changes: 6 additions & 0 deletions lib/tasks/grape_route_helpers.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace :grape do
desc 'Print route helper methods.'
task routes: :environment do
GrapeRouteHelpers::RouteDisplayer.new.display
end
end
Loading

0 comments on commit 8f237b8

Please sign in to comment.