Rails RABL: how to respond with specified http status code? - ruby-on-rails

Basically I have the following controller method:
def create
begin
#check_in = create_check_in()
rescue => exception
render json: { message: exception }, status: 500
end
end
and the following json.rabl file:
object #check_in => :event_check_in
attributes :id
What I try to achieve is to set manually the HTTP status code of the response. It currently responds with 200, and I need it to be 201 instead.
I saw very few similar question and the answer was generally to render / respond_with from the controller action, so I tried something like this:
def create
begin
#check_in = create_check_in()
render #check_in, status: 201
rescue => exception
render json: { message: exception }, status: 500
end
end
but all my attempts failed, throwing various errors.
Is there a way I could set the status code?

The issue is you're passing in #check_in as the first argument of the render method when it expects the first argument to be a hash of options, including the status option.
Your status: 201 option is being passed in as a hash to the methods second argument and being ignored.
Typicallya render call will look something like:
render json: #check_in.as_json, status: 201
# or more commonly something like
render action: :create, status: 201 # the #check_in variable is already accessible to the view and doesn't need to be passed in
# or just
render status: 201
# since by default it will render the view with the same name as the action - which is `create`.
There are lots of ways to call render, see the docs for more.
-- EDIT --
Max has a great comment - I'd strongly advise against rescuing from all exceptions and also against doing it in a specific controller action. In addition to his suggestion, Rails 5+ supports :api formatting for exceptions out of the box or, if you need more, I'd look at a guide like this one.

Related

How to test with rspec a returned exception?

def show
begin
#cart = Cart.find(params[:id])
authorize(#cart)
#cart_entries = CartEntry.where(:cart_id => #cart.id)
#products = {}
#pr_references = {}
#cart_entries.each do |cart_entry|
#pr_references[cart_entry.id] = Reference.find(cart_entry.reference_id)
#products[cart_entry.id] = Product.find(#pr_references[cart_entry.id].product_id)
end
rescue ActiveRecord::RecordNotFound => e
respond_to do |format|
format.json {render json: {'error': e}, status: :not_found}
end
end
I want to test when Cart.find() doesn't find the cart and I want to test the method return a 404 HTTP code with the test below.
it 'don\'t find cart, should return 404 error status' do
delete :destroy, params: {id: 123, format: 'json'}
expect(response).to have_http_status(404)
end
Have you got some indications or solution to do that ?
I'm a nooby with ruby on rails, if you have some tips with the code I posted I'll take it.
Thank you :)
It seems some other code is raising an exception before your Cart.find statement is executed. For this reason, the ActiveRecord::RecordNotFound exception is never risen, and it is never captured by the rescue block.
Based on the exception that is raising, it seems you are using Pundit gem for dealing with authorization. The authorization rules offered by this gem are surely running before your show method starts. Probably this is happening as a consequence of a before_filter statement, either in this controller or in a parent controller.
You will need to handle this kind of errors in your application. It may be handy to use a rescue_form statement in a base controller that is inherited by all other controllers, so that you don't have to deal with this kind of errors in every controller.

Why is yield not passing the result to block (Rails)?

I know there are several SO questions as well as online articles on using yield in Rails. But I'm still having trouble understanding what's wrong with my code below, and would appreciate any advice.
In my app, I have:
A controller that passes data to the command class's run method, and returns the request status based on the result of the Command.run (true/false)
A command class that deals with the actual meat of the process, then yields true if it succeeded, or false if it failed
However, the command class seems to be failing to yield the results to my controller. According to the error messages when I run my tests, it seems like my block in the controller isn't being recognized as a block:
# If I use "yield result":
LocalJumpError: no block given (yield)
# If I use "yield result if block_given?":
# (This is because I have "assert_response :success" in my tests)
Expected response to be a <2XX: success>, but was a <400: Bad Request>
How should I rewrite the block (do ... end part in the controller below) so that yield works correctly? Or if the issue lies elsewhere, what am I doing wrong?
I've provided a simplified version of my code below. Thank you in advance!
# controller
def create
Command.run(params) do
render json: { message: 'Successfully processed request' }
return
end
render json: { message: 'Encountered an error' }, status: :bad_request
end
# command class
def run(params)
# Do some stuff, then send HTTP request
# "result" below returns true or false
result = send_http_request.parsed_response == 'ok'
yield result
end
def self.run(params)
new.run(params)
end
Note: This code works if I use if true... else... in the controller instead of a block, and just return the boolean result instead of yielding it. But here I'd like to know how to make yield work.
In your controller you need to have a variable for the result.
def create
Command.run(params) do |result|
if result
render json: { message: 'Successfully processed request' }, status: :success
else
render json: { message: 'Encountered an error' }, status: :bad_request
end
return
end
render json: { message: 'Encountered an error' }, status: :bad_request
end
(EDIT)
Also, you are calling the class method which call the instance method. You have to pass the block from the calling code to the instance method you are calling.
def self.run(params, &block)
new.run(params, &block)
end
EDIT: ah, so you have a class method run and instance method run.
Either do as Marlin has suggested and supply the block explicitly from class method to the instance method.
Or use only the class method as I've initially suggested (it doesn't
seem like there's any reason to instantiate Command in your case):
def self.run(params, &block)
result = send_http_request.parsed_response == 'ok'
block.yield(result)
end

The order in which validation and save are executed

