This is for a rails 4.0.2 application running ruby 2.0.0-p353.
I have a helper that has the following method
def render_feed_row(activity_item)
begin
if activity_item.type == "comment"
render "newsfeed/comment", :item => activity_item
elsif activity_item.type == "post"
render "newsfeed/post", :item => activity_item
else
raise NameError
end
rescue NameError => e # note: NoMethodError is a subclass of NameError
render "newsfeed/other", :item => activity_item
end
end
But if a NoMethodError is raised in the newsfeed/post partial, it is not caught in this helper. Why is that? It don't render newsfeed/other, it actually raises an exception.
I've just checked with a simplified version of your code, and it worked correctly.
def render_feed_row
raise NoMethodError
rescue NameError
puts "kaboom"
end
render_feed_row
# => kaboom
However, please note it's a very bad practice to use exceptions for control flow in such way. Moreover, if you really want to use exceptions, you should create your own classes, not use NoMethodError or NameError that have specific meanings in Ruby programs.
Here's a more clean version of your method
def render_feed_row(activity_item)
case (type = activity_item.type)
when "comment", "post"
render "newsfeed/#{type}", item: activity_item
else
render "newsfeed/other", item: activity_item
end
end
Or a more concise version
def render_feed_row(activity_item)
name = case activity_item.type
when "comment", "post"
activity_item.type
else
"other"
end
render "newsfeed/#{name}", item: activity_item
end
Using exceptions for control flow is slow. Why not just:
def render_feed_row(activity_item)
partial = ['comment', 'post'].include?(activity_item.type) ?
activity_item.type : 'other'
render "newsfeed/#{partial}", :item => activity_item
end
Related
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
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'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
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...
I need to implement a custom error page in my rails application that allows me to use erb.
I've been following this tutorial (http://blog.tommilewski.net/2009/05/custom-error-pages-in-rails/) and I cannot get it to work locally (or remotely). I am running Rails 2.3.5
Here's the gist of the approach.
1) in the 'application_controller', I over ride the "render_optional_error_file(status_code)" method, and set the visibility to "protected", like this.
protected
def render_optional_error_file(status_code)
known_codes = ["404", "422", "500"]
status = interpret_status(status_code)
if known_codes.include?(status_code)
render :template => "/errors/#{status[0,3]}.html.erb", :status => status, :layout => 'errors.html.erb'
else
render :template => "/errors/unknown.html.erb", :status => status, :layout => 'errors.html.erb'
end
end
def local_request?
true
end
I also created a folder within views called errors and created the following views: 404.html.erb, 422.html.erb, 500.html.erb,unknown.html.erb and I created a new layout "errors.html.erb"
I can't seem to get it to work. I've been trying to trigger the 404 page by navigating to http://localhost:3000/foobar -- but, instead of getting the new 404.html.erb, I seem to be getting the standard apache 500 error. This happens when I try both mongrel_rails start and mongrel_rails start -e production.
I would suggest using exceptions to render such error pages, so you can use inheritance to group your error messages...
First, declare some (I usually do it in application_controller.rb)
class Error404 < StandardError; end
class PostNotFound < Error404; end
Then add code to ApplicationController to handle them
class ApplicationController < ActionController::Base
# ActionController::RoutingError works in Rails 2.x only.
# rescue_from ActionController::RoutingError, :with => :render_404
rescue_from Error404, :with => :render_404
rescue_from PostNotFound, :with => :render_post_not_found
def render_404
respond_to do |type|
type.html { render :template => "errors/error_404", :status => 404, :layout => 'error' }
type.all { render :nothing => true, :status => 404 }
end
true
end
def render_post_not_found
respond_to do |type|
type.html { render :template => "errors/shop_not_found", :status => 404, :layout => 'error' }
type.all { render :nothing => true, :status => 404 }
end
true
end
end
This renders errors/error_404 with the errors layout. Should get you started :)
And in your target_controller:
raise PostNotFound unless #post
Edit
Note for Rails 3
for a longer explanation on why ActionController::RoutingError doesn't work for rails 3:
Rails 3.0 Exception Handling.
Rails ticket 4444
"If your application relies on engines that extend your app with their
own routes, things will break because those routes will never get
fired!"
Firstly - have you deleted the file: 'public/500.html' If that file exists, it will override anything else that you try to do.
Secondly, using an explicit "rescue_from" in the controller (as explained in the other comment) - is a good option if you need to fine-tune the response to different kinds of errors.
You most likely get the 500 error because of an application error.
Have you checked the log files?
Update:
Are you certain that you are running 2.3.5 and not an older version that happens to be installed?
Mongrel should say which version you are running when it starts, otherwise it should say in the config/environment.rb file.
There are some errors in the code that might create the 500 error. I've changed that and also corrected a few other things I think you meant :)
def render_optional_error_file(status_code)
known_codes = ["404", "422", "500"]
status = interpret_status(status_code)
if known_codes.include?(status) # Here it should be status, not status_code (which is a symbol)
render :template => "errors/#{status[0,3]}", :status => status, :layout => 'errors' # No need to mention ".html.erb", you can do it, but it is not necessary since rails will guess the correct format.
else
render :template => "errors/unknown", :status => status, :layout => 'errors'
end
end
def local_request?
# This must return false instead of true so that the public viewer is called
# instead of the developer version.
false
end
Purpose of completeness for newer rails versions:
http://www.frick-web.com/en/blog/nifty_errorpages-gem
that is a little rails engine for handling your error pages. Maybe you will need it for newer projects. it is a good option to handle errors in my opinion.