Respond with a status unauthorised (401) with Rails 4 - ruby-on-rails

Given the following Rails 4.2 controller:
class Api::UsersController < ApplicationController
def index
respond_to do |format|
format.html do
flash[:error] = 'Access denied'
redirect_to root_url
end
format.json do
render json: {}, status: :unauthorised
end
end
end
end
When, with RSpec 3, I try to call this index action and expect to have the status 401 I always have the status 200.
The only moment where I got the 401 is to replace the index action content with head 401 but I would like to respond with the error 401 and also build a "nice" body like { error: 401, message: 'Unauthorised' }.
Why is the status: :unauthorised ignored ?

Use error code instead of it's name:
render json: {}, status: 401

I had to replace my controller with this following:
class Api::UsersController < ApplicationController
def index
respond_to do |format|
format.html do
flash[:error] = 'Access denied'
redirect_to root_url
end
format.json do
self.status = :unauthorized
self.response_body = { error: 'Access denied' }.to_json
end
end
end
end
Using render is not preventing the called action to be executed. Using head :unauthorized is returning the right status code but with a blank body.
With self.status and self.response_body it's working perfectly.
You can see have a look to the source code my gem where I had this issue here: https://github.com/YourCursus/fortress

Replace unauthorised by unauthorized

Related

Rspec fails because too few arguments when rescuing error

