Grape API saving serialized attribute - ruby-on-rails

I'm having a problem to save serialized attribute of my Model. I have a grape api with this function in my class.
# app/controllers/api/v1/vehicules.rb
module API
module V1
class Vehicules < Grape::API
include API::V1::Defaults
version 'v1'
format :json
helpers do
def vehicule_params
declared(params, include_missing: false)
end
end
resource :vehicules do
desc "Create a vehicule."
params do
requires :user_id, type: String, desc: "Vehicule user id."
requires :marque, type: String, desc: "Vehicule brand."
end
post do
#authenticate! #todo
Vehicule.create(vehicule_params)
end
My model is like so
class Vehicule < ActiveRecord::Base
serialize :marque, JSON
When I create a Vehicule in the console like vehicule = Vehicule.create(user_id: 123, marque: {label: "RENAULT"} it works fine.
But when I try to send a request : curl http://localhost:3000/api/v1/vehicules -X POST -d '{"user_id": "123", "marque": {"label": "RENAULT"}}' -H "Content-Type: application/json" I have this error message :
Grape::Exceptions::ValidationErrors
marque is invalid, modele is invalid
grape (0.16.1) lib/grape/endpoint.rb:329:in `run_validators'
If I send it with "marque": "{label: RENAULT}" it works but it's saved in db as marque: "{label: RENAULT}" and it should be marque: {"label"=>"RENAULT"} as I want marque['label'] to return RENAULT.
How could I send the data ?

I simply had to change in the grape controller the type of the attribute.
desc "Create a vehicule."
params do
requires :user_id, type: Integer, desc: "Vehicule user id."
requires :marque, type: Hash, desc: "Vehicule brand."
end
post do
#authenticate! #todo
Vehicule.create(vehicule_params)
end
And to test, you can do like so.
test "PUT /api/v1/vehicules/1" do
put("/api/v1/vehicules/1", {"id" => 1,"user_id" => 1,"marque" => {"label" => "RENAULT"}}, :format => "json")
assert(200, last_response.status)
vehicule = Vehicule.find(1)
assert_equal("RENAULT", vehicule.marque['label'], "La marque devrait ĂȘtre")
end

Related

How to set type and desc params with a function with Grape/Rails API

I am writing an API with Grape for may Rails 6 application.
I have a model with custom columns (depending on specification).
I generate a patch request parameters list, and I would like to generate desc and type with a function.
But I do not know if it is possible, and if yes, where to put the function (all I tried do not work).
This is a part of my Api class.
class MyObject < Grape::API
# ...
desc 'Update a MyObject.'
params do
requires :id, type: Integer, desc: 'MyObject ID.', documentation: {param_type: 'body'}
optional :title, type: String, desc: 'MyObject Title.', documentation: {param_type: 'body'}
optional :custom_field_attributes, type: Hash, documentation: {param_type: 'body'} do
CustomField.column_names.each do |cf|
next unless cf.include? 'custom_'
optional cf.intern,
type: custom_field_type(cf), # Here I would like to call a function
desc: custom_field_desc(cf) # Here too
end
end
end
patch do
# ...
end
end
Is it possible to do this ?

How to pass array of strings in query api rspec tests using rswag?

I need to test search api endpoint - search users by skills.
When I trying to find user in app frontend, I see this params in console:
Processing by UsersController#search as JSON
Parameters: {"tags"=>["rails"]}
How can I correctly pass array of string from the test to the api endpoint?
Here is the integration test code:
path '/api/users/search' do
post 'search users' do
tags 'Users'
security [apiKey: []]
consumes 'application/json'
produces 'application/json'
parameter name: :tags,
in: :query,
type: :array,
example: %w(angular ruby)
response '200', 'Returns users' do
let(:logged_user) { User.find_by(email: 'admin#company.name') }
let(:skill) { create :skill }
let(:skill2) { create :skill }
let(:tags) do
[skill.title, skill2.title]
end
let(:user) { create :user }
before do
user.skills << skill
user.skills << skill2
end
run_test!
end
end
end
When I run this test, I getting error:
1) User resource /api/users/search post Returns users returns a 200 response
Failure/Error:
params.dig(:tags).each do |tag|
skills.concat(Skill.where(
"title ilike '#{tag}' or variants ~* '(^#{tag}|[\s,]#{tag})(,|$)'"
).pluck(:id))
end
NoMethodError:
undefined method `each' for "aut,iste":String
But I can't find anything in the gem docs that can help me to solve it.
Any suggestions?

Rspec format Post parameters to String values

When I send an object json all fields inside are changed to string, breaking my validation in the controller and i get the following error.
Api::V1::BillsController POST #create when logged in
Failure/Error: post :create, { bill: bill_attributes }
Apipie::ParamInvalid:
Invalid parameter 'value' value "41.64794235693306": Must be Float
# ./app/controllers/concerns/exception_aspects.rb:4:in exception_wrapper
# ./spec/controllers/api/v1/bills_controller_spec.rb:135:in block (4 levels) in <top (required)>
My test I try indicate request.headers['Content-Type'] = 'application/json'
let(:bill_attributes) { FactoryGirl.attributes_for :bill }
before(:each) do
request.headers['Content-Type'] = 'application/json'
post :create, { bill: bill_attributes }
end
it "when is valid description" do
expect(json_response[:description]).to eq(bill_attributes[:description])
end
My factory is
FactoryGirl.define do
factory :bill do
description { FFaker::Lorem.phrase }
value { (rand() * 100).to_f }
end
end
My controller validations are
api :POST, "/bills", "Add a new bill to an event"
description "Add a new bill"
param :bill, Hash, :required => true, :action_aware => true do
param :description, String, "Bill description"
param :bill_photo, Hash, :required => false do
param :base64image, String, "Base 64 image file"
end
param :value, Float, "Amount of the bill"
end
I try to change validation :value from Float to :number but the problem continues
I am using rails 4.2.3 and rspec 3.3.0
post :create, params: {}, as: :json
This is what works in Rails 5.0.3 and rspec-rails 3.6.0
Rails controller specs now inherit from ActionDispatch::IntegrationTest instead of ActionController::TestCase. But RSpec controller specs still use ActionController::TestCase which is deprecated.
Relevant Rails commit here
I added format json to post request and It worked like charm
before(:each) do
post :create, { bill: bill_attributes, format: :json }
end
None of above works for me.(rails 5.1.7, rspec 3.6)
The simple way you can give it a try is stub ActionController::Parameters
In my case, in controller I always use permit for strong parameters.
def product_parameters
_params = params.permit :name, :price
# validate for price is integer
raise BadRequest, code: 'blah_code' unless _params[:price].is_a?(Integer)
end
and then in Rspec I stub ActionController::Parameters like below
allow_any_instance_of(ActionController::Parameters).to receive(:permit).and_return(ActionController::Parameters.new(name: 'product name', price: 3000).permit(:name, :price)
just like that, and Rspec test checking Integer will work
NOTE: This can also apply with send boolean, float in params too.
For Rails 4 We can try this
post 'orders.json', JSON.dump(order: {boolean: true, integer: 123}), "CONTENT_TYPE" => "application/json"
In my case using rails 4.2, rspec 3.5 and ruby 2.3
post '/api/webhooks/v1/subscriptions', { abc: 134 }.to_json, headers
the to_json part is the most important here and the headers have:
let(:headers) do
{
'Authorization' => 'Basic my-token',
'Content-Type' => 'application/json'
}
end

How can find domain name in Grape::API

I am not able to fetch request object in Grape::API, My method is
module Artical
module Railsapp
module V1
class Articleapi < Grape::API
include Railsapp::V1::Defaults
resource :articleapi do
desc "Return all article"
get "", root: :articles do
error!({:error_message => "Please provide a article id."}, 422)
end
desc "Return a acticle"
params do
requires :id, type: String, desc: "ID of the photo"
end
get ":id", root: "photo" do
#Artical = Contents.where(id: params[:id],content_type: 'Article').first
if #Artical.present?
error!({:success_message => "Record found",:result => #Artical }, 300)
else
error!({:error_message => "Record Could not found"}, 422)
end
# Photos.where(:id => #id).update_all(publish_status: #status_value)
end
end
In any grape endpoint you can access the request object as "request".
The request object has many methods available to access its various parameters.
For example: If you want to access the path details of a request then :
request.path_info
will give the path details of the current request.If you want to know various methods available for request object, just print and check :
request.methods

Grape: required params with grape-entity

I'm writing an API server with grape and i choose to use grape-entity because it has the capability to auto generate the documentation for swagger.
But now i have a problem when i set a param as required. Because grape don't validate that the param is present. It looks like grape ignores the required: true of the entity's params.
app.rb
module Smart
module Version1
class App < BaseApi
resource :app do
# POST /app
desc 'Creates a new app' do
detail 'It is used to re gister a new app on the server and get the app_id'
params Entities::OSEntity.documentation
success Entities::AppEntity
failure [[401, 'Unauthorized', Entities::ErrorEntity]]
named 'My named route'
end
post do
app = ::App.create params
present app, with: Entities::AppEntity
end
end
end
end
end
os_entity.rb
module Smart
module Entities
class OSEntity < Grape::Entity
expose :os, documentation: { type: String, desc: 'Operative system name', values: App::OS_LIST, required: true }
end
end
end
app_entity.rb
module Smart
module Entities
class AppEntity < OSEntity
expose :id, documentation: { type: 'integer', desc: 'Id of the created app', required: true }
expose :customer_id, documentation: { type: 'integer', desc: 'Id of the customer', required: true }
end
end
end
Everything else is working great now, but i don't know how to use the entities in a DRY way, and make grape validating the requirement of the parameter.
After some work, I was able to make grape work as I think it should be working. Because I don't want to repeat the code for both of the validation and the documentation. You just have to add this to the initializers (if you are in rails, of course). I also was able to support nested associations. As you can see, the API code looks so simple and the swagger looks perfect.
Here are the API and all the needed entities:
app/api/smart/entities/characteristics_params_entity.rb
module Smart
module Entities
class CharacteristicsParamsEntity < Grape::Entity
root :characteristics, :characteristic
expose :id, documentation: { type: Integer, desc: 'Id of the characteristic' }
end
end
end
app/api/smart/entities/characterisitcs_entity.rb
module Smart
module Entities
class CharacteristicsEntity < CharacteristicsParamsEntity
expose :id, documentation: { type: Integer, desc: 'Id of the characteristic' }
expose :name, documentation: { type: String, desc: 'Name of the characteristic' }
expose :description, documentation: { type: String, desc: 'Description of the characteristic' }
expose :characteristic_type, documentation: { type: String, desc: 'Type of the characteristic' }
expose :updated_at, documentation: { type: Date, desc: 'Last updated time of the characteristic' }
end
end
end
app/api/smart/entities/apps_params_entity.rb
module Smart
module Entities
class AppsParamsEntity < Grape::Entity
expose :os, documentation: { type: String, desc: 'Operative system name', values: App::OS_LIST, required: true }
expose :characteristic_ids, using: CharacteristicsParamsEntity, documentation: { type: CharacteristicsParamsEntity, desc: 'List of characteristic_id that the customer has', is_array: true }
end
end
end
app/api/smart/entities/apps_entity.rb
module Smart
module Entities
class AppsEntity < AppsParamsEntity
unexpose :characteristic_ids
expose :id, documentation: { type: 'integer', desc: 'Id of the created app', required: true }
expose :customer_id, documentation: { type: 'integer', desc: 'Id of the customer', required: true }
expose :characteristics, using: CharacteristicsEntity, documentation: { is_array: true, desc: 'List of characteristics that the customer has' }
end
end
end
app/api/smart/version1/apps.rb
module Smart
module Version1
class Apps < Version1::BaseAPI
resource :apps do
# POST /apps
desc 'Creates a new app' do
detail 'It is used to register a new app on the server and get the app_id'
params Entities::AppsParamsEntity.documentation
success Entities::AppsEntity
failure [[400, 'Bad Request', Entities::ErrorEntity]]
named 'create app'
end
post do
app = ::App.create! params
present app, with: Entities::AppsEntity
end
end
end
end
end
And this is the code that do the magic to make it work:
config/initializers/grape_extensions.rb
class Evaluator
def initialize(instance)
#instance = instance
end
def params parameters
evaluator = self
#instance.normal_params do
evaluator.list_parameters(parameters, self)
end
end
def method_missing(name, *args, &block)
end
def list_parameters(parameters, grape)
evaluator = self
parameters.each do |name, description|
description_filtered = description.reject { |k| [:required, :is_array].include?(k) }
if description.present? && description[:required]
if description[:type] < Grape::Entity
grape.requires name, description_filtered.merge(type: Array) do
evaluator.list_parameters description[:type].documentation, self
end
else
grape.requires name, description_filtered
end
else
if description[:type] < Grape::Entity
grape.optional name, description_filtered.merge(type: Array) do
evaluator.list_parameters description[:type].documentation, self
end
else
grape.optional name, description_filtered
end
end
end
end
end
module GrapeExtension
def desc name, options = {}, &block
Evaluator.new(self).instance_eval &block if block
super name, options do
def params *args
end
instance_eval &block if block
end
end
end
class Grape::API
class << self
prepend GrapeExtension
end
end
This is the result of the example:
I love the grape/grape-swagger/grape-entity combination for building API's. I generally use the grape entities for building the result, and not at all for documenting/validating the API. According to the documentation (for grape-entity) it should work, but I am guessing just to build the documentation.
According to the grape documentation on parameter validation and coercion it requires a block to enforce any validation/coercion.
[EDIT: mixing up params]
You can define the params in the desc using an entity, but for validation you have to supply the params block, on the same level as the desc block, so for example:
# POST /app
desc 'Creates a new app' do
detail 'It is used to re gister a new app on the server and get the app_id'
params Entities::OSEntity.documentation
success Entities::AppEntity
failure [[401, 'Unauthorized', Entities::ErrorEntity]]
named 'My named route'
end
params do
requires :name, String
optional :description, String
end
post do
app = ::App.create params
present app, with: Entities::AppEntity
end
They are both called params but located quite differently and with a different function.
I am not sure if the desc block has any use other than documentation (and how to extract this documentation is a bit of a mystery to me).
The grape-swagger gem does not use it, my typical desc looks like this:
desc "list of batches", {
:notes => <<-NOTE
Show a list of all available batches.
## Request properties
* _Safe:_ Yes
* _Idempotent:_ Yes
* _Can be retried:_ Yes
NOTE
}
params do
optional :page, desc: 'paginated per 25'
end
get do
present Batch.page(params[:page]), with: API::Entities::Batch
end
where the :notes are rendered using markdown. How this looks in swagger-ui

Resources