How to conditionally require a parameter based on a previous parameter - ruby-on-rails

I am working with apipie for my rails app API and have a parameter that needs to be conditionally required. If a user is hired, they need to have a hired_at date. For other reasons those must be 2 separate columns. I cannot just check for the presence of a hired_at date...
What I currently have is essentially the following:
param :person, Hash, desc: "person information", required: true do
param :name, String, desc: "person name", required: true
param :hired, [true, false], desc: "person has been hired", required: true
param :dates, Array, of: Hash, desc: "important dates", required: true do
param :hired_at, String, desc: "date of fire", required: <if hired == true >
end
end
the <if hired==true> is pseudocode. That is the logic I need there but I don't know how to implement it.

Related

Is there a way to include an external Params block in a Rails Grape resource?

I'm using Ruby on Rails 4 and Grape.
I'd like my Grape Resources to take up a little space so that they are more readable by other developers.
In the last few days we have been integrating the Stripe API (as an example)
and in the params do section of the Resources there are code blocks like this:
desc 'Add bank account' do
headers API::V1::Defaults.xxxxxxx
success API::V1::Entities::xxxxxxx
end
params do
requires :external_account, type: Hash, allow_blank: false, desc: 'Bank account nested data' do
requires :bank_account, type: Hash, allow_blank: false, desc: 'Bank account nested data' do
requires :id, type: String, desc: 'Stripe token for bank account'
requires :account_holder_name, type: String, desc: 'Bank account holder name'
requires :account_holder_type, type: String, desc: 'Bank account holder type [individual or company]'
optional :bank_name, type: String, desc: 'Bank name'
requires :country, type: String, desc: 'Bank account country'
optional :currency, type: String, desc: 'Bank account currency'
requires :routing_number, type: String, desc: 'Bank account routing number'
requires :name, type: String, desc: 'Bank account holders name'
requires :status, type: String, desc: 'Bank account status'
requires :last4, type: Integer,
desc: 'Account holder ID number.'
end
requires :client_ip, type: String, desc: 'IP address of user for Stripe service agreement'
end
requires :email, type: String, desc: 'Users email'
requires :business_type, type: String, desc: 'Individual or Company'
requires :tos_acceptance, type: Hash, allow_blank: false, desc: 'Type of Service' do
requires :date, type: Integer, desc: 'ToS [date]'
requires :ip, type: String, desc: 'ToS [ip]'
end
optional :individual, type: Hash do
requires :first_name, type: String, desc: 'Individuals [first name]'
requires :last_name, type: String, desc: 'Individuals [last name]'
requires :ssn_last_4, type: String, desc: 'Individuals SSN number'
optional :dob, type: Hash do
requires :day, type: String, desc: 'Individuals date of birth [day]'
requires :month, type: String, desc: 'Individuals date of birth [month]'
requires :year, type: String, desc: 'Individuals date of birth [year]'
end
end
optional :company, type: Hash do
requires :name, type: String, desc: 'Company [first name]'
requires :email, type: String, desc: 'Company [email]'
requires :phone, type: String, desc: 'Company [phone]'
end
end
# ...
oauth2
post '/' do
# ...
end
How can I make that params block go to another file (for example inside a file in the helpers folder) and the params block can be included?
I tried to do this with include API::V1::Helpers::my_helper but I don't know how to insert the params block. Can someone help me, please?
You might use shared params
module SharedParams
extend Grape::API::Helpers
params :pagination do
optional :page, type: Integer
optional :per_page, type: Integer
end
end
class API < Grape::API
helpers SharedParams
desc 'Get collection.'
params do
use :pagination
end
get do
# your logic here
end
end

Ruby GraphQL: How to order arguments in an option?

I am using SearchObject with GraphQL in Ruby on Rails and created an option called :order_by
option :order_by, type: LinksOrderBy, with: :apply_order_by
The type LinksOrderBy is defined by this:
class LinksOrderBy < ::Types::BaseInputObject
argument :created_at, AscDescEnum, required: false
argument :description, AscDescEnum, required: false
argument :id, AscDescEnum, required: false
argument :updated_at, AscDescEnum, required: false
argument :url, AscDescEnum, required: false
end
But when I try something like this in GraphQL query:
{allLinks(order_by:{id: asc, description: desc}) {
id
description
}}
I don't get it in the right order:
def apply_order_by(scope, value)
scope.order(value.arguments.to_h.map{|k, v| k + ' ' + v}.join(', ')) # "description desc, id asc"
end
As you can see the right order should be "id, description", not "description, id".
The ruby Hash instances are not ordered (see Is order of a Ruby hash literal guaranteed?)
To leverage the optional multi sorting options in GraphQL input types, I usually use the following structure:
1 enum to contain all filterable/sortable/searchable field of a resource (ex: UserField)
1 enum to contain the 2 sort directions (asc and desc)
1 field accepting an optional list of { field: UserField!, sortDir: SortDir! } inputs.
This then enables the API consumers to simply do queries like:
allUsers(sort_by: [{field: username, sortDir: desc}, {field: id, sortDir: asc}]) {
# ...
}
And this pattern can be easily re-used for searching and filtering:
allUsers(search: [{field: username, comparator: like, value: 'bob'}]) {}
allUsers(search: [{field: age, comparator: greater_than, value: '22'}]) {} # type casting is done server-side
allUsers(search: [{field: username, comparator: equal, value: 'bob'}]) {} # equivalent of filtering
Eventually, with further deeper work you can allow complex and/or for the input:
allUsers(
search: [
{
left: {field: username, comparator: like, value: 'bob'}
operator: and
right: {field: dateOfBirth, comparator: geater_than, value: '2001-01-01'}
}
]
)
Disclaimer: the last example above is one of the many things I want to implement in my GQL API but I haven't had the time to think it through yet, it's just a draft

