Error handing and flow within private methods on controller - ruby-on-rails

Reviewing a coworker's PR I came across a pattern I have not seen before, where a private method was called and or return appended to the end if that method failed. I found a blog post mentioning this (number 2) but it feels strange to me.
The code sort of looks like this:
class OurController < ApplicationController
def index
amount = BigDecimal.new(params[:amount]).to_i
if amount < 0
cancel_processing(amount) or return
else
process(amount)
end
render json: {success: true}
end
private
def cancel_processing(amount)
response = CancelProcessingService.call(amount)
if response
log_stuff
else
render json: {error: true} and return
end
end
end
Since the render error is being called from within the method, it's not ending, and it's therefore going to the end of the index action and double rendering (without the or render after cancel_processing).
This feels like a smell to me. renders and returns are respected within before_filters, so them not being respected in methods feels inconsistent. Maybe it just feels wrong because I haven't encountered this or return pattern before, but I ask: is there a way to get Rails to respect render... and returns from within methods (which are not before_filters or actions)?
I feel like advocating for rewriting these methods to simply return JSON, and pass the response to render later on in the method -- but if this is a normal pattern then I have no ground to suggest that.
What are your thoughts?

render ... and/or return is a bogus pattern. Rails documentation uses it in a couple of places but I believe it should not. It's bogus because although render returns a truthy value (the rendered response body) when it succeeds, when it fails it does not return a falsy value but raises an error. So there is no point in handling the nonexistent case when it returns a falsy value. (The same applies to redirect_to.)
In an action, to avoid misleading anyone about how render works, just do
render # options
return
to render and then exit.
In a private method called by an action, you can't say return and exit from the calling action, because Ruby methods don't work that way. Instead, make the method return a value that the action can interpret and return early if appropriate. Something like this:
def index
amount = BigDecimal.new(params[:amount]).to_i
if amount < 0
if !cancel_processing(amount)
return
end
else
process(amount)
end
render json: {success: true}
end
def cancel_processing(amount)
response = CancelProcessingService.call(amount)
if response
log_stuff
else
render json: {error: true}
end
response
end

In a controller you can call performed? to find out if a render/redirect has already been called. For example:
performed? # => false
render json: {error: true}
performed? # => true

Related

Rendering ActiveRecord validation errors with attributes AND full error messages

Here's a simple controller update action:
def update
note = Note.find(params[:id])
if note.update(note_params)
render json: note.to_json
else
render json: {errors: note.errors}, status: :unprocessable_entity
end
end
This renders errors in the form
{"errors":{"title":["can't be blank"]}}
but I want it in the form of
{"errors":{"title":["Title can't be blank"]}}
Simply using {errors: note.errors.full_messages}
gives
{:errors=>["Title can't be blank"]} and misses the attribute keys.
The only way I can get it into the desired form seems to be a bit more involved:
full_messages_with_keys = note.errors.keys.inject({}) do |hash, key|
hash[key] = note.errors.full_messages_for(key)
hash
end
render json: {errors: full_messages_with_keys}, status: :unprocessable_entity
This works, but it seems odd that I have to do this since it seems to be a pretty common use case for doing validations on a SPA front-end. Is there a built-in method/more canonical way?
You can use ActiveModel::Errors#group_by_attribute to get a hash of errors per key:
person.errors.group_by_attribute
# => {:name=>[<#ActiveModel::Error>, <#ActiveModel::Error>]}
From there is simply a matter of generating the full message from each ActiveModel::Error instance:
note.errors
.group_by_attribute
.transform_values { |errors| errors.map(&:full_message) }
Is there a built-in method/more canonical way?
Not really. A framework can't cover every possible need. It provides the tools needed to format the errors however you want.
However instead of repeating this all across your controllers this functionality can be pushed into the model layer, a serializer or even monkeypatched onto ActiveModel::Errors if you want to get crazy.
class ApplicationRecord < ActiveRecord::Base
def grouped_errors
errors.group_by_attribute
.transform_values { |errors| errors.map(&:full_message) }
end
end

