How can find domain name in Grape::API - ruby-on-rails

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

Related

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?

rails request spec PUT method with json doesn't go through

In one of my tests I have to verify that a certain offer returns 404 response if the available limit (10 seats for example with that offer) are all sold, or it has expired which ever comes first.
I just cannot get the PUT request to work in Request Spec, here's my code
RSpec.describe "Offers", type: :request do
describe "gives not found response" do
it "when available limit exhausts before date till available" do
offer = Offer.new
Timecop.freeze(Date.today - 12.days) do
offer = FactoryGirl.create(:offer)
end
payload = FactoryGirl.attributes_for(:offer, :available_limit => 0, :discount_id => offer.discount.id, :coupon_id => offer.coupon.id)
sign_in
put '/offers/'<<offer.id, params: { id: offer.id, offer: payload }, as: :json
get "/vouchers/"<<offer.coupon.voucher_code
expect(response.status).to eq 404
end
end
end
needles to say that I have tried many hacks including
put '/offers/'<<offer.id.to_s<<".json", params: { offer: payload }
or even
put '/offers/'<<offer.id.to_s<<".json", payload
What I also noticed was that in one of the combinations the request did go through but it responds with both HTML and JSON format which lead to error in the spec as I am not running them under capybara (and I do not want to either)
Don't use << to build those paths. Using << with an id will insert a character code equivalent to the integer value of the id, e.g.
'XYZ' << 123 << 'ABC'
=> "XYZ{ABC"
Just use normal string interpolation, e.g.
put "/offers/#{offer.id}", params: { id: offer.id, offer: payload }, format: :json
get "/vouchers/#{offer.coupon.voucher_code}"
Credit to #house9 for noticing the format: :json part as well.
I think you want to use format: :json not as: :json
Try:
params = { id: offer.id, offer: payload }
put :offers, params: params, format: :json

Grape API saving serialized attribute

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

How can I return custom errors to my form in Rails?

I have a simple form on a website where a user enters a mailing address.
I have a service which can validate this address and return various responses, either Success, Suspect, or Invalid, as well as return the full and most complete zip code for that address. If the response is "Success", then I will save it to the db. If the response is "Suspect", then I will update the zip field and ask them to confirm. If the response is "Invalid" then I will return an error message asking them to contact us in person.
I'm trying to set up my rails create action such that it makes a call to my service (for example http:/addresssValidator.com) and I want to inform the user if they have a valid address or not, and update the zip with the suggested zip code.
Looking for address validation in rails however, seems to only give me APIs for using the built in error and validation system in rails and not how to return my own custom results to the form.
How can I do this?
Below is my code:
def create
#valid = validate_address
#address = Address.new(address_params)
if #valid
if #address.save
redirect_to "/survey/success"
else
p #address.errors
respond_to do |format|
format.json {render json: #address.errors}
format.html {render "/survey/failure"}
end
end
else
##//Display errors
end
end
def validate_address
#api_key = "my_api_key"
HTTParty.post("http://addressValidator.com",
{
:body => [
"StreetAddress" => params[:address][:address_line_2] + " " + params[:address][:address_line_1],
"City" => params[:address][:city],
"PostalCode" => params[:address][:zip],
"State" => params[:address][:state],
"CountryCode" => params[:address][:country],
"Locale" => "en" ].to_json,
:APIKey => #api_key ,
:headers => { 'Content-Type' => 'application/json', 'Accept' => 'application/json'}
})
return false;
##//return actual results from validator
end
If you want to add a custom error to a specific field:
#address.errors[:FIELD_NAME] << "Custom Error Message"
that way the error is added to the instance that you have, and will be shown in the form itself as the other errors appear.
if you just want to display an error, you can add it to the flash[:error] like this:
flash[:error] = "Invalid address"
if you want the error to move to the next method (like if you will use redirect after it), or:
flash.now[:error] = "Invalid address"
if you want the error to be available only in current action.
as #illusionist said in comment.
and in the html code, check if it exists, and if so, print it:
<% if flash[:error] %>
<%= flash[:error] %>
<% end %>
in both ways, you can add the error from #valid.
If you want to store the new zip, add #address.zip = #valid.zip. That way it will show it in the field in the form.
Models do the work, forms are stupid
In Rails you add errors to your models. To add an error you would do something like this:
if #address.country == "Sealand"
#address.errors[:country] << ["we don't ship there"]
end
In Rails forms are just are just simple form-builders bound to a model which create HTML. They don't actually have a role in validation besides displaying errors.
Custom validations
Rails lets you create custom validator classes and HTTParty is made to extend classes.
Lets mosh them together:
class AddressValidator < ActiveModel::Validator
include HTTParty
base_uri 'addressValidator.com'
format :json
def validate(address)
response = self.post('/',
# Note that I am just guessing here.
body: {
"StreetAddress" => address.street_address,
"City" => address.city,
"PostalCode" => address.zip,
"State" => address.state,
"CountryCode" => address.country
}
)
if response.success?
# bind the errors to the model
unless response["StreetAddress"] == "valid"
record.errors[:street_address] << response["StreetAddress"]
end
else
e = response.response
logger.warn "Failed to remotely validate Address. #{ e.message }"
address.errors[:base] << "Failed to remotely validate!"
end
end
end
Of course you will need to adapt this to the actual response from the API and your model. This is just a starting point demonstrating the coarse strokes.
So how would you use it?
def Address < ActiveRecord::Base
validates_with AddressValidator
# ...
end
http://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations
http://blog.teamtreehouse.com/its-time-to-httparty

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