Oddly I'm having a hard time finding good docs about basic error handling in rails. I'd appreciate any good links as well as thoughts on a handling errors in a really basic method like this:
def self.get_record(id)
People.first( :conditions => ["id = ?", id] )
end
1) I could verify that id != nil, and that it's numeric.
2) I could also then verify that a record is found.
Is there anything else?
Are both #1 and #2 recommended practice? In both cases would you simply create a flash message with the error and display it, or is that giving away too much information?
As I'm sure you know, this is just like People.find(id), except that find raises an error.
However, People.find_by_id(id) returns nil if no record is found, which I suspect takes care of all you need. You don't need to check that what you put into ActiveRecord is the correct data type and the like; it handles SQL injection risks, so to check ahead of time would not affect actual behavior.
If we're just looking at the show action, though, there's an even more elegant way: rather than using find_by_id and checking for nil, use find, let an error bubble up, and let the controller catch it with rescue_from. (By default, in production, ActiveRecord::RecordNotFound will be caught and rescued by showing a generic 404, but you can customize this behavior if necessary.)
class UsersController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, :with => :not_found
def show
#user = User.find params[:id]
end
protected
def not_found
flash[:error] = "User not found"
redirect_to users_path
end
end
Code untested, for illustrative purposes only ;)
Don't do flash[:notice]'s insted just say that "Record not found"
The two things required by you can be done as follows:
1) I could verify that id != nil, and that it's numeric.
def self.get_record(id)
People.first( :conditions => ["id = ?", id] ) if id.integer? unless id.blank?
end
2) I could also then verify that a record is found.
def self.get_record(id)
#people = People.first( :conditions => ["id = ?", id] ) if id.integer? unless id.blank?
flash[:notice] = #people.blank? # this will print true/false depending on value in #people
end
Hope it works for you. :D
Related
I'm working on a rails 4 app, and i have the following controller code
def index
#issue = Issue.find(1)
#sections = #issue.sections
#articles = #issue.articles
end
which breaks if the database is empty with the error: "Couldn't find Issue with id=1". What is the proper way to check for this in a way that if nothing is in the db it doesn't raise an error?
One method you can use is the exists? active record method, like so:
#issue = Issue.where(id: 1)
if #issue.exists?
# do something if it exists
else
# do something if it is missing
end
Side note: Since you're attempting to find by id, you don't necessarily need the .where portion; you can simply do: Issue.exists?(1).
exists? documentation on APIDoc
In most cases such exception is expected and recommenced. For example, you can rescue it with a custom 404 page.
Anyway, if you really don't want that, you can use find_by method which will output nil if nothing found
#issue = Issue.find_by(id: 1)
you can handle that exception in your controller
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
def record_not_found
flash[:alert] = "invalid information"
redirect_to root_url
end
or you can use a where clause
#issue = Issue.where(id: 1).first
now check for nil by
#issue.nil?
This is more a style question than anything.
When writing queries, I always find myself checking if the result of the query is blank, and it seems - I dunno, overly verbose or wrong in some way.
EX.
def some_action
#product = Product.where(:name => params[:name]).first
end
if there is no product with the name = params[:name], I get a nil value that breaks things.
I've taken to then writing something like this
def some_action
product = Product.where(:name -> params[:name])
#product = product if !product.blank?
end
Is there a more succinct way of handling nil and blank values? This becomes more of a headache when things rely on other relationships
EX.
def some_action
#order = Order.where(:id => params[:id]).first
# if order doesn't exist, I get a nil value, and I'll get an error in my app
if !#order.nil?
#products_on_sale = #order.products.where(:on_sale => true).all
end
end
Basically, is there something I haven;t yet learned that makes dealing with nil, blank and potentially view breaking instance variables more efficient?
Thanks
If its just style related, I'd look at Rails' Object#try method or perhaps consider something like andand.
Using your example, try:
def some_action
#order = Order.where(:id => params[:id]).first
#products_on_sale = #order.try(:where, {:onsale => true}).try(:all)
end
or using andand:
def some_action
#order = Order.where(:id => params[:id]).first
#products_on_sale = #order.andand.where(:onsale => true).andand.all
end
Well even if you go around "nil breaking things" in your controller, you'll still have that issue in your views. It is much easier to have one if statement in your controller and redirect view to "not found" page rather than having several ifs in your views.
Alternatively you could add this
protected
def rescue_not_found
render :template => 'application/not_found', :status => :not_found
end
to your application_controller. See more here: https://ariejan.net/2011/10/14/rails-3-customized-exception-handling
I'm wondering what is best practive for handling this type of situation in ruby on rails.
users_controller.rb
def show
#user = User.find params[:id]
end
If user is not found it throws an exception which isn't catched anywhere so it will display some ugly things to enduser.
Solution would be pack it into begin...rescue...end block:
def show
begin
#user = User.find params[:id]
rescue
flash[:error] = "User not found"
redirect :action => :index
end
end
although I've never seen such code in any rails article or tutorial I've seen.
What is the proper way of handling this type of situations?
See docs rescue_from
It depends on your requirement.
But you want a general solution then you can have a rescue block in ApplicaionController which will handle the RecordNotFound exception.
You can do
def show
#user = User.find_by_id(params[:id])
unless #user
flash[:error] = "User not found"
redirect :action => :index
end
end
But you cant expect you will call a link with id which is not in the db from within the application. Please see the answer of the question
The development environment will show you ugly error messages, but a production environment will just give an HTTP 404 error (page not found) when the id is invalid.
I think that you may be able to fix this with
#user = User.find(params[:id] = current_user)
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).
I'd like to create a before_filter method in my application controller like this...
def check_role(role_name)
unless logged_in_user.has_role? role_name
flash[:notice] = 'Access to that area requires additional privileges.'
redirect_to :back
end
end
However, it doesn't look as though before filters can take parameters.
Is there a way to parameterize this call, or am I trying to drive a screw with a hammer?
You should be able to do this with a block:
before_filter {|controller| controller.check_role('admin') }
You can use a bit of meta-programming. Something like this (completely untested, just something to give you an idea of how it might go):
Module RoleWithIt
Role.all.each do |role|
define_method("check_#{role.name}_role".to_sym) do
check_role(role.name)
end
end
def check_role(role_name)
return if logged_in_user.has_role?(role_name)
flash[:notice] = 'Access to that area requires additional privileges.'
redirect_to :back
end
end
ApplicationController.send :include, RoleWithIt
To have it load when your app initialises, just put it in a file called role_with_it.rb and put it in your lib directory.
am I trying to drive a screw with a
hammer?
Er, possibly ;-)
If I'm reading this correctly, you have a situation where actions within a controller have different access levels, so you want to remove the duplication by creating a single check function?
So you're looking to do something like this?
before_filter :check_role('admin'), :only => [:admin, :debug]
before_filter :check_role('power'), :only => [:edit, :delete]
But the parameter in parens thing is not legal. And anyway, I still see a fair bit of duplication here!
In general, with an area of functionality as well-visited as controller filters, if you can't do something, it's probably because you're looking at something the wrong way. (Remember that Rails is proud to describe itself as "opinionated software"!)
How would it be if you were able to know the action name in your filter method?
Then we'd have
before_filter :check_role
Which is pretty DRY.
We could define permissions in a Hash, perhaps:
Perms = { :admin => ['admin'], :edit => ['admin', 'power'], etc
... which seem to encapsulate the distinct elements of the duplication. If it got complex then the whole thing could move off into a table, although then you're probably duplicating functionality already available in a plugin.
And we'd have
protected
def check_role
for required_role in Perms[params[:action]]
return if logged_in_user.has_role? required_role
end
flash[:notice] = 'Access to that area requires additional privileges.'
redirect_to :back
end
Or something similar. params[:action] works on my current Rails version (2.1.2), although the Rails book (v2) mentions an action_name method that seems to return blank for me.
I don't believe you can pass parameters to filters. So what I have do in the past is made static methods that pass the parameter to the method that needs the params.
So I would have something like this:
def check_role(role_name)
unless logged_in_user.has_role? role_name
flash[:notice] = 'Access to that area requires additional privileges.'
redirect_to :back
end
end
def check_admin_role
check_role('admin')
end
def check_blah_role
check_role('blah')
end
Then in your controller you'd just call
before_filter :check_admin_role
There is probably some way to implement this with meta-programming but I am still quite a n00b and haven't figured that part out yet ;)
it's an old question, but if somebody still wonders, a good asnwer for rails 4 can be found here