Rails - Array of hashes in Grape API - ruby-on-rails

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.

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

How to conditionally require a parameter based on a previous parameter

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.

Rails-Grape Nested PUT routes for Ember

I create Grape-create action which work perfectly
desc "Create a project."
params do
group :project, type: Hash do
requires :name, type: String, desc: "Name of project."
requires :user_id, type: String, desc: "user id"
requires :description, type: String, desc: "Description of project"
requires :project_type_id, type: String, desc: "Type of project"
end
end
post do
Project.create!(
name: params[:project][:name],
user_id: params[:project][:user_id],
description: params[:project][:description],
project_type_id: params[:project][:project_type_id]
)
end
But, when I want to create a PUT action to edit some values, I have error from response in JSON: error: "project[id] is missing"
My PUT code:
desc "Update a project."
params do
group :project, type: Hash do
requires :id, type: String, desc: "project ID."
requires :name, type: String, desc: "Name of project."
requires :user_id, type: String, desc: "user id"
requires :description, type: String, desc: "Description of project"
requires :project_type_id, type: String, desc: "Type of project"
end
end
put ':id' do
Project.find(params[:project][:id]).update!(
name: params[:project][:name],
user_id: params[:project][:user_id],
description: params[:project][:description],
project_type_id: params[:project][:project_type_id]
)
end
What I do wrong with this?
OK. I repair this by this code and it work
desc "Update a project."
params do
group :project, type: Hash do
requires :name, type: String, desc: "Name of project."
requires :user_id, type: String, desc: "user id"
requires :description, type: String, desc: "Description of project"
requires :project_type_id, type: String, desc: "Type of project"
end
end
put ':id' do
Project.find(params[:id]).update!(
name: params[:project][:name],
user_id: params[:project][:user_id],
description: params[:project][:description],
project_type_id: params[:project][:project_type_id]
)
end
I'm not sure of your structure without seeing what you are sending in your PUT request, but I would think the Grape code should be like - but again, I'm totally guessing without seeing your JSON payload.
put ':id' do
Project.find(params[:id]).update!(
name: params[:name],
user_id: params[:user_id],
description: params[:description],
project_type_id: params[:project_type_id]
)
end

Ruby on Rails model custom to_s method, exclude nill or blank values

I have the following model defined (Using Mongoid, not active record)
class Address
include Mongoid::Document
field :extra, type: String
field :street, type: String
field :area, type: String
field :city, type: String
field :code, type: Integer
validates :street, :city, presence: true
def to_s
"#{extra},#{street},#{area},#{city},#{code}"
end
end
I am defining the to_s method, so i can just use:
<%= address %>
in my views, and it will print out the address correctly. However the issue with the code above, if any of the attributes are blank or nil i end up with the following:
1.9.3p327 :015 > a
=> #<Address _id: 50f2da2c8bffa6e877000002, _type: nil, extra: "hello", street: nil, area: nil, city: nil, code: nil>
1.9.3p327 :016 > puts a
hello,,,,,
=> nil
Using a bunch of unless statements to check if the value is blank or nil seems like the wrong way to go (i can get it to work like that, but seems hackish)
What would be a better way of doing this?
You can use this
def to_s
[extra, street, area, city, code].select{|f| !f.blank?}.join(',')
end
Store elements in an array, throw invalid values out, join with separator.

Resources