In a system spec, I'm trying to test the correct handling of a database timeout. When that happens a new TinyTds::Error is raised.
Here my controller (EMData handles the DB connection)
class Json::ChartController < ApplicationController
rescue_from TinyTds::Error, with: :connection_timed_out
def index
data = EMData.call(params)
respond_to do |format|
format.json { render json: data }
end
end
def connection_timed_out(_error)
format.json { head :request_timeout }
end
end
Here my spec
context 'when the SQL Server connection times out' do
let(:data_class) { class_spy('EMData').as_stubbed_const }
it 'a feedback message is displayed' do
allow(data_class).to receive(:call).and_raise(TinyTds::Error.new('message'))
...
SUBMIT FORM VIA JS
...
expect(page).to have_content("Some Content")
end
The spec seems pretty straightforward to me. However, when I run it , I get
Rack app error handling request { GET /json/chart/ }
/app/controllers/json/chart_controller.rb:24:in `format' ....
Failure/Error: format.json { head :request_timeout }
ArgumentError:
too few arguments
Am I misisng anything here?
You're missing the respond_to do |format| in connection_timed_out(_error). It should be like:
def connection_timed_out(_error)
respond_to do |format|
format.json { head :request_timeout }
end
end

How to override rescue_from in application controller for one specific instance

I have this in my application_controller.rb:
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def user_not_authorized
redirect_back fallback_location: root_url,
warning: 'Not authorized'
end
but for one method, I need
def slug_available
authorize Page
rescue Pundit::NotAuthorizedError
render status: :unauthorized
else
render json: { available: Page.where(slug: params[:slug]).empty? }
end
However, the rescue_from is overriding the explicit rescue in slug_available, and I am getting a 302 Found instead of a 401 Unauthorized.
I would have thought an explicit rescue would have taken priority. How can I make this happen?
You can overwrite the not_authorized declaration method in your controller and check the action_name.
protected
def not_authorized
if action_name == 'slug_available'
render status: :unauthorized
else
super
end
end
then, you do not need to authorize your slug_avaiable action method
def slug_available
render json: { available: Page.where(slug: params[:slug]).empty? }
end
The ApplicationController base method needs to be a protected method.
It turned out that replacing
render status: :unauthorized
with
render plain: 'Not authorized.', status: :unauthorized
fixed the problem. It looks like Rails was trying to render the current page as HTML, with a 401 status, until I was explicit about what to render.

Rails 5 action mailer error

I'm having issues with rails action mailer. When I click on my subscribe button I keep getting the same error for almost an hour now, after spending the same amount of time trying to fix it. I don't know what I'm missing or what I need. Any help please. I want the user to click on the subscribe button and to have it save and then send them a verification email.
This is the error message I keep receiving "SubscribersController#show is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot."
This is the code for my controller-
class SubscribersController < ApplicationController
respond_to? :html, :json
def index
#subscriber = Subscriber.new
end
def show
end
def create
#subscriber = Subscriber.new(subscriber_params)
respond_to do |format|
if #subscriber.save
# Tell the UserMailer to send a welcome email after save
SubscriberMailer.welcome_email(#subscriber).deliver_now
format.html { redirect_to(#subscriber, notice: 'User was successfully created.') }
format.json { render json: #subscriber, status: :created, location: #subscriber }
else
format.html { render action: 'new' }
format.json { render json: #subscriber.errors, status: :unprocessable_entity }
end
end
end

Refactoring multiple render in controller

In my rails controller, I have to check after getting #group with before_action that this group is not system.
But I have lot's of repetition in my controller. I've tried to turn into a separate method but I get the classic :
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
Here is a part of my code without the separate method who give me the error.
def destroy
if #group.is_system?
render json: { errors: 'You can\'t delete a group system' }, status: 403
return
end
...
end
def update
if params[:group] && !params[:group].empty?
if #group.is_system?
render json: { errors: 'You can\'t edit a group system' }, status: 403
return
end
...
else
render json: { errors: 'Missing correct parameters' }, status: :unprocessable_entity
end
end
.....
You could have in a parent controller:
def render_errors(errors, status)
render json: { errors: Array(errors) }, status: status
end
def render_403(errors)
render_errors(errors, 403)
end
def render_422(errors)
render_errors(errors, 422)
end
then in your action:
before_action :check_system
def check_system
# I assume you already defined #group
render_403('You can\'t delete a group system') if #group.is_system?
end
Notice I changed a bit of your code: having errors key which is only a string is very misleading, should be an array.

DoubleRenderError when delivering message with mail_form

The following code sample is part of a Rails 3.2.16 app running on Ruby 1.9.3p484.
Whenever a new location is created or one is updated a message should be sent as defined in the after_filter.
class LocationController < InheritedResources::Base
respond_to :json
after_filter :notify_location_contact, only: [:create, :update]
def create
#location.user = current_user if current_user
create!
end
def update
update!
end
private
def notify_location_contact
message = MailForm.new
deliver_location_message(message)
end
def deliver_location_message(location_message)
begin
if location_message.deliver
render json: { message: "Successfully delivered" }, status: 201
else
render json: { error: "Delivery failure" }, status: 500
end
rescue => e
if e.is_a?(ArgumentError)
render json: { error: "Invalid Recipient" }, status: 422
else
render json: { error: e.message }, status: 500
end
end
end
end
The message itself is sent. Though, deliver_location_message first renders the "Successfully delivered" block and after the last block rendering the error message. This causes an internal server error:
Completed 500 Internal Server Error
AbstractController::DoubleRenderError - Render and/or redirect were
called multiple times in this action. Please note that you may only
call render OR redirect, and at most once per action. Also note that
neither redirect nor render terminate execution of the action, so if
you want to exit an action after redirecting, you need to do something
like "redirect_to(...) and return".
For sending the message the mail_form gem ~> 1.5.0 is used.
The DoubleRenderError seems to happen because create and update both render the JSON response when they finished there work. After, .deliver renders its JSON response to inform about success or failure.
As the error points out you need to return after calling render because you have multiple calls to render in your deliver_location_message(message) method. The reason for the error is because Rails continues execution until the end of the method regardless of render or redirect.
Please try the following. Note the return on each render line.
def deliver_location_message(message)
begin
if message.deliver
# Here
return render json: { message: "Successfully delivered" }, status: 201
else
# Here
return render json: { error: "Delivery failure" }, status: 500
end
rescue => e
if e.is_a?(ArgumentError)
# Here
return render json: { error: "Invalid Recipient" }, status: 422
else
# Here
return render json: { error: e.message }, status: 500
end
end
end
Alternative syntax to:
return render json: { message: "Successfully delivered" }, status: 201
is:
render json: { message: "Successfully delivered" }, status: 201 and return

Resources