Exception for incorrect format in rails - ruby-on-rails

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

Related

Rails 7 rescue API error and stil show the view

In my Rails 7 app I'm using several 3rd parties API to provide fetch data. Each time I'm receiving an error I've to rescue with nil to still be able to display redirect user to desired page, e.g.:
# lib/custom_api.rb
module CustomApi
extend self
def fetch_transactions(user_id)
client.transactions.list(user_id:)
# rescue from custom error
rescue Errors::NotFoundError
nil
end
end
# transactions_controller.rb
class TransactionsController < ApplicationController
def index
transaction_list = CustomApi.fetch_transactions(current_user.id)
if transaction_list
#transactions = transaction_list
else
#transactions = transaction_list
flash[:alert] = 'No transactions'
end
end
end
# views/transactions/index.html.erb
<%= turbo_frame_tag 'transactions' do %>
<%= render partial: 'table_headers' %>
<%= render Transactions::TableComponent.new(records: #transactions) if #transactions %>
<% end %>
Everything works well but I've got 50 endpoints where I need to include rescue Errors::NotFoundError and I don't think it's super sufficient to to repeat this line 50 times. Is there a way to avoid that?
In general, using Rescuable is the Rails' way for rescuing from exception in a centralized manner.
Add this to your ApplicationController:
rescue_from Errors::NotFoundError, with: :handle_not_found_error_from_external_api
private
def handle_not_found_error_from_external_api
# handle the error in a generalized way, for example, by returning a response
# that renders a modal or a toast.
end
And remove these lines from your CustomApi:
# rescue from custom error
rescue Errors::NotFoundError
nil

UrlGenerationError: No route matches, missing required keys and link_to

I'm having issues with my static pages strategy on rails.
I have a controller that handles static pages. Basically, it reads templates in the app/views/static directory and render as asked. Like so:
class StaticController < ApplicationController
def show
templ = File.join(params[:controller], params[:page])
puts params, templ
render templ
rescue ActionView::MissingTemplate => e
if e.message =~ %r{Missing template #{templ}}
raise ActionController::RoutingError, 'Not Found'
else
raise e
end
end
end
These are my routes:
Rails.application.routes.draw do
root 'static#show', page: 'home'
get ':page', to: 'static#show', as: :static, constraints: { page: /a-zA-Z\-_\/+/ }
end
The root route works fine, I am able to access the view just fine. I get no errors.
Now, on my header partial, I have this, edited for simplicity/relevance:
<%= link_to('Home', static_path(:home)) %>
There is no other ruby code in the partial or the main template. I'm not sure what I'm doing wrong. The error just does NOT make sense.
ActionController::UrlGenerationError - No route matches {:action=>"show", :controller=>"static", :format=>nil, :page=>:home} missing required keys: [:page]
Where exactly is the required key missing from? I don't have any objects other or models.
Now this works just fine:
<%= link_to('Home', controller: 'static', action: 'show', page: 'home') %>
So how do I make static_path work like that?
Thanks.
I think there's a problem with the regexp your using for the constraint, I'd suggest dropping it from the route definition, as you already check if template is present in the controller.
Also, you should check out high_voltage gem which handles creating static pages quite nicely.
My first guess would be:
<%= link_to 'Home', static_path("home") %> #-> try passing a string, rather than symbol
This should allow you to reference the required path
--
class StaticController < ApplicationController
def show
templ = File.join(params[:controller], params[:page])
puts params, templ
render templ
rescue ActionView::MissingTemplate => e
if e.message =~ %r{Missing template #{templ}}
raise ActionController::RoutingError, 'Not Found'
else
raise e
end
end
end
I would personally make this a lot simpler:
class StaticController < ApplicationController
def show
render "pages#{params[:page]}
end
end

Rails 4 - How to render JSON regardless of requested format?

I'd like a Rails controller (all of them, actually, it's an API) to render JSON always always.
I don't want Rails to return "route not found", or try and fail to find an HTML template, or return 406. I just want it to automatically and always render JSON, e.g. from a RABL or JBuilder view.
Is this possible? Related questions seem to have answers that have the aforementioned downsides.
You can add a before_filter in your controller to set the request format to json:
# app/controllers/foos_controller.rb
before_action :set_default_response_format
protected
def set_default_response_format
request.format = :json
end
This will set all response format to json. If you want to allow other formats, you could check for the presence of format parameter when setting request.format, for e.g:
def set_default_response_format
request.format = :json unless params[:format]
end
You can use format.any:
def action
respond_to do |format|
format.any { render json: your_json, content_type: 'application/json' }
end
end
It's just:
render formats: :json
I had similar issue but with '.js' extension. To solve I did the following in the view:
<%= params.except!(:format) %>
<%= will_paginate #posts %>
I tried the above solutions and it didn't solve my use case.
In some of the controllers of my Rails 4.2 app, there was no explicit render called. For example, a service object was called and nothing was returned. Since they are json api controllers, rails was complaining with a missing template error. To resolve I added this to our base controller.
def render(*args)
options = args.first
options.present? ? super : super(json: {}, status: :ok)
end
It's a large app I'm converting to Rails 5, so this is just a safety measure as I removed the RocketPants gem that seemed to do this automatically.
As a note, my controllers inherit from ActionController::Base
Of course:
before_filter :always_json
protected
def always_json
params[:format] = "json"
end
You should probably put this in a root controller for your API.

Need help returning

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

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