activeresource error status and response body - ruby-on-rails

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

Related

Error handing and flow within private methods on controller

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

self.errors[:base] << inside an ActiveRecord transaction block

I want to update #some_instance unless the user does not meet some criteria. I put the criteria check and the #some_instance.save! in a transaction block so that if either of them fail, no transaction is made. This works well, but I am having trouble returning the correct error message. If the user does not meet the criteria, I want to return the reason why, OR if the #some_instance doesn't save, I want to return that error.
My code:
#some_controller.rb
begin
Some_Class.transaction do
return render json: { error: #user.errors }, status: :payment_required,
location: new_payment_path unless #user.meets_criteria
#some_instance.save!
end
rescue ActiveRecord::RecordInvalid => exception
render :json => { :error => exception.messages }, status: :unprocessable_entity
rescue => error
# handle some other exception
end
#User.rb
def meets_criteria
self.errors[:base] << "Does not meet criteria"
return false
end
The problem I'm facing is this: When the meets_criteria method returns false, I expect the return render json line to execute. Instead it catches an error in "rescue => error".
The return render json is never executed.
UPDATE:
#Gen suggested using a before_action instead of calling the meets_criteria in the transaction do block. I think this is a much better implementation, however I'm still curious why the return render is never called. Is it because ActiveRecord raises an error? If so shouldn't that be caught in the RecordInvalid exception?
#TheJKFever, I am not sure that your code supposed to jump to any rescue block (well, and I actually confirmed by running it).
Your some_validation method returns false, and therefore "unless #user.some_validation" evaluates to true, and the render is executed with the following log output:
Completed 402 Payment Required in 128ms
{"error":{"base":["Some error message"]}}
You can refer to ActiveRecord::RecordInvalid API for details about RecordInvalid. Namely, "Raised by save! and create! when the record is invalid".
So, your "rescue ActiveRecord::RecordInvalid => exception" is supposed to handle exceptions in the "#some_instance.save!" statement and not in your custom validation.
In your validation you don't actually have the code that raises the ActiveRecord::RecordInvalid exception and probably fails with another error, which is easy to check by outputing it in details.
In order to use some_validation with "self.errors[:base] <<" properly first your need to add the following statement to your user model:
validate :some_validation
In this case, if you call "#user.save!" instead of "#some_instance.save!", you would fall into that "rescue ActiveRecord::RecordInvalid => exception" block.
PS: #TheJKFever, I saw one of your comments below and wanted to clarify something. A validation has a well defined purpose to validate a model before saving, and what you need then is not a model validation. What you actually need is a before_action on your controller that will check that your user is ready to be used in such and such action (consider it as controller validation). And yes, you probably will need some method on your user model to do that check.
Updated (after question update)
#TheJKFever, as I mentioned earlier, when I implemented your code I was able to execute "return render json: { error: #user.errors }...". So, if it fails for you, it must be due to some exception during meets_criteria call, but it is not RecordInvalid exception. Since you wrapped meets_criteria into transaction, it means that it probably does some database changes that you want to rollback if #some_instance.save! was unsuccessful. It would be a good idea to wrap your meets_criteria with the same rescue blocks too and find out. Do you create! or save! something in meets_criteria? If so, then it also can throw RecordInvalid exception. The problem is that in your code RecordInvalid can be thrown by meets_criteria and by #some_instance.save!, and there is no way to see in the code which one. In any case, if you wrap your meets_criteria with rescue, you will be able to send your render errors request from it. And if you decide to go with before_action filter then you will have to move whole your transaction into it (assuming that it requires the integrity of the data).
The point is that ActiveRecord::RecordInvalid exception will only be thrown in case of save! or create! failure due to invalid data. It might not be the case in your code, and some other exception is thrown, and you end up in "rescue => error" block.
You are adding error to #user model and handling exception raised on #some_instance. Try #user.errors.messages[:base]
Custom validations aren't usually invoked via a public instance method. They're usually written as a private method and invoked by ActiveRecord during valid? if you register them in your model with, e.g. validate :some_validation.
In your case, the User model would need the following:
validate :some_validation
…
private
def some_validation
errors.add :base, "Some error message" if some_error_happens?
end
Then, in the controller, you would do:
Some_Class.transaction do
return render json: { error: #user.errors }, status: :payment_required,
location: new_payment_path if #user.valid?
#some_instance.save!
end

Render Rails responses as JSON... but keep Rails default HTTP response codes

When Rails responds to an HTTP request, it's HTTP response will always be correctly headed up with an appropriate HTTP response code. This can be for successful operations (a 2xx code), such as the creation of a new ActiveRecord in the database, or for errors (a 4xx). In the latter case a rendered HTML page is supplied containing information about the error (a backtrace, etc).
My app requires that all Rails HTTP responses take the form of JSON, so I am writing my own code to render these HTTP responses accordingly. A number of tutorials talk about writing something like this to render such responses (in this example, located in user_account_controller.rb where UserAccount is the name of an ActiveRecord model):
# method to create a UserAccount object based on supplied user_account_params
def create
#user_account = UserAccount.create!(user_account_params)
if #user_account
render :status => 201, :json => {
'message' : 'Operation was successful!"
}
end
end
And, if an exception is thrown, it is possible to bind a customer exception handler as follows:
# In ApplicationController
rescue_from Exception, :with => :json_exception_handler
def json_exception_handler(exception)
render :status => 422, :json => {
:exception => {
:backtrace => exception.backtrace,
:message => exception.message
}
}
end
The problem with both of these solutions is that they require me to statically set the HTTP response codes to have the same value every time (201 and 422 in these examples). Rails already does a great job of automatically picking the best response code to use, depending on the type of error or type of successful operation. I don't want to have to reinvent the wheel badly by inventing my own response code structure for each error and operation in my app.
So my question is this: how do I supply the response code that Rails will have automatically chosen anyway, whilst retaining the ability to print out custom JSON?

There is a better way to validate in Rails?

The example below is how I'm authenticating my users today:
def create
if is_internal_request(params[:authenticity_token])
#user = User.authenticate(params[:email], params[:password])
if #user
session[:user_id] = #user.id
render :json => true
else
render :json =>
{
error:
{
code: 1,
message: t('globals.errors.authentication.user-not-found')
}
}.to_json
end
end
end
Pay attention to this fragment:
render :json =>
{
error:
{
code: 1,
message: t('globals.errors.authentication.user-not-found')
}
}.to_json
Based on it, I want to know if it's organized and solid. I mean, is that something in the edge of the right way?
Forward thinking
Lets suppose that there are some places in my application that check about user's e-mail availability. I want to be DRY and reuse that verification every time I need to use it. If the way that I'm doing the validation (as below) isn't "perfect" at all, how can I create a validation layer that can be useful every time I want?
I mean, instead of create and recreate that fragment of code each time that I want to validate, what's the best way to do something like this?:
email = params[:email]
unless email_is_available(email)
render :json => { message: 'E-mail isn't available' }
end
With "what's the best way to do something like this?" I'm saying, where I have to place email_is_available function to get it working right?
Every Controller can access ApplicationController methods as they inherit from it.
Anyways, I'd recommend using a gem like Devise for a more complete solution.
GL & HF.
What about something like this?
if #user.valid?
render :json => true
else
error_hash = {}
#user.errors.each{|k,v| error_hash[k] = "#{k.capitalize} #{v}"}
#this will give you a hash like {"email" => "Email is not available"}
render :json => {:errors => error_hash}
end
At the client end, you will get this back (eg as an object called data), see if there is a value for data.errors and then display the various error messages to the user.
Note i haven't plugged in any translation stuff but you can do that :)
I'd suggest you take a look at Ryan Bates' Railscast on authentication from scratch. It walks you through all the issues and is much lighter weight than relying on something as big and heavy as Devise.
http://railscasts.com/episodes/250-authentication-from-scratch

Delaying render_to with Resque

I am trying to replicate the setup Ryan Bates has in this railscast on Resque, where he queues up a third party service web request and then updates his results page with results.
I am designing an application that will interact with another Rails app, not a browser, and would like to replicate analogous behavior, with key difference being that only JSON output is expected
Currently I have something like this: (my models are Lists and Tasks, a List has_many Tasks and a Task belongs_to a List.
My lists_controller.rb
def show
Resque.enqueue(TaskDataFetcher,params[:id])
# confused if I need to have a render_to below this.
end
In task_data_fetcher.rb
require "net/http"
require "uri"
class TaskDataFetcher
#queue = :tasks_queue
def self.perform(id)
list = List.new(:id => id)
url = "taskservice.com/" + id + ".json"
uri = URI.parse(url)
response = Net::HTTP.get_response(uri)
task = Task.new(:contents => response.body)
task.list = list
# how to return this to the requesting server????
end
end
In the Railscast you see that result doesn't automatically update after the Resque task finishes, he has to reload the page several times, re-making the show request. So if you want to replicate this behaviour you could do something like:
def show
list = List.find(params[:id])
if list
respond_to do |format|
format.json {render :json => list.to_json}
end
else
Resque.enqueue(TaskDataFetcher, params[:id])
render :nothing => true, :status => 202
end
end
Requerement:
So your user is requesting your service to see some tasks. And you have to fetch those from another service taskservice.com. Then i think you have to do this through database for persistency.
Suggestion:
You can have a model like TaskRequest having attributes
`id` # must ;)
`task_list` # might be xml or whatever format suits you best
`is_received` # boolean
In your show method,
You create a TaskRequest entry and render a view which will show a loading type thing and will be requesting for task via ajax. The ajax response should return the task list and the is_received. However, once you get is_received true with a content you should request again.
In parallel, your TaskDataFetcher should receive two ids. One that you are sending now and another is of TaskRequest id. So after fetching the data from the service it will store that in the TaskRequest table and will update the is_recieve to true. Setting it true will eventually turn off requesting for this data anymore.
well the whole explanation might seem a bit hazy. Just let me know if you didnt any part or you need anything else specifically.
Note: It is something like the way SO shows the code formatting while answering a question ;)

Resources