grape-path-helpers-1.7.0/0000755000004100000410000000000014113665310015237 5ustar www-datawww-datagrape-path-helpers-1.7.0/Gemfile.lock0000644000004100000410000000467514113665310017475 0ustar www-datawww-dataPATH remote: . specs: grape-path-helpers (1.7.0) activesupport grape (~> 1.3) rake (> 12) ruby2_keywords (~> 0.0.2) GEM remote: https://rubygems.org/ specs: activesupport (6.1.3.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) ast (2.4.0) builder (3.2.4) coderay (1.1.2) concurrent-ruby (1.1.8) diff-lcs (1.3) dry-configurable (0.12.1) concurrent-ruby (~> 1.0) dry-core (~> 0.5, >= 0.5.0) dry-container (0.7.2) concurrent-ruby (~> 1.0) dry-configurable (~> 0.1, >= 0.1.3) dry-core (0.5.0) concurrent-ruby (~> 1.0) dry-inflector (0.2.0) dry-logic (1.2.0) concurrent-ruby (~> 1.0) dry-core (~> 0.5, >= 0.5) dry-types (1.5.1) concurrent-ruby (~> 1.0) dry-container (~> 0.3) dry-core (~> 0.5, >= 0.5) dry-inflector (~> 0.1, >= 0.1.2) dry-logic (~> 1.0, >= 1.0.2) grape (1.5.3) activesupport builder dry-types (>= 1.1) mustermann-grape (~> 1.0.0) rack (>= 1.3.0) rack-accept i18n (1.8.10) concurrent-ruby (~> 1.0) method_source (0.9.0) minitest (5.14.4) mustermann (1.1.1) ruby2_keywords (~> 0.0.1) mustermann-grape (1.0.1) mustermann (>= 1.0.0) parallel (1.12.1) parser (2.5.1.0) ast (~> 2.4.0) powerpack (0.1.1) pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) rack (2.2.3) rack-accept (0.4.5) rack (>= 0.4) rainbow (3.0.0) rake (13.0.3) rspec (3.7.0) rspec-core (~> 3.7.0) rspec-expectations (~> 3.7.0) rspec-mocks (~> 3.7.0) rspec-core (3.7.1) rspec-support (~> 3.7.0) rspec-expectations (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) rspec-support (3.7.1) rubocop (0.56.0) parallel (~> 1.10) parser (>= 2.5) powerpack (~> 0.1) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.9.0) ruby2_keywords (0.0.4) tzinfo (2.0.4) concurrent-ruby (~> 1.0) unicode-display_width (1.3.3) zeitwerk (2.4.2) PLATFORMS ruby DEPENDENCIES grape-path-helpers! pry (~> 0.11) rspec (~> 3.7) rubocop (~> 0.56) BUNDLED WITH 2.2.3 grape-path-helpers-1.7.0/.travis.yml0000644000004100000410000000031314113665310017345 0ustar www-datawww-datalanguage: ruby cache: bundler rvm: - ruby-head - 2.3.0 - 2.2.0 - 2.1.0 - 2.0.0 - 1.9.3 - jruby-19mode - jruby-head matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head grape-path-helpers-1.7.0/README.md0000644000004100000410000001033414113665310016517 0ustar www-datawww-data# grape-path-helpers [![Build status](https://gitlab.com/gitlab-org/grape-path-helpers/badges/master/pipeline.svg)](https://gitlab.com/gitlab-org/grape-path-helpers/commits/master) Provides named route helpers for Grape APIs, similar to [Rails' route helpers](http://edgeguides.rubyonrails.org/routing.html#path-and-url-helpers). **This is a fork and rename of [grape-route-helpers](https://github.com/reprah/grape-route-helpers). It [includes some fixes](CHANGELOG.md) needed for GitLab.** ### Installation #### Compatibility with Grape If you're using grape 0.16.0 or higher, you'll need version 2.0.0 or higher of grape-path-helpers. #### Rails p 1.) Add the gem to your Gemfile. ```bash $ bundle install grape-path-helpers ``` #### Sinatra/Rack 1.) Add the gem to your Gemfile if you're using Bundler. If you're not using Bundler to install/manage dependencies: ```bash $ gem install grape-path-helpers ``` ```ruby # environment setup file require 'grape' require 'grape/path_helpers' ``` 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: ```ruby # Rakefile require 'rake' require 'bundler' Bundler.setup require 'grape-path-helpers' require 'grape-path-helpers/tasks' desc 'load the Sinatra environment.' task :environment do require File.expand_path('your_app_file', File.dirname(__FILE__)) end ``` ### Usage #### List All Helper Names To see which methods correspond to which paths, and which options you can pass them: ```bash # In your API root directory, at the command line $ rake grape:path_helpers ``` #### Use Helpers in IRB/Pry You can use helper methods in your REPL session by including a module: ```ruby [1] pry(main)> include GrapePathHelpers::NamedRouteMatcher ``` #### Use Helpers in Your API Use the methods inside your Grape API actions. Given this example API: ```ruby class ExampleAPI < Grape::API::Instance 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 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.json' # specifying the format api_v1_cats_path(format: '.xml') # => '/api/v1/cats.xml' # adding a query string api_v1_cats_path(params: { sort_by: :age }) # => '/api/v1/cats?sort_by=age' # passing in values required to build a path api_v1_cats_path(id: 1) # => '/api/v1/cats/1.json' # catch-all paths have helpers api_v1_anything_path # => '/api/v1/*anything' ``` #### Custom Helper Names If you want to assign a custom helper name to a route, pass the `:as` option when creating your route in your API: ```ruby class Base < Grape::API::Instance get 'ping', as: 'is_the_server_running' 'pong' end end ``` This results in creating a helper called `is_the_server_running_path`. #### Testing You can use route helpers in your API tests by including the `GrapePathHelpers::NamedRouteMatcher` module inside your specs. Here's an example: ```ruby require 'spec_helper' describe Api::Base do include GrapePathHelpers::NamedRouteMatcher describe 'GET /ping' do it 'returns a 200 OK' do get api_v2_ping_path expect(response.status).to be(200) end end end ``` ### Contributing 1.) Fork it 2.) Create your feature branch `(git checkout -b my-new-feature)` 3.) Write specs for your feature 4.) Commit your changes `(git commit -am 'Add some feature')` 5.) Push to the branch `(git push origin my-new-feature)` 6.) Create a new pull request ### Releasing 1. Update the [CHANGELOG](CHANGELOG.md). 2. Update the version in [lib/grape-path-helpers/version.rb](lib/grape-path-helpers/version.rb). 3. [Tag](https://gitlab.com/gitlab-org/grape-path-helpers/-/tags) the commit with the version number prefixed by 'v'. 4. Run `gem build grape-path-helpers` locally. 5. Run `gem push $new_file.gem` locally, where `$new_file` is the file generated by the step above. ### License See LICENSE grape-path-helpers-1.7.0/spec/0000755000004100000410000000000014113665310016171 5ustar www-datawww-datagrape-path-helpers-1.7.0/spec/grape_path_helpers/0000755000004100000410000000000014113665310022025 5ustar www-datawww-datagrape-path-helpers-1.7.0/spec/grape_path_helpers/named_route_matcher_spec.rb0000644000004100000410000000762214113665310027400 0ustar www-datawww-datarequire 'spec_helper' # rubocop:disable Metrics/BlockLength describe GrapePathHelpers::NamedRouteMatcher do include described_class let(:helper_class) do fake_class = Class.new do prepend GrapePathHelpers::NamedRouteMatcher def method_missing(method_id, *args, **kwargs) [method_id, args, kwargs] || super end def respond_to_missing?(_method_name, _include_private = false) super end end fake_class.new end describe '#method_missing' do it 'returns super method_missing if the route does not exist' do expect(helper_class.test_method(id: 1)) .to eq([:test_method, [], { id: 1 }]) end it 'returns super method_missing if first arg is not a hash' do expect(helper_class.api_v1_cats_path(:arg1, kwarg1: :kwarg1)) .to eq([:api_v1_cats_path, [:arg1], { kwarg1: :kwarg1 }]) end context 'when method name matches a Grape::Route path helper name' do it 'returns the path for that route object' do expect(helper_class.api_v1_ping_path).to eq('/api/v1/ping.json') end context 'when route contains dynamic segments' do it 'returns the path for that route object' do expect(helper_class.api_v1_cats_path(id: 5)) .to eq('/api/v1/cats/5.json') end end context 'when route requires dynamic segments but none are passed in' do it 'returns super method_missing' do expect(helper_class.api_v1_cats_owners_path) .to eq([:api_v1_cats_owners_path, [], {}]) end end context 'when route has no dynamic segments but some are passed in' do it 'returns super method_missing' do expect(helper_class.api_v1_ping_path(invalid: 'test')) .to eq([:api_v1_ping_path, [], { invalid: 'test' }]) end end end end describe '#respond_to_missing?' do context 'when method name matches a Grape::Route path with segments' do let(:method_name) { :api_v1_cats_path } it 'returns true' do expect(respond_to_missing?(method_name)).to eq(true) end end context 'when method name matches a Grape::Route path' do let(:method_name) { :api_v1_ping_path } it 'returns true' do expect(respond_to_missing?(method_name)).to eq(true) end end context 'when method name does not match a Grape::Route path' do let(:method_name) { :some_other_path } it 'returns false' do expect(respond_to_missing?(method_name)).to eq(false) end end end context 'when Grape::Route objects share the same helper name' do context 'when helpers require different segments to generate their path' do it 'uses arguments to infer which route to use' do show_path = helper_class.api_v1_cats_path( 'id' => 1 ) expect(show_path).to eq('/api/v1/cats/1.json') index_path = helper_class.api_v1_cats_path expect(index_path).to eq('/api/v1/cats.json') end it 'does not get shadowed by another route with less segments' do show_path = helper_class.api_v1_cats_owners_path( 'id' => 1 ) expect(show_path).to eq('/api/v1/cats/1/owners.json') show_path = helper_class.api_v1_cats_owners_path( 'id' => 1, 'owner_id' => 1 ) expect(show_path).to eq('/api/v1/cats/1/owners/1.json') end end context 'when query params are passed in' do it 'uses arguments to infer which route to use' do show_path = helper_class.api_v1_cats_path( 'id' => 1, params: { 'foo' => 'bar' } ) expect(show_path).to eq('/api/v1/cats/1.json?foo=bar') index_path = helper_class.api_v1_cats_path( params: { 'foo' => 'bar' } ) expect(index_path).to eq('/api/v1/cats.json?foo=bar') end end end end # rubocop:enable Metrics/BlockLength grape-path-helpers-1.7.0/spec/grape_path_helpers/all_routes_spec.rb0000644000004100000410000000305414113665310025537 0ustar www-datawww-datarequire 'spec_helper' # rubocop:disable Metrics/BlockLength describe GrapePathHelpers::AllRoutes do Grape::API::Instance.extend described_class describe '#all_routes' do context 'when API is mounted within another API' do let(:mounting_api) { Spec::Support::MountedAPI } it 'does not include the same route twice' do mounting_api # A route is unique if no other route shares the same set of options all_route_options = Grape::API::Instance.all_routes.map do |r| r.instance_variable_get(:@options).merge(path: r.path) end duplicates = all_route_options.select do |o| all_route_options.count(o) > 1 end expect(duplicates).to be_empty end end # rubocop:disable Metrics/LineLength context 'when there are multiple POST routes with the same namespace in the same API' do it 'returns all POST routes' do expected_routes = Spec::Support::MultiplePostsAPI.routes.map(&:path) all_routes = Grape::API::Instance.all_routes expect(all_routes.map(&:path)).to include(*expected_routes) end end context 'when an API is created via an intermediate class' do it 'includes those routes on both Grape::API::Instance and the base class' do expect(Spec::Support::BaseAPI.all_routes.map(&:origin)).to include('/derived_ping') expect(Grape::API::Instance.all_routes.map(&:origin)).to include('/derived_ping') end end # rubocop:enable Metrics/LineLength end end # rubocop:enable Metrics/BlockLength grape-path-helpers-1.7.0/spec/grape_path_helpers/decorated_route_spec.rb0000644000004100000410000002677414113665310026554 0ustar www-datawww-datarequire 'spec_helper' # rubocop:disable Metrics/BlockLength describe GrapePathHelpers::DecoratedRoute do let(:api) { Spec::Support::API } let(:routes) do api.routes.map do |route| described_class.new(route) end end let(:index_route) do routes.detect { |route| route.route_namespace == '/cats' } end let(:show_route) do routes.detect { |route| route.route_namespace == '/cats/:id' } end let(:catch_all_route) do routes.detect { |route| route.route_path =~ /\*path/ } end let(:custom_route) do routes.detect { |route| route.route_path =~ /custom_name/ } end let(:ping_route) do routes.detect { |route| route.route_path =~ /ping/ } end let(:optional_route) do routes.detect { |route| route.route_path =~ /optional/ } end let(:wildcard_route) do routes.detect { |route| route.route_path =~ /\*owner_ids/ } end describe '#sanitize_method_name' do it 'removes characters that are illegal in Ruby method names' do illegal_names = ['beta-1', 'name_with_+', 'name_with_('] sanitized = illegal_names.map do |name| described_class.sanitize_method_name(name) end expect(sanitized).to match_array(%w[beta_1 name_with__ name_with__]) end it 'only replaces integers if they appear at the beginning' do illegal_name = '1' legal_name = 'v1' expect(described_class.sanitize_method_name(illegal_name)).to eq('_') expect(described_class.sanitize_method_name(legal_name)).to eq('v1') end end describe '#define_path_helper' do context 'with only static segments' do let(:route) { ping_route } subject { route.api_v1_ping_path } it { is_expected.to eq '/api/v1/ping.json' } end context 'with optional segments' do let(:route) { optional_route } subject { route.api_v1_cats______optional_path } it { is_expected.to eq '/api/v1/cats/(-/)/optional.json' } end context 'with dynamic segments' do let(:route) { show_route } subject { route.api_v1_cats_path(id: 1) } it { is_expected.to eq '/api/v1/cats/1.json' } end context 'with wildcard segments' do let(:route) { wildcard_route } context 'including them' do subject do route.api_v1_cats_owners_owner_ids_cats_path( { owner_ids: 'foobar' }, true ) end it { is_expected.to eq '/api/v1/cats/owners/foobar/cats.json' } end context 'excluding them' do subject { route.api_v1_cats_owners_owner_ids_cats_path } it { is_expected.to eq '/api/v1/cats/owners/owner_ids/cats.json' } end end end describe '#helper_names' do context 'when a route is given a custom helper name' do it 'uses the custom name instead of the dynamically generated one' do expect(custom_route.helper_names.first) .to eq('my_custom_route_name_path') end it 'returns the correct path' do expect( custom_route.my_custom_route_name_path ).to eq('/api/v1/custom_name.json') end end context 'when an API has multiple POST routes in a resource' do let(:api) { Spec::Support::MultiplePostsAPI } it 'it creates a helper for each POST route' do expect(routes.size).to eq(2) end end context 'when an API has multiple versions' do let(:api) { Spec::Support::APIWithMultipleVersions } it "returns the route's helper name for each version" do helper_names = ping_route.helper_names expect(helper_names.size).to eq(api.versions.size) end it 'returns an array of the routes versions' do expect(ping_route.route_version).to eq(%w[beta alpha v1]) end end context 'when an API has one version' do it "returns the route's helper name for that version" do helper_name = show_route.helper_names.first expect(helper_name).to eq('api_v1_cats_path') end end end describe '#helper_arguments' do context 'when no user input is needed to generate the correct path' do it 'returns an empty array' do expect(index_route.helper_arguments).to eq([]) end end context 'when user input is needed to generate the correct path' do it 'returns an array of required segments' do expect(show_route.helper_arguments).to eq(['id']) end end end describe '#path_segments' do context 'when path has optional segments' do it 'leaves them intact' do result = optional_route.path_segments expect(result).to eq(%w[api :version cats :id (-/) optional]) end end end describe '#path_segments_with_values' do context 'when path has dynamic segments' do it 'replaces segments with corresponding values found in options' do opts = { id: 1 } result = show_route.path_segments_with_values(opts) expect(result).to include(1) end context 'when options contains string keys' do it 'replaces segments with corresponding values found in the options' do opts = { 'id' => 1 } result = show_route.path_segments_with_values(opts) expect(result).to include(1) end end end context 'when path has wildcard segments' do context 'with regex excluding them' do it "doesn't replaces wildcard segments" do opts = { version: 4, id: 34, owner_ids: 'foobar' } result = wildcard_route.path_segments_with_values(opts) expect(result).to include(4) expect(result).to include(34) expect(result).to include('owner_ids') expect(result).not_to include('foobar') end it "doesn't blank wildcard segments" do opts = { version: 4, id: 34, owner_ids: '' } result = wildcard_route.path_segments_with_values(opts) expect(result).to include(4) expect(result).to include(34) expect(result).to include('owner_ids') end end context 'with regex including them' do context 'replaces segments' do it 'with static text' do opts = { version: 4, id: 34, owner_ids: 'foobar' } result = wildcard_route.path_segments_with_values(opts, true) expect(result).to include(4) expect(result).to include(34) expect(result).to include('foobar') expect(result).not_to include('owner_ids') end it 'with additional segments' do opts = { version: 4, id: 34, owner_ids: 'foo/bar/baz' } result = wildcard_route.path_segments_with_values(opts, true) expect(result).to include(4) expect(result).to include(34) expect(result).to include('foo/bar/baz') expect(result).not_to include('owner_ids') end end it 'blanks wildcard segments' do opts = { version: 4, id: 34, owner_ids: '' } result = wildcard_route.path_segments_with_values(opts, true) expect(result).to include(4) expect(result).to include(34) expect(result).not_to include('owner_ids') end context 'when options contains string keys' do it 'replaces segments' do opts = { 'version' => 4, 'id' => 34, 'owner_ids' => 'foobar' } result = wildcard_route.path_segments_with_values(opts, true) expect(result).to include(4) expect(result).to include(34) expect(result).to include('foobar') expect(result).not_to include('owner_ids') end end end end end describe '#path_helper_name' do it "returns the name of a route's helper method" do expect(index_route.path_helper_name).to eq('api_v1_cats_path') end context 'when the path is the root path' do let(:api_with_root) do Class.new(Grape::API) do get '/' do end end end let(:root_route) do grape_route = api_with_root.routes.first described_class.new(grape_route) end it 'returns "root_path"' do result = root_route.path_helper_name expect(result).to eq('root_path') end end context 'when the path is a catch-all path' do it 'returns a name without the glob star' do result = catch_all_route.path_helper_name expect(result).to eq('api_v1_path_path') end end context 'when the path has a wildcard segment' do it 'returns a name without the glob star' do result = wildcard_route.path_helper_name expect(result).to eq('api_v1_cats_owners_owner_ids_cats_path') end end end describe '#segment_to_value' do context 'when segment is dynamic' do it 'returns the value the segment corresponds to' do result = index_route.segment_to_value(':version') expect(result).to eq('v1') end context 'when segment is found in options' do it 'returns the value found in options' do options = { id: 1 } result = show_route.segment_to_value(':id', options) expect(result).to eq(1) end end end context 'when segment is static' do it 'returns the segment' do result = index_route.segment_to_value('api') expect(result).to eq('api') end end end describe 'path helper method' do context 'when given a "params" key' do context 'when value under "params" key is a hash' do it 'creates a query string' do query = { foo: :bar, baz: :zot } path = index_route.api_v1_cats_path(params: query) expect(path).to eq('/api/v1/cats.json?' + query.to_param) end end context 'when value under "params" is not a hash' do it 'coerces the value into a string' do path = index_route.api_v1_cats_path(params: 1) expect(path).to eq('/api/v1/cats.json?1') end end end # handle different Grape::Route#route_path formats in Grape 0.12.0 context 'when route_path contains a specific format' do it 'returns the correct path with the correct format' do path = index_route.api_v1_cats_path expect(path).to eq('/api/v1/cats.json') end end context 'when helper does not require arguments' do it 'returns the correct path' do path = index_route.api_v1_cats_path expect(path).to eq('/api/v1/cats.json') end end context 'when arguments are needed required to construct the right path' do context 'when not missing arguments' do it 'returns the correct path' do path = show_route.api_v1_cats_path(id: 1) expect(path).to eq('/api/v1/cats/1.json') end end end context "when a route's API has multiple versions" do let(:api) { Spec::Support::APIWithMultipleVersions } it 'returns a path for each version' do expect(ping_route.alpha_ping_path).to eq('/alpha/ping') expect(ping_route.beta_ping_path).to eq('/beta/ping') expect(ping_route.v1_ping_path).to eq('/v1/ping') end end context 'when a format is given' do it 'returns the path with a correct extension' do path = show_route.api_v1_cats_path(id: 1, format: '.xml') expect(path).to eq('/api/v1/cats/1.xml') end end end describe '#optional_segment?' do it { expect(index_route.optional_segment?('mandatory')).to eq(false) } it { expect(index_route.optional_segment?('(optional/)')).to eq(true) } end end # rubocop:enable Metrics/BlockLength grape-path-helpers-1.7.0/spec/spec_helper.rb0000644000004100000410000000027214113665310021010 0ustar www-datawww-data$LOAD_PATH.unshift File.expand_path('lib') require 'pry' require 'grape/path_helpers' support_files = Dir.glob('spec/support/*') support_files.each { |f| require File.expand_path(f) } grape-path-helpers-1.7.0/spec/support/0000755000004100000410000000000014113665310017705 5ustar www-datawww-datagrape-path-helpers-1.7.0/spec/support/api.rb0000644000004100000410000000332214113665310021003 0ustar www-datawww-datamodule Spec module Support # Test API class API < Grape::API::Instance version 'v1' prefix 'api' format 'json' get 'custom_name', as: :my_custom_route_name do 'hello' end get 'ping' do 'pong' end resource :cats do get '/' do %w[cats cats cats] end route_param :id do get do 'cat' end end get ':id/(-/)optional' do 'optional content' end get ':id/owners' do %w[owner1 owner2] end get ':id/owners/:owner_id' do 'owner' end get ':id/owners/*owner_ids/cats' do %w[cats cats cats] end end route :any, '*path' do 'catch-all route' end end # API with more than one version class APIWithMultipleVersions < Grape::API::Instance version %w[beta alpha v1] get 'ping' do 'pong' end end # API with another API mounted inside it class MountedAPI < Grape::API::Instance mount Spec::Support::API mount Spec::Support::APIWithMultipleVersions end # API with a version that would be illegal as a method name class APIWithIllegalVersion < Grape::API::Instance version 'beta-1' get 'ping' do 'pong' end end # API with multiple POST routes class MultiplePostsAPI < Grape::API::Instance resource :hamlet do post 'to_be' do end post 'or_not_to_be' do end end end class BaseAPI < Grape::API::Instance end class DerivedAPI < BaseAPI get 'derived_ping' do 'pong' end end end end grape-path-helpers-1.7.0/CHANGELOG.md0000644000004100000410000000655314113665310017061 0ustar www-datawww-data# Changelog ## 1.7.0 * [Further improve performance of route matching](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/38) ## 1.6.3 * [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) ## 1.6.2 * [Improve performance of route matching](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/33) ## 1.6.1 * [Use ruby2_keywords to fix 2.7 warning](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/31) ## 1.6.0 * [Extract kwargs to fix 2.7 warnings](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/29) ## 1.5.0 * [Relax rake dependency](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/27) ## 1.4.0 * [Support using a base class other than Grape::API::Instance](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/23) ## 1.3.0 * [Upgrade to Grape 1.3.1](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/21) ## 1.2.0 * [Add wildcard segments support](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/16) ## 1.1.0 * [Relax dependency on ActiveSupport](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/12) ## 1.0.6 * [Fix segments parsing for optional segments](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/10) ## 1.0.5 * [Relax dependencies](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/9) ## 1.0.4 * [Fix respond_to_missing? for included modules](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/8) ## 1.0.3 * [Fix return value in method_missing? implementation](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/7) * [Fix broken respond_to_missing? implementation](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/6) ## 1.0.2 * [Rename rake task from `grape:route_helpers` to `grape:path_helpers`](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/5) ## 1.0.1 * [Do not shadow helpers with the same name but more params](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/3) * [Reduce the number of calls to HashWithIndifferentAccess](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/4) * Update to Grape 1.0 # Changelog for grape-route-helpers ## December 17 2016 * Bump to 2.1.0 * Fix bug that caused POST routes to be ignored if more than one was defined * Many thanks to @njd5475 for the bug report and pull request that helped me write this ## April 28 2016 * Release 2.0.0 * Fix incompatibility between grape-route-helpers and grape 0.16.0 ## April 11 2016 * Release 1.2.2 * Fixed incompatibility between grape-route-helpers and Ruby 2.3 by merging PR #8 from phallguy ## October 11 2015 * Release 1.2.1 * Fixed issue #4 ## September 27 2015 * Release 1.2.0 * You can now assign custom helper names to Grape routes * Fixed a bug where routes would be listed more than once in the rake task if they are mounted to another API * Added the HTTP verb to rake task output ## July 5 2015 * You can now pass query parameters to helper functions in order to generate your own query string (Issue #1) ## June 28 2015 Release 1.0.1 * Rename rake task from `grape:routes` to `grape:route_helpers` * If a Grape::Route has a specific format (json, etc.) in its route_path attribute, the helper will return a path with this extension at the end grape-path-helpers-1.7.0/.rubocop.yml0000644000004100000410000000033114113665310017506 0ustar www-datawww-dataMetrics/ClassLength: Max: 104 Enabled: false Metrics/MethodLength: Max: 21 Enabled: false Style/FileName: Enabled: false # Removed in newer versions of Rubocop Lint/SplatKeywordArguments: Enabled: false grape-path-helpers-1.7.0/grape-path-helpers.gemspec0000644000004100000410000000156514113665310022303 0ustar www-datawww-datarequire File.join(Dir.pwd, 'lib', 'grape-path-helpers', 'version') Gem::Specification.new do |gem| gem.name = 'grape-path-helpers' gem.version = GrapePathHelpers::VERSION gem.licenses = ['MIT'] gem.summary = 'Route path helpers for Grape' gem.description = 'Route path helpers for Grape' gem.authors = ['Drew Blessing', 'Harper Henn'] gem.email = '' gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) gem.homepage = 'https://gitlab.com/gitlab-org/grape-path-helpers' gem.add_runtime_dependency 'activesupport' gem.add_runtime_dependency 'grape', '~> 1.3' gem.add_runtime_dependency 'rake', '> 12' gem.add_runtime_dependency 'ruby2_keywords', '~> 0.0.2' gem.add_development_dependency 'pry', '~> 0.11' gem.add_development_dependency 'rspec', '~> 3.7' gem.add_development_dependency 'rubocop', '~> 0.56' end grape-path-helpers-1.7.0/.gitignore0000644000004100000410000000003214113665310017222 0ustar www-datawww-data*.swp *.gem .ruby-version grape-path-helpers-1.7.0/Rakefile0000644000004100000410000000054714113665310016712 0ustar www-datawww-datarequire 'rubygems' require 'bundler' require 'bundler/gem_tasks' Bundler.setup :default, :test, :development Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = 'spec/**/*_spec.rb' end task :spec require 'rainbow/ext/string' unless String.respond_to?(:color) task default: [:spec] grape-path-helpers-1.7.0/lib/0000755000004100000410000000000014113665310016005 5ustar www-datawww-datagrape-path-helpers-1.7.0/lib/grape/0000755000004100000410000000000014113665310017103 5ustar www-datawww-datagrape-path-helpers-1.7.0/lib/grape/path_helpers.rb0000644000004100000410000000003514113665310022104 0ustar www-datawww-datarequire 'grape-path-helpers' grape-path-helpers-1.7.0/lib/tasks/0000755000004100000410000000000014113665310017132 5ustar www-datawww-datagrape-path-helpers-1.7.0/lib/tasks/grape_path_helpers.rake0000644000004100000410000000023114113665310023626 0ustar www-datawww-datanamespace :grape do desc 'Print route helper methods.' task path_helpers: :environment do GrapePathHelpers::RouteDisplayer.new.display end end grape-path-helpers-1.7.0/lib/grape-path-helpers/0000755000004100000410000000000014113665310021475 5ustar www-datawww-datagrape-path-helpers-1.7.0/lib/grape-path-helpers/version.rb0000644000004100000410000000010514113665310023503 0ustar www-datawww-data# Gem version module GrapePathHelpers VERSION = '1.7.0'.freeze end grape-path-helpers-1.7.0/lib/grape-path-helpers/decorated_route.rb0000644000004100000410000001071514113665310025176 0ustar www-datawww-datamodule GrapePathHelpers # wrapper around Grape::Route that adds a helper method class DecoratedRoute attr_reader :route, :helper_names, :helper_arguments, :extension, :route_options PATH_SEGMENTS_REGEXP = %r{\(/?\.:?\w+\)|/(?!\))|(?<=\))|\??\*} PATH_SEGMENTS_WITH_WILDCARDS_REGEXP = %r{\(/?\.:?\w+\)|/(?!\))|(?<=\))|\?} def self.sanitize_method_name(string) string.gsub(/\W|^[0-9]/, '_') end def initialize(route) @route = route @route_options = route.options @helper_names = [] @helper_arguments = required_helper_segments @extension = default_extension define_path_helpers end def default_extension pattern = /\((\.\:?\w+)\)$/ match = route_path.match(pattern) return '' unless match ext = match.captures.first if ext == '.:format' '' else ext end end def define_path_helpers route_versions.each do |version| route_attributes = { version: version, format: extension } 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 = {}, include_wildcard_segments = false) attrs = #{route_attributes}.merge(attributes) query_params = attrs.delete(:params) content_type = attrs.delete(:format) path = '/' + path_segments_with_values(attrs, include_wildcard_segments).join('/') path + content_type + query_string(query_params) end RUBY instance_eval method_body end def query_string(params) if params.nil? '' else '?' + params.to_param end end def route_versions return [nil] if route_version.nil? || route_version.empty? if route_version.is_a?(String) version_pattern = /[^\[",\]\s]+/ route_version.scan(version_pattern) else route_version end end def path_helper_name(opts = {}) if route_options[:as] name = route_options[:as].to_s else segments = path_segments_with_values(opts) name = if segments.empty? 'root' else segments.join('_') end end sanitized_name = self.class.sanitize_method_name(name) sanitized_name + '_path' end def segment_to_value(segment, opts = {}) if dynamic_segment?(segment) options = route.options.merge(stringify_keys(opts)) key = segment.slice(1..-1).to_sym options[key] else segment end end def path_segments_with_values(opts, include_wildcard_segments = false) segments = path_segments(include_wildcard_segments).map do |s| segment_to_value(s, opts) end segments.reject(&:blank?) end def path_segments(include_wildcard_segments = false) pattern = if include_wildcard_segments PATH_SEGMENTS_WITH_WILDCARDS_REGEXP else PATH_SEGMENTS_REGEXP end 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 optional_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 special_keys %w[format params] end def uses_segments_in_path_helper?(segments) segments = segments.reject { |x| special_keys.include?(x) } if helper_arguments.empty? && segments.any? false else helper_arguments.all? { |x| segments.include?(x) } end end def route_path route.path end def route_version route.version end def route_namespace route.namespace end def route_method route.request_method end private def stringify_keys(original) original.each_with_object({}) do |(key, value), hash| hash[key.to_sym] = value end end end end grape-path-helpers-1.7.0/lib/grape-path-helpers/named_route_matcher.rb0000644000004100000410000000167314113665310026036 0ustar www-datawww-datamodule GrapePathHelpers # 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_name, *args) possible_routes = Grape::API::Instance .decorated_routes_by_helper_name[method_name] return super unless possible_routes segments = args.first || {} return super unless segments.is_a?(Hash) requested_segments = segments.keys.map(&:to_s) route = possible_routes.detect do |r| r.uses_segments_in_path_helper?(requested_segments) end if route route.send(method_name, *args) else super end end ruby2_keywords(:method_missing) def respond_to_missing?(method_name, _include_private = false) !Grape::API::Instance.decorated_routes_by_helper_name[method_name].nil? || super end end end grape-path-helpers-1.7.0/lib/grape-path-helpers/railtie.rb0000644000004100000410000000040214113665310023447 0ustar www-datawww-datamodule GrapePathHelpers # Pulls in Rake helper for displaying route helper methods class Railtie < Rails::Railtie rake_tasks do files = File.join(File.dirname(__FILE__), '../tasks/*.rake') Dir[files].each { |f| load f } end end end grape-path-helpers-1.7.0/lib/grape-path-helpers/all_routes.rb0000644000004100000410000000161214113665310024173 0ustar www-datawww-datamodule GrapePathHelpers # 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_by_helper_name return @decorated_routes_by_helper_name if @decorated_routes_by_helper_name # rubocop:disable Metrics/LineLength routes = {} all_routes .map { |r| DecoratedRoute.new(r) } .sort_by { |r| -r.dynamic_path_segments.count } .each do |route| route.helper_names.each do |helper_name| key = helper_name.to_sym routes[key] ||= [] routes[key] << route end end @decorated_routes_by_helper_name = routes end def all_routes routes = descendants.flat_map { |s| s.send(:prepare_routes) } routes.uniq { |r| r.options.merge(path: r.path) } end end end grape-path-helpers-1.7.0/lib/grape-path-helpers/route_displayer.rb0000644000004100000410000000171414113665310025237 0ustar www-datawww-datamodule GrapePathHelpers # class for displaying the path, helper method name, # and required arguments for every Grape::Route. class RouteDisplayer def route_attributes Grape::API::Instance.decorated_routes.values.flatten.uniq.map do |route| { route_path: route.route_path, route_method: route.route_method, helper_names: route.helper_names, helper_arguments: route.helper_arguments } end end def display puts("== GRAPE ROUTE HELPERS ==\n\n") route_attributes.each do |attributes| printf("%s: %s\n", 'Verb', attributes[:route_method]) printf("%s: %s\n", 'Path', attributes[:route_path]) printf("%s: %s\n", 'Helper Method', attributes[:helper_names].join(', ')) printf("%s: %s\n", 'Arguments', attributes[:helper_arguments].join(', ')) puts("\n") end end end end grape-path-helpers-1.7.0/lib/grape-path-helpers/tasks.rb0000644000004100000410000000011614113665310023145 0ustar www-datawww-dataDir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each { |f| load f } grape-path-helpers-1.7.0/lib/grape-path-helpers.rb0000644000004100000410000000100114113665310022012 0ustar www-datawww-datarequire 'grape' require 'active_support' require 'active_support/core_ext/class' require 'grape-path-helpers/decorated_route' require 'grape-path-helpers/named_route_matcher' require 'grape-path-helpers/all_routes' require 'grape-path-helpers/route_displayer' # Load the Grape route helper for Rails module GrapePathHelpers require 'grape-path-helpers/railtie' if defined?(Rails) end Grape::API::Instance.extend GrapePathHelpers::AllRoutes Grape::Endpoint.send(:include, GrapePathHelpers::NamedRouteMatcher) grape-path-helpers-1.7.0/CONTRIBUTING.md0000644000004100000410000000424114113665310017471 0ustar www-datawww-data## Developer Certificate of Origin and License By contributing to GitLab B.V., you accept and agree to the following terms and conditions for your present and future contributions submitted to GitLab B.V. Except for the license granted herein to GitLab B.V. and recipients of software distributed by GitLab B.V., you reserve all right, title, and interest in and to your Contributions. 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). _This notice should stay as the first item in the CONTRIBUTING.md file._ ## Code of conduct As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior can be reported by emailing contact@gitlab.com. This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), version 1.1.0, available at [https://contributor-covenant.org/version/1/1/0/](https://contributor-covenant.org/version/1/1/0/). grape-path-helpers-1.7.0/Gemfile0000644000004100000410000000004714113665310016533 0ustar www-datawww-datasource 'https://rubygems.org' gemspec grape-path-helpers-1.7.0/LICENSE.txt0000644000004100000410000000213414113665310017062 0ustar www-datawww-dataMIT License (MIT) Copyright (c) 2015,2016 Harper Henn Copyright (c) 2018-2020 GitLab B.V. 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. grape-path-helpers-1.7.0/.gitlab-ci.yml0000644000004100000410000000042014113665310017667 0ustar www-datawww-dataimage: "ruby:3.0-alpine" cache: paths: - vendor/ruby key: ruby-3.0 before_script: - apk add --update git - gem install bundler - bundle install --path vendor/ruby rubocop: script: - bundle exec rubocop rspec: script: - bundle exec rspec spec