Can't understand Grape API route param - ruby

I am having a lot of trouble understanding Grape API, specifically route_param and how it works with just params.
Consider this code:
desc "Return a status."
params do
requires :id, type: Integer, desc: "Status id."
end
route_param :id do
get do
Status.find(param[:id])
end
end
What route does this block produce? I get that this is a get request, but why is it wrapped in route_param block? Why can't it be in params block?

Your block produces this route:
http://yourdomain.com/<resource>/<id>
Note that your code and the code below do the same thing and produce the same route:
desc "Return a status."
params do
requires :id, type: Integer, desc: "Status id."
end
get ':id' do
Status.find(params[:id])
end
You can use route_param to group methods that receive the same params, for example:
resource :categories do
route_param :id do
get do # produces the route GET /categories/:id
end
put do # produces the route PUT /categories/:id
end
end
end

Related

Hanami parameters whitelisting

Following the hanami docs, in order to block a admin parameter inside an action, I can use the following configuration:
params do
required(:email).filled
required(:address).schema do
required(:country).filled
end
end
def call(params)
puts params[:email] # => "alice#example.org"
puts params[:address][:country] # => "Italy"
puts params[:admin] # => nil
end
But this does not work for nested parameters, i.e.:
params do
required(:email).filled
required(:address).schema do
required(:country).filled
end
end
def call(params)
puts params[:email] # => "alice#example.org"
puts params[:address] # => { country: "Italy", admin: true }
puts params[:address][:admin] # => true
end
I was able to solve this by using select to filter out the undesirable parameters with a private method, but this does not seems like the Hanami way. What would be the proper way to do this whitelisting of nested parameters?
I have never had this issue when using Hanami Validations. Within the app directory there should be a validations folder which should have the same directory structure as your controllers, views, templates etc. Your validation file should look something like this:
# apps/web/validations/users/create.rb
module Web
module Validations
module Users
class Create < Web::Action::Params
predicates Web::Validations::CommonPredicates
validations do
required(:email).filled
required(:address).schema do
required(:country).filled
end
end
end
end
end
end
And then your controller should set the params to be filtered through the validation:
module Web
module Controllers
module Users
class Create
include Web::Action
params Web::Validations::Users::Create
def call(params); end
end
end
end
end

Reusable Custom DSL Extension in Grape

I have a custom Grape DSL method called scope that remembers the parameter then delegates some work to the params helper.
class API::V1::Walruses < Grape::API
resources :walruses do
scope :with_tusks, type: Integer,
desc: "Return walruses with the exact number of tusks"
scope :by_name, type: String,
desc: "Return walruses whose name matches the supplied parameter"
get do
# Equivalent to `Walrus.with_tusks(params[:with_tusks]).
# by_name(params[:by_name])`, but only calls scopes whose
# corresponding param was passed in.
apply_scopes(Walrus)
end
end
end
This works and is clean, which I like. The problem is that many child endpoints also want to use those same scopes. This means maintaining them in multiple places and keeping them all in sync.
I would like to do the following instead:
class API::V1::Walruses < Grape::API
helpers do
scopes :index do
scope :with_tusks, type: Integer,
desc: "Return walruses with the exact number of tusks"
scope :by_name, type: String,
desc: "Return walruses whose name matches the supplied parameter"
end
end
resources :walruses do
use_scopes :index
#...
end
end
Then API::V1::Walruses will have those scopes available, as will API::V1::Walruses::Analytics (if included), and any others that are nested within.
Params have a similar methodology but I've had trouble understanding how to inherit settings.

Why does invoking a method with parameters return a 404 error?

I'm pretty new to Ruby, I know a little bit of Sinatra but what I actually need for my app is Grape for rest api.
Working with a method with parameters at all works like a charm, but when I'm trying to add parameters I get 404 not found exception.
Where am I going wrong here? Thanks
resource :devs do
desc "Get all devs"
get do
authenticate!
Dev.all
end
desc "Get dev by email"
params do
requires :email, type: String, desc: "Dev's email"
end
route_param :email do
get do
authenticate!
#devs = Dev.all(:email == params[:email])
#!error('email not found', 204) unless #devs.length > 0
end
end
desc "Get dev by API key"
get :key do
authenticate!
#dev = Dev.first(:api_key == params[:key])
!error('email not found', 204) unless #devs.length > 0
end
end
This is the call I make in PostMan (I also added the header for Apikey there)
localhost:9292/devs/email/orelzion#gmail.com
But it always give me the same result 404
The route_param directive doesn't work the way you think it does. Given the code you posted, the link you should be visiting is localhost:9292/devs/orelzion#gmail.com.
If you want the code to match the URL you wrote instead, use a nested namespace (or equivalently, a nested resource):
resource :devs do
...
namespace :email do
desc "Get dev by email"
params do
requires :email, type: String, desc: "Dev's email"
end
route_param :email do
get do
...
end
end
end
...

