How do you handle bad formats in Rails 2 routing? - ruby-on-rails

How does one handle bad formats in routes in Rails 2.3? For instance suppose that you have an action that wants to handle html or json requests but nothing else how do you restrict it while allowing user-readable errors to be promulgated? The following snippet shows a start:
respond_to do |format|
format.html # render the default
format.json { do something appropriate }
format.all ?
end
The trouble is what to put in place of the ?, I tried:
format.all :text => "That's a bad format.", :status => 406
and while the status code got set appropriately the text does not get rendered (at least with a format like com, which is one that I'm receiving.
One possibility would be to change the routes file so that only the two formats were accepted, but that runs into route explosion. (I have 4 acceptable formats.) The idea of using
map.connect '/xyz.:format', :action => ..., :controller => ..., :format => '/html|json/'
sounds good but doesn't work -- it matches something like xyz.comhtml. I'm frustrated and hoping there's something I'm missing.

I might be wrong but I think for your format.all calls you can pass it a file in return.. something like this where you define the return type as well:
format.all { render :file => File.join(Rails.public_path, '406.html'), :status => 406, :content_type => 'text/html' }
And just put a "406.html" file in your public directory with that text "That's a bad format." in it.

Related

Route interprets JSON request as HTML?

Using Ruby 1.8.7 and Rails 3.2.12.
I am experiencing an issue when testing URLs with a ".json" extension. I'm building custom error pages and have the following:
# errors_controller.rb
def show
#exception = env["action_dispatch.exception"]
respond_to do |format|
format.json { render :json => { :error => #exception.message, :status => request.path[1..-1] } }
format.html { render :file => File.join(Rails.root, 'public', request.path[1..-1]), :format => [:html], :status => request.path[1..-1], :layout => false }
end
end
# routes.rb
match ":status" => "errors#show", :constraints => { :status => /\d{3}/ }
# application.rb
config.exceptions_app = self.routes
For URLs such as "localhost:3000/session/nourl.json", I trigger the HTML block of respond_to, and I can verify that the server responds with the HTML format with these logs:
Processing by ErrorsController#show as HTML
Parameters: {"status"=>"404"}
Rendered public/404.html (13.2ms)
Completed 404 Not Found in 48ms (Views: 47.3ms | ActiveRecord: 0.0ms)
The only way I've been able to trigger the JSON block is with :format => :json in the route, then it works fine but "localhost:3000/session/nourl" would respond with JSON too.
It feels like I am doing something foolish here because I've seen other examples of both cases being triggered in the expected way and I see absolutely no other cases of similar behavior, so I'm compelled to think this is an isolated situation or it's some cascading issue that I cannot observe or am causing elsewhere.
If anyone could provide some insight on potential issues I would be appreciative.
Updated:
A little more info: If I query something like "localhost:3000/locations/1.json", I get the expected response; a JSON formatted page with the object details. I can't achieve this same behavior when requesting arbitrary URLs with a ".json" extension and attempting to format a custom JSON response to return. Is there a way to do this?
Rails delegates the call to the Error-Application where all the request-format stuff gets lost. So you will need to check that on your own. You could check on the request information like this:
def api_request?
env['ORIGINAL_FULLPATH'] =~ /^\/api/
end
def json_request?
env['ORIGINAL_FULLPATH'] =~ /\.json$/
end
Read more about this approach here: http://phillipridlen.com/notes/2012/12/13/returning-multiple-formats-with-custom-dynamic-error-pages-in-rails/

Defining a non-resourceful route in rails

I want to setup a non-resourceful route in rails but I dont know how. Rails api says the structure has to be like this. post 'post/:id' => 'posts#create_comment' however, I'm not sure what I should exatly write.
I want it to post to the method "addbank" which is in the bankacctscontroller
I will be on the page localhost:3000/bankaccts/new
def addbank
if (params['customer_uri'])
current_user.customer_uri = (params['customer_uri'])
end
if current_user.save
redirect_to root_url, :notice => "bank account added"
else
render json: {error: "Payment account could not be configured properly"}, status: 401
end
end
There are many formats for defining custom routes. The most elaborate one is:
<METHOD> 'PATH' => 'Controller#Action', :as => path_helper_name (:as is optional)
So for your problem it would be :
post '/bankaccts/:id' => 'bankaccts#addbank'
If you use rails4.0,it will be written like this:
get "/bankaccts/new", to: "bankaccts#new", as: :new_post
I suggest you should learn rails routing first via the website "http://guides.rubyonrails.org/routing.html"

Rails proper way to display error with jbuilder

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.

How to render sitemap.xml in rails app

I have added /views/sitemap/index.xml and want it displayed when i go to the relevant url.
class SitemapController < ApplicationController
def index
respond_to do |format|
format.html
format.xml
end
end
end
And in routes.rb
match "sitemap/" => "sitemap#index"
Using Rails 3
When I go to mydomain.com/sitemap/ I just get a white page. Any ideas?
index.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>http://www.mydomain.com/</loc>
<changefreq>weekly</changefreq>
</url>
</urlset>
Problem is that you are using your index action to render xml and it will render "index.xml"
file not "sitemap.xml" which is what you have created in your views
While your routes are correct, you are using the wrong filename in views
Try renaming sitemap.xml file to index.xml ( in the views/sitemap folder)
If you define name routes, you need to define :format with it
match "/sitemap/sitemap.[:format]", :to => "sitemap#index"
it will pickup your format from there. Also you can define a default format in the routes
match "sitemap/sitemap.xml", :to => "sitemap#index", :defaults => {:format => :xml}
I may be wrong , but I see 2 reasons:
index action doesn't actually do anything judging by this code sample, it just responds back with no info.
you need to render your object as xml - if you don't rails, doesn't know you want xml - it just treats it as another file extension. It actually lets you do little tricks - like sending json to an xml request ( thou I have no idea why would anyone try to do that). Thou one useful application is that you can make rails send custom rendering of an object to a common format or render regular data in common format for an unusual extension ( we had a client who wanted csv data for a .dat request)
Here is a short example, from a sample home controller:
class HomeController < ApplicationController
def index
#m = {
:color => "yellow",
:total => "20"
}
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #m}
end
end
end
this returns this object as xml:
<hash>
<total>20</total>
<color>yellow</color>
</hash>

Rails inherited resources usage

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...

Resources