Reuse GraphQL arguments in Ruby

I'm building my first GraphQL api using rails and the graphql-ruby gem. So far it's been very simple and just awesome.
I'm kinda stuck now in regards to duplicate code. I've got a project management rails app which has spaces, todos (they belong to a space), users.
What I want to achieve is to be able to query spaces and its todos but also all the todos for the current user for example. For todos I want to be able to filter them using different arguments:
Done - Boolean,
Scope - String (today or thisWeek),
Assignee - Integer (Id of a user)
query {
space(id: 5) {
todos(done: false) {
name
done
dueAt
}
}
}
query {
me {
todos(done: false, scope: today) {
name
done
dueAt
}
}
}
That means everytime I got the field todos I want to be able to filter them whether they are done or due today etc.
I know how to use arguments and everything works fine but currently I got the same coder over and over again. How (and where) could I extract that code to make it reusable everytime I have the same todos field?
field :todos, [TodoType], null: true do
argument :done, Boolean, required: false
argument :scope, String, required: false
argument :limit, Integer, required: false
end
Okay, as #jklina also said I ended up using a custom resolver.
I changed:
field :todos, [TodoType], null: true do
argument :done, Boolean, required: false
argument :scope, String, required: false
argument :limit, Integer, required: false
end
to:
field :todos, resolver: Resolvers::Todos, description: "All todos that belong to a given space"
and added a Resolver:
module Resolvers
class Todos < Resolvers::Base
type [Types::TodoType], null: false
TodoScopes = GraphQL::EnumType.define do
name "TodoScopes"
description "Group of available scopes for todos"
value "TODAY", "List all todos that are due to today", value: :today
value "THISWEEK", "List all todos that are due to the end of the week", value: :this_week
end
argument :done, Boolean, required: false
argument :scope, TodoScopes, required: false
argument :limit, Integer, required: false
argument :assignee_id, Integer, required: false
def resolve(done: nil, scope:nil, limit:5000, assignee_id: nil)
todos = object.todos
# filtering the records...
return todos.limit(limit)
end
end
end
Quite easy!

Rails - Array of hashes in Grape API

I'm really a new guy to Rails, in my project I want to submit a JSON string to Grape API. As you can see, my JSON has an user array that contains many objects. How can I define it in my Grape ?
Thank you
{
"users":[
{
"first_name":"Brittany",
"last_name":"Chen",
"email":"comstock#mailinator.com",
"phone_number":"+29-46-957-15423"
},
{
"first_name":"Lynn",
"last_name":"Brooks",
"email":"jensen#mailinator.com",
"phone_number":"+84-95-185-00137"
},
{
"first_name":"Claire",
"last_name":"Paul",
"email":"mei#mailinator.com",
"phone_number":"+66-64-893-53401"
},
{
"first_name":"Gemma",
"last_name":"Carter",
"email":"malik#mailinator.com",
"phone_number":"+83-46-325-54538"
}
],
"service_ids":["1", "2", "3"],
"auth_token":"xxxxxxxxxxxxxxxxxxxxxx"
}
this is my Grape params
params do
optional :user, type: Hash do
optional :email, type: String, desc: "user email"
optional :first_name, type: String, desc: "user first name"
optional :last_name, type: String, desc: "user last name"
optional :phone_number, type: String, desc: "user phone number"
end
optional :service_ids, type: Array[Integer], desc: "list of service ids selected"
requires :auth_token, type: String, desc: "authentication_token"
end
This is called "Validation of nested parameters" in Grape. In your code, you were actually asking for a user hash containing optional parameters email, first_name, last_name and phone_number, so not exactly what you were looking for.
With a block, group, requires and optional accept an additional option type which can be either Array or Hash, and defaults to Array. Depending on the value, the nested parameters will be treated either as values of a hash or as values of hashes in an array.
Source: https://github.com/ruby-grape/grape#validation-of-nested-parameters
So in your case, you would have to describe your params like this :
params do
optional :users, type: Array do
optional :email, type: String, desc: "user email"
optional :first_name, type: String, desc: "user first name"
optional :last_name, type: String, desc: "user last name"
optional :phone_number, type: String, desc: "user phone number"
end
# ...
# any other params
# ...
end
And so each item in the array will be expected to match the fields in the given block.

Rails: documenting post endpoint which consumes json params using swagger-blocks?

Using swagger-blocks in rails, would i document a post endpoint which consumes a single json body such as:
{
"id":"1",
"name": "bill",
"age":"22"
}
No matter what I do, my tests keep saying that my setup is not valid Swagger 2.0 JSON schema.
Below is the code I am using to generate my documentation:
swagger_path '/list/add' do
operation :post do
key :summary, 'Add person to list'
parameter name: :id, in: :body, required: true, type: :string
parameter name: :name, in: :body, required: true, type: :string
parameter name: :age, in: :body, required: true, type: :string
response 200 do
key :description, 'Successfully added to list'
end
end
end
The JSON seems correct syntactically except that "id" should be generated automatically by default if you are trying to create. You may check this specification for violations.

Resources