I'm using Inherited Resources for my Rails 2.3 web service app.
It's a great library which is part of Rails 3.
I'm trying to figure out the best practice for outputting the result.
class Api::ItemsController < InheritedResources::Base
respond_to :xml, :json
def create
#error = nil
#error = not_authorized if !#user
#error = not_enough_data("item") if params[:item].nil?
#item = Item.new(params[:item])
#item.user_id = #user.id
if !#item.save
#error = validation_error(#item.errors)
end
if !#error.nil?
respond_with(#error)
else
respond_with(#swarm)
end
end
end
It works well when the request is successful. However, when there's any error, I get a "Template is missing" error. #error is basically a hash of message and status, e.g. {:message => "Not authorized", :status => 401}. It seems respond_with only calls to_xml or to_json with the particular model the controller is associated with.
What is an elegant way to handle this?
I want to avoid creating a template file for each action and each format (create.xml.erb and create.json.erb in this case)
Basically I want:
/create.json [POST] => {"name": "my name", "id":1} # when successful
/create.json [POST] => {"message" => "Not authorized", "status" => 401} # when not authorized
Thanks in advance.
Few things before we start:
First off. This is Ruby. You know there's an unless command. You can stop doing if !
Also, you don't have to do the double negative of if !*.nil? – Do if *.present?
You do not need to initiate a variable by making it nil. Unless you are setting it in a before_chain, which you would just be overwriting it in future calls anyway.
What you will want to do is use the render :json method. Check the API but it looks something like this:
render :json => { :success => true, :user => #user.to_json(:only => [:name]) }
authorization should be implemented as callback (before_filter), and rest of code should be removed and used as inherited. Only output should be parametrized.Too many custom code here...
Related
Im trying to test if the format send through the request url is json or not?
so in link_to I sent the format like this
<%= link_to "Embed", {:controller=>'api/oembed' ,:action => 'show',:url => catalog_url, format: 'xml'} %>
In the relevant controller I catch the param and raise the exception like this
format_request = params[:format]
if format_request != "json"
raise DRI::Exceptions::NotImplemented
end
but the exception wont display instead the server simply ran into internal error but if I changed the param inside the controller then exception displayed so if the url is like this
<%= link_to "Embed", {:controller=>'api/oembed' ,:action => 'show',:url => catalog_url, format: 'json'} %>
format_request = "xml"
if format_request != "json"
raise DRI::Exceptions::NotImplemented
end
why 501 exception does not triggered if I send the format as xml in url? Im doing it for the testing purpose that in case if someone send the request with wrong format 501 expetion show up
Use ActionController::MimeResponds instead of badly reinventing the wheel:
# or whatever your base controller class is
class ApplicationController < ActionController::API
# MimeResponds is not included in ActionController::API
include ActionController::MimeResponds
# Defining this in your parent class avoids repeating the same error handling code
rescue_from ActionController::UnknownFormat do
raise DRI::Exceptions::NotImplemented # do you really need to add another layer of complexity?
end
end
module Api
class OembedController < ApplicationController
def oembed
respond_to :json
end
end
end
If you don't use respond_to Rails will implicitly assume that the controller responds to all response formats. But if you explicitly list the formats you respond to with a list of symbols (or the more common block syntax) Rails will raise ActionController::UnknownFormat if the request format is not listed. You can rescue exceptions with rescue_from which lets you use inheritance instead of repeating yourself with the same error handling.
As #max mentions, sending the format: 'xml' is unnecessary because Rails already knows the format of the request.
<%= link_to "Embed", {:controller=>'api/oembed' ,:action => 'show',:url => catalog_url } %>
In the controller:
def oembed
respond_to do |format|
format.json { # do the thing you want }
format.any(:xml, :html) { # render your DRI::Exceptions::NotImplemented }
end
end
Or if you want more control you could throw to a custom action:
def oembed
respond_to do |format|
format.json { # do the thing you want }
format.any(:xml, :html) { render not_implemented }
end
end
def not_implemented
# just suggestions here
flash[:notice] = 'No support for non-JSON requests at this time'
redirect_to return_path
# or if you really want to throw an error
raise DRI::Exceptions::NotImplemented
end
If you really want to reinvent the wheel (it's your wheel, reinvent if you want to):
I'd rename format to something else, it's probably reserved and might give you problems
<%= link_to "Embed", {:controller=>'api/oembed' ,:action => 'show',:url => catalog_url, custom_format: 'xml'} %>
Then, in your controller, you need to explicitly allow this parameter:
def oembed
raise DRI::Exceptions::NotImplemented unless format_params[:custom_format] == 'json'
end
private
def format_params
params.permit(:custom_format)
end
I am looking to display an error message in a jbuilder view. For instance, one route I might have might be:
/foos/:id/bars
If :id submitted by the user does not exist or is invalid, I'd like to be able to display the error message accordingly in my index.json.builder file.
Using Rails, what's the best way to get this done? The controller might have something such as:
def index
#bar = Bar.where(:foo_id => params[:id])
end
In this case, params[:id] might be nil, or that object might not exist. I'm not sure whether the best thing to do here is handle it in the controller and explicitly render an error.json.builder, or handle it in the index.json.builder view itself. What's the correct way to do this and if it's in the index.json.builder, is params[:id] available to check there? I know I can see if #bar.nil? but not sure on the inverse?
I would render index.json.builder or just inline json with :error => 'not found'
And don't forget to set proper HTTP status: :status => 404
So result could look like this:
render :json => { :error => 'not found' }, :status => 422 if #bar.nil?
I think you meant show, since index is really for lists/collections. And you should get .first on the where, otherwise you just have a relation, right? Then, use .first! to raise an error, because Rails' Rack middleware in Rails 4 public_exceptions will handle is in a basic fashion, e.g.
def show
# need to do to_s on params value if affected by security issue CVE-2013-1854
#bar = Bar.where(:foo_id => params[:id].to_s).first!
end
You can also use #bar = Bar.find(params[:id]), but that is deprecated and will be removed in Rails 4.1, after which you would have to add gem 'activerecord-deprecated_finders' to your Gemfile to use.
For index, you'd probably want #bars = Bar.all. If for some reason you want to filter and don't want to scope, etc., then you could use #bars = Bar.where(...).to_a or similar.
Rails 4: Basic Exception Handling in Rack Is Automatic
As long as the query kicks off an error, Rails 4 should be able to return the message part of the error for any supported format where to_(format) can be called on a hash (e.g. json, xml, etc.).
To see why, take a look at Rails' Rack public_exceptions middleware.
If it is html, it is going to try to read in the related file from the public directory in Rails for the status code (e.g. 500.html for a server error/HTTP 500).
If it is some other format, it will try to do to_(the format) on the hash: { :status => status, :error => exception.message }. To see how this would work go to Rails' console:
$ rails c
...
1.9.3p392 :001 > {status: 500, error: "herro shraggy!"}.to_xml
=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<hash>\n <status type=\"integer\">500</status>\n <error>herro shraggy!</error>\n</hash>\n"
1.9.3p392 :002 > {status: 500, error: "herro shraggy!"}.to_json
=> "{\"status\":500,\"error\":\"herro shraggy!\"}"
In the middleware, you'll see the X-Cascade header in the code and in various places related to Rails' exception handling in Rack. Per this answer, the X-Cascade header is set to pass to tell Rack to try other routes to find a resource.
Rails 3.2.x: Can Handle Exceptions in Rack
In Rails 3.2.x, that code to do to_(format) for the response body, etc. is not in public_exceptions.rb. It only handles html format.
Perhaps you could try replacing the old middleware with the newer version via a patch.
If you'd rather have Rack handle your error in a more specific way without a patch, see #3 in José Valim's post, "My five favorite “hidden” features in Rails 3.2".
In that and as another answer also mentions, you can use config.exceptions_app = self.routes. Then with routes that point to a custom controller, you can handle the errors from any controller like any other request. Note the bit about config.consider_all_requests_local = false in your config/environments/development.rb.
You don't have to use routes to use exceptions_app. Although it may be a little intimidating, it is just a proc/lambda that takes a hash and returns an array whose format is: [http_status_code_number, {headers hash...}, ['the response body']]. For example, you should be able to do this in your Rails 3.2.x config to make it handle errors like Rails 4.0 (this is the latest public_exceptions middleware collapsed):
config.exceptions_app = lambda do |env|
exception = env["action_dispatch.exception"]
status = env["PATH_INFO"][1..-1]
request = ActionDispatch::Request.new(env)
content_type = request.formats.first
body = { :status => status, :error => exception.message }
format = content_type && "to_#{content_type.to_sym}"
if format && body.respond_to?(format)
formatted_body = body.public_send(format)
[status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
'Content-Length' => body.bytesize.to_s}, [formatted_body]]
else
found = false
path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
if found || File.exist?(path)
[status, {'Content-Type' => "text/html; charset=#{ActionDispatch::Response.default_charset}",
'Content-Length' => body.bytesize.to_s}, [File.read(path)]]
else
[404, { "X-Cascade" => "pass" }, []]
end
end
end
Note: For any problem with that handling, the failsafe implementation is in ActionDispatch::ShowExceptions here.
Rails 3 and 4: Handling Some Exceptions in Rails Controller
If you'd rather have error rendering in the controller itself, you can do:
def show
respond_with #bar = Bar.where(:foo_id => params[:id].to_s).first!
rescue ActiveRecord::RecordNotFound => e
respond_to do |format|
format.json => { :error => e.message }, :status => 404
end
end
But, you don't need to raise errors. You could also do:
def show
#bar = Bar.where(:foo_id => params[:id].to_s).first
if #bar
respond_with #bar
else
respond_to do |format|
format.json => { :error => "Couldn't find Bar with id=#{params[:id]}" }, :status => 404
end
end
end
You can also use rescue_from, e.g. in your controller, or ApplicationController, etc.:
rescue_from ActiveRecord::RecordNotFound, with: :not_found
def not_found(exception)
respond_to do |format|
format.json => { :error => e.message }, :status => 404
end
end
or:
rescue_from ActiveRecord::RecordNotFound do |exception|
respond_to do |format|
format.json => { :error => e.message }, :status => 404
end
end
Though some common errors can be handled in the controller, if you errors related to missing routes, etc. formatted in json, etc., those need to be handled in Rack middleware.
I am building a small application in RoR that has a form asking for a URL. Once the URL has been filled in and submit button is pressed I have downloaded a web-scraping plugin scrAPI(which is working fine) which gets the of URL and creates a record in db with title.
My issue right now is that I am able to make the whole thing work if the URL is valid and scrAPI is able to process it. If a URL entered does not work it gives this "Scraper::Reader::HTTPInvalidURLError" which is expected, but my knowledge of working in Model is preventing me from handing that error in a correct manner.
Controller:
#controller
class ArticleController < ApplicationController
def savearticle
#newarticle = params[:newarticle]
#link = #newarticle["link"]
#id = #newarticle["id"]
Article.getlink(#link)
success = Article.find(:last).update_attributes( params[:newarticle] )
if success
render :partial => 'home/articlesuccess'
else
render :partial => 'home/articlebad'
end
end
end
# model
require 'scrapi'
class Article < ActiveRecord::Base
attr_accessor :getlink
def self.getlink(link)
scraper = Scraper.define do
process "title", :title => :text
result :title
end
uri = URI.parse(link)
Article.create(:title => scraper.scrape(uri))
end
end
How to:
1) Handle the Scraper::Reader::HTTPInvalidURLError properly, so text could be returned to view with proper error.
2) I would also like to know how I can return 'uri' from model and use it in the controller or view.
3) Also, I would like to return the ID of the Article created in Model so I can use that in the controller instead of doing find(:last) which seems like bad practice.
Something like...
class ApplicationController < ActionController::Base
rescue_from 'Scraper::Reader::HTTPInvalidURLError', :with => :invalid_scrape_url
private
def invalid_scrape_url
flash[:error] = 'The URL for scraping is invalid.'
render :template => 'pages/invalid_scrape_url'
end
end
rescue_from is what you need.
That's 1)
for 2) You could just use #uri but personally I'd create a new model called Scrape and then you can retrieve each Scrape that is attempted.
for 3) I'm not quite sure of the question but
#article = Article.create(:title => scraper.scrape(uri))
then
#article.id
Hope that helps!
(1) In Ruby, you can handle any exception as follows:
begin
# Code that may throw an exception
rescue Scraper::Reader::HTTPInvalidURLError
# Code to execute if Scraper::Reader::HTTPInvalidURLError is raised
rescue
# Code to execute if any other exception is raised
end
So you could check for this in your controller as follows:
begin
Article.getlink(#link)
# all your other code
rescue Scraper::Reader::HTTPInvalidURLError
render :text => "Invalid URI, says scrAPI"
rescue
render :text => "Something else horrible happened!"
end
You'll need to require 'scrapi' in your controller to have access Scraper::Reader::HTTPInvalidURLError constant.
I would probably make the creation of the new Article and the call to scrAPI's method separate:
title = scraper.scrape(uri)
Article.create(:title => title)
(2) and (3) In Ruby, the last statement of a method is always the return value of that method. So, in your self.getlink method, the return value is the newly created Article object. You could get the ID like this in your controller:
article = Article.getlink(#link)
article_id = article.id
You may need to refactor the code a bit to get the results you want (and make the code sample on the whole cleaner).
Oddly enough, my model passes validation just fine, and acts as expected, however I still have the error rendered to the view.
# Controller
def up
#vote = Vote.create :vote => true, :voter => current_user, :voteable => Recipe.find(params[:id])
respond_to do |format|
format.js { render :json => {:model => 'vote', :success => #vote.valid?, :errors => #vote.errors }}
end
#vote.errors.clear # <= doesn't seem to help
end
The model I wrote has a custom validation:
class Vote < ActiveRecord::Base
# ... associations etc.
validate :voter_voting_too_frequently?
private
def voter_voting_too_frequently?
last_vote_cast_by_voter = Vote.find_last_by_voter_id self.voter
unless last_vote_cast_by_voter.nil? || last_vote_cast_by_voter.created_at < 5.seconds.ago
errors.add_to_base("You can only vote every 5 seconds.")
end
end
end
And lastly, the response that is rendered to the view: (returned as js no doubt, but would be the same if it were in a <div>)
{"errors":[["base","You can only vote every 5 seconds."]],"model":"vote","success":false}
And even though it was successful, this is continuously returned.
Ideas on how to debug this?
The issue was totally bizarre, and was related to render => :json. Strangely the :success key couldn’t be first in the hash, when the respond_to block renders json.
This was the only change that needed to be changed:
format.js { render :json => {:model => 'vote', :errors => #vote.errors.on_base, :success => #vote.errors.on_base.nil?} }
Also I'm logging a ticket with the rails team.
It doesn't look like it is passing the validation since the success status is false.
If you take a look at you custom validation it seems that the "unless" keyword has caused some confusion.
Try:
if !last_vote_cast_by_voter.nil? && last_vote_cast_by_voter.created_at < 5.seconds.ago
errors.add_to_base ...
I think your unless statement is wrong. Personally I am not used to them, so I always rewrite to if statements
if last_vote_cast_by_voter.nil? == false && last_vote_cast_by_voter.created_at > 5.seconds.ago
Looking at this line, I would suspect that last_vote_cast_by_voter.created_at should be lower than 5 seconds, therefore I suppose your unless statement should be changed into
unless last_vote_cast_by_voter.nil? || last_vote_cast_by_voter.created_at > 5.seconds.ago
Adding #vote.errors.clear indeed does not help, since the view is rendered at the point already...
If this still is not working try to write a test where you cast some votes. The time between two votes should be 1 second and 10 seconds for example.
Check if Vote.find_last_by_voter_id is working properly.
If all these test are working and rendering your view is not, then something strange is going on in your view and you should post some more information about your view, I guess.
When writing the "create" method for an object in a Ruby on Rails app, I have used two methods. I would like to use one method for the sake of cleaner and more consistent code. I will list the two methods below. Does anyone know if one is better than the other? If so, why?
Method 1:
def create1
# is this unsecure? should we grab user_id from the session
params[:venue]['user_id'] = params[:user_id]
begin
venue = Venue.create(params[:venue])
#user_venues = #user.venues
render :partial => 'venue_select_box', :success => true, :status => :ok
rescue ActiveRecord::RecordInvalid
render :text => 'Put errors in here', :success => false, :status => :unprocessable_entity
end
end
Method 2:
def create2
# is this unsecure? should we grab user_id from the session
params[:venue]['user_id'] = params[:user_id]
venue = Venue.new(params[:venue])
if venue.save
#user_venues = #user.venues
render :partial => 'venue_select_box', :success => true, :status => :ok
else
render :text => 'Put errors in here', :success => false, :status => :unprocessable_entity
end
end
class VenuesController < ApplicationController
def create
#venue = #user.venues.create!(params[:venue])
render :partial => 'venue_select_box', :success => true, :status => :ok
end
rescue_from ActiveRecord::RecordInvalid do
render :text => 'Put errors in here', :success => false, :status => :unprocessable_entity
end
end
Using #user.venues in this way ensure that the user ID will always be set appropriately. In addition, ActiveRecord will protect the :user_id field from assignment during the course of the #create! call. Hence, attacks from the outside will not be able to modify :user_id.
In your tests, you may verify that doing a POST to :create raises an ActiveRecord::RecordInvalid exception.
I'm of the school of thought that exceptions shouldn't be used for routine conditions, so I'd say the second is better.
It depends. If you expect all of the create statements to work, use the former, because the failure to create-and-save is exceptional, and may possibly be a condition from which the program can't readily recover. Also, if you use relational integrity (foreign_key_migrations by RedHill Consulting), this will throw exceptions on foreign key violations, so you probably want to catch them whenever creating or updating.
The second is workable, and good if the query not succeeding is something you expect as part of the day-to-day operation of that particular action.
Also, your code comment about session being insecure -- the session is the place to put the user_id. As long as you're checking to verify that the user has been authenticated before doing anything else, you'll be okay.
I totally agree with Don's comment. But I would even go one step further with the user_id part and set it as a before filter on the model.