Syntax for declaring resource paths in Grape

I am looking for clarification on the syntax to declare API resource paths in Grape. The example below declares the resource paths "/items", "/items/:id", "/objects", and "/objects/:id". What I do not undestand is why the definition for "/items/:id" returns null?
class API < Grape::API
format :json
default_format :json
rescue_from :all, backtrace: true
resource :items do
desc "Returns an array of all items."
get do
ITEMS.find.to_a
end
desc "Returns an item by its id."
get '/:id' do
# hardcoding the document id returns the correct document
# ITEMS.find_one( "item_id" => 2519 )
# using the path parameter :id returns null, why???
ITEMS.find_one( "item_id" => params[:id] )
end
end
resource :objects do
desc "Returns an array of all objects."
get do
OBJECTS.find.to_a
end
##
# using the route_param syntax correctly returns the document
# resource path /objects/:id
##
desc "Returns an object by its id."
params do
requires :id, type: Integer
end
route_param :id do
get do
OBJECTS.find_one( "object_id" => params[:id] )
end
end
end
end
Your use of resource and route methods is ok.
You have a problem with parameter processing - params[:id] is a String by default. Your example hard-coded value that works is aFixnum (integer).
Probably your (not shown) code that queries the list on ITEMS is looking up integer values.
You could use ITEMS.find_one( "item_id" => params[:id].to_i ) to convert the param inline.
However, you probably should use a params description block to get Grape to convert for you, as you already are for OBJECTS:
desc "Returns an item by its id."
params do
requires :id, type: Integer
end
get '/:id' do
ITEMS.find_one( "item_id" => params[:id] )
end

Multiple Route Params in Grape

How do you get multiple route params in Grape to work in grape?
I can make this route work:
.../api/company/:cid
But when I try this:
.../api/company/:cid/members
.../api/company/:cid/members/:mid
I get errors.
Here's the code that works.
resource 'company' do
params do
optional :start_date, type: Date, desc: "Start date of range."
optional :end_date, type: Date, desc: "End date of range."
end
route_param :cid do
get do
{company_id: params[:cid]}
end
end
class API::Company < Grape::API
resource :company do
route_param :cid do
desc "GET"
params do
# your params
end
get '/' do # => '.../api/company/:cid
# process get
end
resources :members do
desc "GET"
params do
# your params
end
get '/' do # => '.../api/company/:cid/members/'
# process get
end
route_param :mid do
desc "GET"
params do
# your params
end
get '/' do # => '.../api/company/:cid/members/:mid'
# process get
end
end
end
end
end
end
You can do that that way. Or you can create two different resources file and mount Members to Company. Like so:
# api/company.rb
class API::Company < Grape::API
resource :company do
route_param :cid do
desc "GET"
params do
# your params
end
get '/' do # => '.../api/company/:cid
# process get
end
mount API::Members
end
end
end
# api/member.rb
class API::Member < Grape::API
resources :members do
desc "GET"
params do
# your params
end
get '/' do # => '.../api/company/:cid/members/'
# process get
end
route_param :mid do
desc "GET"
params do
# your params
end
get '/' do # => '.../api/company/:cid/members/:mid'
# process get
end
end
end
Hope that helps
Another way to do that is using a regexp to validate the ids.
resource 'company' :requirements => { :id => /[0-9]*/, :mid => /[0-9]*/ } do
get '/:id' do
# returns company
end
get ":id/members" do
members = Company.find_by_id(params[:id]).members
present members, :with => Members::MemberResponseEntity
end
get ":id/members/:mid" do
member = Member.find_by_id(params[:mid])
present member, :with => Members::MemberResponseEntity
end
end

Resources