How to handle possible params on Rails 4? - ruby-on-rails

I'm doing an API for my app.
Currently, you can call api/v1/clients and get the Clients JSON back, as expected. You can also do api/v1/clients?client_id=1 and get the JSON representation of the Client object with id 1.
Here's my API::V1::ClientsController:
class API::V1::ClientsController < ApplicationController
def index
if params[:client_id]
#client = Client.find(params[:client_id])
render template: 'api/v1/clients/show'
else
#clients = Client.all
end
end
end
I want that if, for example, you have a typo on the endpoint (api/v1/clients?clent_id=1), the app returns a JSON object with an error:
{
error: {
error_code: 10,
error_description: "Bad endpoint"
}
}
Is there a way to, say, make a switch statement on the params to handle the possible cases?

My suggestion:
Make a private method in your controller, this one will check your params:
If params is empty it returns false
If params contains 'client_id' and its value is a numeric it returns the value
Otherwise it raises an exception.
Then in you action method you use this result:
If the result is false you display all results
Otherwise it display the record based on the id returned by your private method
As for the exception: you use a rescue_from to display the "Bad endpoint" JSON response

Related

Remote API method won't create records based on received params

I have an api for mass creating records of a model Series. A remote machine sends a POST request to my url, with an array #series passed as a json parameter, like this:
#series = [{:id=>1,name:"test"}, {:id=>2,name:"test2"}]
req = Net::HTTP::Post.new(post_uri, 'Content-Type' => 'application/json')
req.body = {series: #series}.to_json
res = http.request(req)
but I cannot for the life of me get the respective Series to be created. Here is the method that receives the data and is supposed to create one Series for each hash in the #series array:
def api
series = params[:series]
series.each do |s|
name = s[:name]
if !Series.where(name: name).exists?
Series.create(s)
end
end
end
The params are definitely passed through, but no Series are created. When I check my logs there's a 500 error, but since it's remote, I have no way of getting a more specific error.
When I remove the params and just create a generic Series for each hash in the #series array, it works. For example, with the following code, if #series has 3 hashes, 3 Series are created:
def post_product_data
series = params[:series]
series.each do |s|
name = s[:name]
if !Series.where(name: name).exists?
Series.create #GENERIC SERIES NOT BASED ON PARAMS
end
end
end
I thought it might be a permissions issue, so I tried permitting all params. But when I changed the method to this, I got a "undefined method "permit!" error:
def post_product_data
series = params[:series]
series.each do |s|
name = s[:name]
if !Series.where(name: name).exists?
Series.create(s.permit!) #TRIED PERMITTING ALL PARAMS
end
end
end
Anyone have any ideas?
UPDATE
I changed the offending line to this:
Series.create({id: s[:id], name: s[:name]})
and now it works. I have no idea why, since the hashes should have been inserting the exact same thing. But at least it works finally.

Doing search by count in json with params results

I'm trying to implement a counter filter in my json. When I access the url api/v1/contacts?Results=2 [sic], I would like to get only two results from my json.
For this, I created two methods in my controller: an index that takes the information from the database and turns render into json, and a method that returns the number of times the json is returned.
class Api::V1::ContactsController < ApplicationController
before_action :results, only: [:index]
def index
#contacts = Contact.all
render json: {results: #contacts[0..#number_results]}
end
def results
if params[:results]
results = params[:results]["results"]
#number_results = results.to_i
else
#number_results = 3
end
end
end
Regardless of the value entered for results =, the value of #number_results is set to 0, so whenever I type results = I, get only the first result of json. If I do not type results = in the url, it sets #number_results to 3, and shows four results on the screen.
Can anyone help?
First, in the url you propose, "Results" is capitalized. If that is how you intend to submit it, then you'll need to search for it that way on the back end. But since all your code uses lowercase "results", we'll go with that. You should modify your url in the same way: api/v1/contacts?results=2.
If that's what your url looks like then the number you pass in is accessible in params[:results]. If you pass no parameter, then params[:results] will return nil. Don't call #to_i on the param before you check for its existence, because nil.to_i is 0.
All that said, here's what you probably want:
class Api::V1::ContactsController < ApplicationController
def index
number_results = params[:results] || 3
#contacts = Contact.all.limit(number_results.to_i)
render json: {results: #contacts}
end
end
If the result of params[:results] is nil, then number_results is assigned 3.
Now we use #limit to return only the number of contacts that was requested. This allows us to do away with the #results method entirely. You can also get rid of the before_action callback.

How to create Post request to Rails API using Postman?

I am new to Postman. I have a Rails server running on the background. I am trying to mock a POST request, but it is not being accepted.
Let's say the model is called manufacturer_organization.rb. Inside, it requires 3 parameters: organization_id (uuid data type), manufacturer_id (integer data type), and account_number (string data type). manufacturer_organization belongs_to organization and it also belongs_to :manufacturer (vice versa; manufacturer and organization has_many manufacturer_organization)
Inside manufacturer_organizations_controller.rb, I have a create method:
def create
#manufacturer_organization = ManufacturerOrganization.new(manufacturer_organization_params)
if #manufacturer_organization.save
puts "success!"
render json: #manufacturer_organization
else
puts "Sorry, something went wrong"
end
end
I can confirm that I have sufficient authorization; when I perform a GET request I got the right JSON response. I am using rails serializer and I have setup serializer for this model as well. Route is also setup using resources :manufacturer_organizations. My gut feeling says the way I am using postman is wrong.
Here is the screenshot of Postman app. I have the right address on address bar, and I am performing a POST request. I have the three params under key-value.
After I Send it, under my Rails Server log I see:
Started POST "/manufacturer_organizations" for 127.0.0.1 at 2017-04-13 16:56:44 -0700
Processing by ManufacturerOrganizationsController#create as */*
Parameters: {"organization_id"=>"fb20ddc9-a3ee-47c3-bdd2-f710541-ff89c", "manufacturer_id"=>"1", "account_number"=>"A rand
om account number test"}
...
  (0.4ms)  BEGIN
   (0.3ms)  ROLLBACK
Sorry, something went wrong
I can do ManufacturerOrganization.new(organization_id: Organization.last.id, manufacturer_id: Manufacturer.last.id, and account_number: "random test account number") just fine inside rails console.
How can I submit a POST request from postman to add a new manufacturer_organization?
Edit:
def manufacturer_organization_params
api_params.permit(:organization_id, :manufacturer_id, :account_number)
end
whereas inside application_controller.rb
def api_params
#api_params ||= ActionController::Parameters.new(ActiveModelSerializers::Deserialization.jsonapi_parse(params))
end
Edit2:
I added error.full_messages and this is what I got:
Manufacturer can't be blank
Organization can't be blank
Account number can't be blank
Why are they blank?
You can pass the data using params or within the body request.
The best way to do this is using the body, because you can send files and the request becomes more clean without the params.
To send data in the body, you must pass the model name and attribute in the "key" field, and the value in the "value" field, like this:
I don't understand what you do to your params. There is a reason the ActiveModelSerializers::Deserialization is namespaced in the "Model" namespace. It shouldn't be used to serialize or de-serialize internet params, but instead it's for serializing/de-serializing model instances.
If parameters arrive in the correct format ActionController::Base from which AplicationController and thus ManufacturerOrganizationsController inherit will de-serialize them for you. The Rails query parameter format looks as follows:
name=something #=> params[:name] = 'something'
names[]=something1&names[]=something2 #=> params[:names] = ['something1', 'something2']
instance[id]=1&instance[name]=foo #=> params[:instance] = {id: '1', name: 'foo'}
This can also be stacked and is used for nested resources by Rails. Example:
instance[title]=some&instance[nested][name]=thing&instance[nested][ids][]=1&instance[nested][ids][]=2
#=> params[:instance] = {title: 'some', nested: {name: 'thing', ids: ['1', '2']}}
Having said that let's get to your example. First of al let us throw away those manual building of params and stick to the convention:
class ManufacturerOrganizationsController
# ...
private
def manufacturer_organization_params
# arriving params should look like this:
#
#=> params = {
# manufacturer_organization: {
# organization_id: 'fb20ddc9-a3ee-47c3-bdd2-f710541-ff89c',
# organization_id: '1',
# account_number: 'A random account number test'
# }
# }
#
# The method #require raises an exception if the provided key
# is not present or has a blank value (with exception of false).
# If the key is found and has a value present than that value is
# returned.
#
params.require(:manufacturer_organization)
.permit(:organization_id, :manufacturer_id, :account_number)
end
end
With that out of the way let's send the correct formatted params:
+--------------------------------------------+---------------------------------------+
| Key | Value |
|--------------------------------------------|---------------------------------------|
| manufacturer_organization[organization_id] | fb20ddc9-a3ee-47c3-bdd2-f710541-ff89c |
| manufacturer_organization[manufacturer_id] | 1 |
| manufacturer_organization[account_number] | A random account number test |
+--------------------------------------------+---------------------------------------+
Those 2 things combined should let you create your resource successfully.
The key thing you should take from this is that params is not a string containing al the params that should be de-serialized. It already should be de-serialized, if it's not than you might have send your parameters wrong.
Ruby on Rails and Postman - Post request.
Hello, this is an example that I developed with Postman and Rails API.
Postman.
I can't add images but this what you have to add in postman Key = Value
Change to Post Request and send.
book[name] = 'Harry Potter'
book[author] = J.K. Rowling
Ruby on Rails 7.
Rails maintains the same code.
def create
#book = Book.new(book_params)
if #book.save
render json: #book, status: :created, location: api_v1_books_url(#book)
else
render json: #book.errors, status: :unprocessable_entity
end
end
def book_params
debugger
params.require(:book).permit(:name, :author, :price)
end
I hope this helps.

Printing error when using PARAMS in Rails

For my API in RAILS I have programmed a code that basically does the following.
class Api::V1::NameController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
end
def create
# Loading data
data_1_W = params[:data1]
data_2_W = params[:data2]
while len > i
# -Here I do some calculations with data_1_W and data_2_W.
# Its not important to show the code here
end
# -Organizing outputs to obtain only one JSON-
# Its not important to show the code here
# Finally HTTP responses
if check_error == 1
render status: 200, json: {
message: "Succesful data calculation",
data_output: response_hash
}.to_json
end
end
end
To test that everything is working I use the cURL command. I notice that loading the data could be a problem and therefore the code would break.
I want to tell the user that it was an error loading the data for some reason (HTTP response), but I don't know where to put it. If I put and else under my success status it would not print it because the code breaks just starting (instead of sending the correct name - d '#data.json' of the data in cURL I send -d '#dat.json').
The data I am loading is a JSON data {"data1":[{"name1":"value1"},{"name2":number2}...],"data2":[{"name1":"value1"},{"name2":number2...}]}. (This data has 70080 rows with 2 columns if we see it as a table, which I divided into two in my CODE for calculations purposes data_1_W and data_2_W)
Could anyone help me where to put it? more or less like this:
render status: 500, json: {
message: "Error loading the data",
}.to_json
Put it in a rescue block around the code that throws the error.
E.g.
def func
# code that raises exception
rescue SomeException => e
# render 422
end
Since you are working in Rails I'd recommend going the rails way. This means that I would create some kind of service and initialize it in the create action.
Now, within the service you do all you funky stuff (which also allows you to clean this controller and make i look prettier) and the moment a condition is not fulfilled in that service return false. So...
# controllers/api/v1/name_controller.rb
...
def create
meaningful_variable_name = YourFunkyService.new(args)
if meaningful_variable_name.perform # since you are in create then I assume you're creating some kind of resource
#do something
else
render json: {
error: "Your error",
status: error_code, # I don't think you want to return 500. Since you're the one handling it
}
end
end
# services/api/v1/your_funky_service.rb
class Api::V1::YourFunkyService
def initiliaze(params)
#params = params
end
def perfom #call it save if you wish
....
return false if check_error == 1
end
end

Rails: validation error codes in JSON

So, I am writing Rails web application which has JSON API for mobile apps. For example, it sends POST JSON request to example.com/api/orders to create order.
{id: 1, order: { product_name: "Pizza", price: 10000}}
In case of validation errors I can response with HTTP 422 error code and order.errors.full_messages in json. But it seems better for me to have specific error code in JSON response. Unfortunately, it seems like Rails does not provide ability to set error code for validation error. How to solve this problem?
You can pass a custom status code by using the status option when rendering the response.
def create
#order = ...
if #order.save
render json: #order
else
render json: { message: "Validation failed", errors: #order.errors }, status: 400
end
end
I usually tend to return HTTP 400 on validation errors. The message is a readable status response, the errors are also attached.
This is a respons example
{
message: "Validation failed",
errors: [
...
]
}
You can also embed additional attributes.
I was after something similar, so what I did was extend String eg
class ErrorCodeString < String
def init(value, error_code)
#error_code = error_code
super(value)
end
def error_code
#error_code
end
end
Then in a custom validation (this won't work on standard validation) I'd do
errors.add(:email, ErrorCodeString.new('cannot be blank', 50)
Now when you return your JSON you can check to see if the error value is an ErrorCodeString and add the error_code value to the output. As ErrorString inherits String, you shouldn't be breaking anything else along the way.
Rails 5 has error.details that can be used for exactly that.
In the model
errors.add(:price, 1023, message: "Transaction value #{price} is above limit (#{maximum_price}).")
In the controller
format.json { render json: #order.errors.details, status: :unprocessable_entity }
error details can be anything, eg. you could also use :above_limit instead of 1023.
The API response body will then look like
pp JSON.parse(response)
{"price"=>[{"error"=>1023}]}
This feature has been backported to Rails 4, see also http://blog.bigbinary.com/2016/05/03/rails-5-adds-a-way-to-get-information-about-types-of-failed-validations.html
Also: Is there a way to return error code in addition to error message in rails active record validation?

Resources