Rails4 jbuilder always return status code 200, even if I put other status code

I built a json view to return json in one of ajax call in rails4 app. I have used the idea suggested here
https://stackoverflow.com/a/12832116/1560470
But I always keep getting status code as 200, even if I enforce other status code.
My jbuilder view in view/managers/create.json.jbuilder looks as follows:
if #manager.errors.messages.any?
envelope(json, :unprocessable_entity, #manager.errors.messages) do
json.success false
end
else
envelope(json, :created) do
json.success true
end
end
My application helper lloks as follows:
module ApplicationHelper
def envelope json, status, errors
json.status status
json.data do
yield if block_given?
end
json.errors errors
end
end
My controller is as follows:
def create
#manager = Manager.new manager_params
#saved = ( #manager.valid? && #manager.save )
end
You can see even I am passing status params value as :unprocessable_entity in my jbuilder view, still response comes back as 200 every time. Even I use any status code, it always return 200. Status codes are defined at http://guides.rubyonrails.org/layouts_and_rendering.html
I had the same issue, I found success with a call to render followed by whatever status you want to issue. At the bottom of create put the following
render status: 400
Reference: https://stackoverflow.com/a/28144206/3826642
You can move the logic from that envelope method to the jbuilder template so you're passing status directly from controller to the view.
That status you have in the envelope method will only be in the json that is rendered, it's not a http response status code sent by the server whereas as the one in the render method is

Define timer method controller rails

I have a method in controller that calls another method created in a module like this example:
def example
#var1 = ModuleName::ClassName.get()
respond_to do |format|
format.json { render json: #var1}
end
end
The method get() goes to a website looking for information and returns an array.
Everything works perfectly, but I wonder if in the controller there is a way to set a timeout if the application takes a long time to run! Is it possible?
here is one way (a more general way) you could do that..
def example
Timeout::timeout(40) do # 40 sec, change it to anything you like
#var1 = ModuleName::ClassName.get()
rescue Timeout::error
# do something (maybe set #var1's value if it couldn't get desired array)
end
respond_to do |format|
format.json { render json: #var1}
end
end
If under ModuleName::ClassName.get() you imply some kind of third-party ruby http library, then it's likely that you should set some kind of timeout parameter (depends on library). You just pass a desired timeout in seconds (or whatever measurement you want).
Thus the pseudo-code might look like this:
ModuleName::ClassName.get(10)
For more detailed answer, can you please be more specific about how are you doing a call to external service?

rails/ruby "and return false" syntax

I've seen this code in a rails tutorial I'm doing
def access_denied
redirect_to login_path, :notice => "Please log in to continue" and return false
end
Before learning rails, I did a large amount of ruby research and none of the books I read covered this "and return false" syntax going on here. I can't find any mention of it in the rails syntax, is anyone able to provide a link or any explanation that would clear this up?
I don't understand the need for the "and" in here as I thought ruby would always return the last evaluated expression.
The and is there only for you to be able to write the return false at the same line as the previous statement.
It's equivalent of doing this:
redirect_to login_path, :notice => "Please log in to continue"
return false
it's not exactly the same because with the "and" it would only return false if the redirect_to method returns something that isn't nil or false but as it almost always returns something that is not nil nor false then the right side of the statement is always executed (and false is returned).
The and in Ruby is just an alias to && and behaves exactly like it with only one difference, it has a lower precedence than all other pieces of the statement, so first it evaluates whatever is on it's left and then gets applied. Ruby also has the or which is the same as || but with a lower precedence in expressions.
redirect_to login_path, :notice => "Please log in to continue" is a normal expression in Ruby that does not return false (or nil, see the source code), so program execution continues (to check the second part after AND) and return false can be executed.
This kind of construct used to be used with a filter, when filter return values were expected. The false would halt normal request processing.
So if a controller looked like this:
class FooController < ActionController::Base
before_filter :access_denied
# etc.
end
None of the controller's actions would run; this method was likely designed to be called from a filter, like:
def check_access
return access_denied unless current_user.has_access
end
These days filter return values are ignored.
It could be used as a normal method in an action to stop processing the action and return after the redirect, so no other logic/rendering/etc. is run inside the controller.
Documentation on operators: Under the "Defined?, And, Or, and Not" section
Using and is useful for chaining related operations together until one of them returns nil or false. You can use it like you showed in your question or you can also use it to short-circuit the line of code.
# Short circuit example
blog = Blog.find_by_id(id) and blog.publish!
The blog only gets .publish! called on it if the find_by_id was successful.
In your example:
def access_denied
redirect_to login_path, :notice => "Please log in to continue" and return false
end
They are just using it to write the code more compactly on one line.
However, it is also useful for a controller action where you may be rendering based on conditionals and you don't want to get Rails "multiple renders" warning:
def show
if something
render :partial => 'some_partial'
end
render :partial => 'some_other_partial'
end
Rails will give you a warning if something returns true since you have multiple render statements being evaluated in the action. However, if you changed it to
if something
render :partial => 'some_partial' and return
end
it would allow you to stop the action execution.

activeresource error status and response body

I am making an activeresource call to a service, and I'd like some custom error messages as feedback. I have some validations that aren't normal model validations, so I can't just return #object.errors.
So, for instance, one of my validations is this. Two objects have a many to many relationship, but I want to restrict one object to only have a limited number (say 2) of relationships to other objects. Here's some code:
In the client:
response = Customer.find(customer_id).put(:add_user, :user_id => user_id)
This puts a request to add a user to the customer. Then in the service I want to check that this addition is valid.
def add_user
#user = User.find(params[:user_id])
#customer = Customer.find(params[:id])
if #customer.users.length > 2
render :xml => "ERR_only_2_users_allowed", :status => :unprocessable_entity
end
end
Here's my problem. In active resource, if the return status is an error, the client side completely fails. I could change the status to 200 and I get back the body err msg fine, but this seems to defeat the purpose of having error reponse codes.
I can put the whole request call from the client in a begin/rescue block
begin
response = Customer.find(customer_id).put(:add_user, :user_id => user_id)
rescue ActiveResource::ResourceInvalid => e
#return error code
end
but when I catch the 422 (unprocessable_entity) response, I get nothing of the body back, so I don't get my custom error message. response = nil
Does anyone know how I can achieve these custom error message with the proper response codes?
This may or may not be your problem, but both of ours seem very close. I'm using a custom put method, but his should work for you too. What's going on is that the code that does this:
rescue ResourceInvalid => error
errors.from_xml(error.response.body)
end
Is only working with the standard save method. If you want errors added when other methods are called it looks like you need to do it yourself.
I had to add it to
vendor/rails/activeresource/lib/active_resource/custom_methods.rb
Here is what my diff from git looks like:
old code:
def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end
new code:
def put(method_name, options = {}, body = '')
begin
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
rescue ResourceInvalid => error
errors.from_xml(error.response.body)
end
self
end
So look at the stack trace when get the exception thrown for the 422 and see which method it's calling exactly. Then add something like what I have and you should be good to go.
Don't ask me why the activeresource folks thought validations should only work with their save method. the save method does a create or update, but calling 'put or post' is the exact same thing, IMO. If we want validations to work on save we want them to work on put and post...anyway give it a shot.
I'm not sure if i need the self at the end...i may not. I'm not totally done with this as I just figured out how to make it work.
Erik
I think that your problem might be the response isn't an xml document but just a plain string. Try changing your render statement to something like:
render :xml => { :error => "ERR_only_2_users_allowed" }, :status => :unprocessable_entity

Resources