In an integration test, I'm trying to save an invalid link. It is invalid because it links two nodes that belong to two different organizations (which my model validation does not permit). The error message displayed is however not the error message from my model validation but the error "Unable" from the controller.
I would have expected the validation from the model to come before this line in the controller. Moreover, I don't understand why, if we would take the model validation out of account, it wouldn't save. Could someone perhaps explain?
Part of my controller method:
if link.save
render json: #organization, message: "Saved", status: :created
else
render json: link, message: "Unable", status: :bad_request)
end
And in the Link model:
validate :same_org
def same_org
org1 = self.first_node.organization unless self.first_node.nil?
org2 = self.second_node.organization unless self.second_node.nil?
unless org1 == org2
errors.add(:second_node_id, "You can't link two nodes from different organizations")
end
end
From the api docs:
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-save
"By default, save always run validations. If any of them fail the action is cancelled and save returns false."
So, you are correct in your assumption that validations (by default) run first. So the issue is that you are not passing that message to your view, not surprising as this line:
render json: link, message: "Unable", status: :bad_request)
Just passed back "Unable"
What you need to do is accesses the errors messages. replace "Unable" with
link.errors.full_messages.to_sentence
And you should be good.
this happens because at your controller you don't send validation error message so, you should change your controller code to something like
if link.save
render json: #organization, message: "Saved", status: :created
else
render json: {errors: link.errors.full_messages, message: "Unable"}, status: :bad_request
end

What's the difference between using render instead of respond_with/to in a Rails API?

I am building a simple rails tutorial on how to build APIs for some students and I am building it without the respond_to and respond_with because I just want to see if I can build an api without using a gem. This is what I have and my tests pass:
controller:
class Api::V1::SuyasController < ApplicationController
def index
render json: Suya.all
end
def create
render json: Suya.create(suyas_params)
end
private
def suyas_params
params.require(:suya).permit(:meat, :spicy)
end
end
routes:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :vendors
resources :suyas
end
end
end
tests:
require 'test_helper'
class Api::V1::SuyasControllerTest < ActionController::TestCase
test "index can get all the suyas" do
Suya.create(meat: "beef", spicy: true)
Suya.create(meat: "kidney", spicy: false)
get :index
suyas = JSON.parse(response.body)
assert_equal "beef", suyas[0]["meat"]
assert_equal true, suyas[0]["spicy"]
assert_equal "kidney", suyas[1]["meat"]
assert_equal false, suyas[1]["spicy"]
end
test "create can create a suya" do
assert_difference("Suya.count", 1) do
create_params = { suya: { meat: "beefy", spicy: true }, format: :json }
post :create, create_params
suya = JSON.parse(response.body)
assert_equal "beefy", suya["meat"]
assert_equal true, suya["spicy"]
end
end
end
What's the difference between using render vs respond_with? I can't find any answers. Is there even something that I am doing wrong? Why are there two ways to create APIs (respond_to/respond_with AND this way?)
-Jeff
render is part of Rails and it just renders whatever you say in whatever format you say. Typically a view, possibly a string, possibly a file.
A pretty low-level function that renders whatever you say making a few assumptions per conventions, like where to look for a view.
respond_to is a micro-DSL that allows you to respond differently to different formats being requested.
I. e. in a block with |format| call to format.json requires a block that will be executed on requests for JSON, otherwise will be a no-op (no operation). Also, if respond_to didn't execute any block, it responds with a generic 406 Not Acceptable (server cannot respond in any format acceptable by the client).
While it is possible to do if request.json?, it's not so readable and needs to explicitly specify when to respond with 406.
respond_with, formerly part of Rails, now (since 4.2) in a separate gem responders (for a reason), takes an object and uses it to construct a response (making a lot of assumptions, all of them can be given at controller declaration).
It makes code much shorter in typical use cases (i. e. some APIs). In not-so-typical use cases it can be customized to suit your needs. In highly unusual use cases it's of no use.
I may be overly simplifying things, it's a general overview.
There are two things :)..render and respond_to.
Render is used to create a full response and sends it back to the browser.
So render is used in respond_to ,to make your action very responsive for every call whether it can be js/ajax call,full page load(html),json(to show autosearch dropdown,tokens) or xml.So if i want my method to work and respond to every calls from client,i will use the below block in my action.
respond_to do |format|
format.html { redirect_to(person_list_url) }
format.js {render "show_person_details"}
format.xml { render :xml => #people.to_xml }
format.json { render json: #people}
end
above controller will work on every scenario,such as js/html/json and xml without getting 403 Forbidden error which we get usually get when a js call is made to action having only format.html and not format.js
HOPE IT HELPS
I think the answer is that render only allows me to respond with JSON, whereas if I use respond_to and respond_with, I can respond in more than just one manner? Is that all?

Set response status before render in rails 4

I'd like to set the response status value in particular action methods before the render method is called. Is this not possible?
Many of my methods in controllers render JSON API views for action methods like #destroy, #update, #create and those actions simply invoke #show or #index as is appropriate. However, I'd like to also return the appropriate HTTP Response status value, like 201, 202, etc, without having to pass arguments to these methods. Essentially, I am looking for something like this:
def destroy
# code that kills
status :accepted # ArgumentError, status= silently fails
index
end
if you just call
response.status = ###
in a controller, and then don't add the :status argument at render, it should get you what you need.
you can use the method render by passing the parameter :status, example :
render nothing: true, status: 201

Resources