How to rescue from bad url - ruby-on-rails

I have a method in my application that finds a photo from the og:image tag of a link that is submitted. In my create action, I use the method photo_form_url, described below.
def photo_from_url(url)
if !Nokogiri::HTML(open(url)).css("meta[property='og:image']").blank?
photo_url = Nokogiri::HTML(open(url)).css("meta[property='og:image']").first.attributes["content"]
self.photo = URI.parse(photo_url)
self.save
end
end
However, this produces an error if a bad url is entered.
I tried to rescue as below, but this gives me an "undefined method redirect_to"
def photo_from_url(url)
begin
if !Nokogiri::HTML(open(url)).css("meta[property='og:image']").blank?
photo_url = Nokogiri::HTML(open(url)).css("meta[property='og:image']").first.attributes["content"]
self.photo = URI.parse(photo_url)
self.save
end
rescue OpenURI::HTTPError
redirect_to :back, notice: 'link's broken!'
end
end
What am I doing wrong?

According to your answer to my comment, your function photo_from_url is defined in the model. Trying to redirect a user within a model is not possible as shown by the error you are facing.
Bear in mind that your model can be called outside of a browsing session environment. EG:
tests
rake task
You should thus never, ever put any code that has to do with manipulating the user browser, or the user session within your models. This is the job of the controller.
So what you need to do is simply raise an exception or return a specific value in your model when you are encountering a bad url. And react to that exception / return value in your controller by redirecting the user. This ensure that anything that has to do with the user browser stays in the controller, and that you could implement a different behavior in a rake task if encountering the same error.
So, your model should do stuff, and raise errors when it can't :
# Link.rb
def photo_from_url(url)
if !Nokogiri::HTML(open(url)).css("meta[property='og:image']").blank?
photo_url = Nokogiri::HTML(open(url)).css("meta[property='og:image']").first.attributes["content"]
self.photo = URI.parse(photo_url)
self.save
end
end
Your controller should ask your model to do stuff, and deal with the user if there is a problem :
# link_controller
# in create
begin
link.photo_from_url(url)
rescue OpenURI::HTTPError
redirect_to :back, notice: 'link's broken!'
end

Related

Rails Devise rollback transaction doesn't give error

I have an application which uses Devise as authentication. I don't want user to be able to changed there email address. I've done this by setting the email attribute to read only in the User model.
class User < ActiveRecord::Base
attr_readonly :email
end
This works fine: Rails rollback the transaction. Devise however thinks that the update was succesful and displays a message succesfull message.
"Your account has been updated successfully."
I've tried several things including creating my onw methode that would return a flash notice but it keeps saying that the account has been succesfully updated.
Is there a way to raise an error when the record is not saved succesfully?
Edit after Ashvin's anwser. This is what I have in my model:
def email=(address)
begin
if new_record?
write_attribute(:email, address)
end
rescue Exception => error
flash[:alert] = error.message
end
end
I dont know I got your question or not, But from what I get following is solution. You can use exception handling while record is rollback
begin
# do some stuff here
rescue Exception => e
flash[:notice] = e.message
end

StandardError redirect to page

I was handed a project that another developer worked on, without leaving any documentation behind. The code fetches some purchases from a shopping website, looks for a price and notifies the user.
The app may encounter errors like "no results found" and then I raise a standarderror.
I want to redirect the user to the error page and notify them about it but I can't do that because it isn't a controller, so the redirect_to option doesn't work.
services/purchase_checker.rb is called once an hour:
def call
user.transaction do
store_purchase
if better_purchase?
update_purchase
end
end
rescue MyError=> e
store_error(e)
end
def store_error(error)
user.check_errors.create!(error_type: error.class.name, message: error.message)
end
services/my_error.rb:
class MyError< StandardError
def initialize(error_type, error_message)
super(error_message)
#error_type = error_type
end
attr_reader :error_type
end
services/purchase_fetcher.rb:
def parse_result_page
raise purchase_form_page.error if purchase_form_page.error.present?
offer = purchase_page.map{|proposal_section|
propose(proposal_section, purchase) }
.min_by(&:price)
offer or raise MyError.new("No results", "No results could be found")
end
you should create another err class, eg NotFoundError:
offer or raise NotFoundError.new("No results", "No results could be found")
then in your controller:
begin
parse_result_page
rescue NotFoundError => e
redirect_to err_page, :notice => e.message
end
Since this is running in a job, the best way to notify the user would be by email, or some other async notification method. When an error is detected, an email is sent.
If that's not an option for some reason, you can check if a user has check_errors in any relevant controllers. Looking at the store_error(error) method that is called when an error is found, it seems it's creating a new record in the Database to log the error. You should be able to check if a user has any error logged via the user.check_errors relationship.
You could do it like this, for example:
class SomeController < ActionController::Base
# ...
before_action :redirect_if_check_errors
# ...
def redirect_if_check_errors
# Assuming you're using Devise or something similar
if current_user && current_user.check_errors.exists?
redirect_to some_error_page_you_create_for_this_path
end
end
end
This will check for these errors in every action of SomeController and redirect the user to an error page you should create, where you render the errors in the user.check_errors relationship.
There are multiple ways to do this, but I still think sending an email from the Job is a better option if you want to actively notify the user. Or perhaps add an interface element that warns the user whenever user.check_errors has stuff there, for example.
I propose that you do this synchronously so that the response can happen directly in the request/response cycle. Perhaps something like this:
# controller
def search
# do your searching
# ...
if search_results.blank?
# call model method, but do it synchrously
purchase_check = PurchaseChecker.call
end
if purchase_check.is_a?(MyError) # Check if it's your error
redirect_to(some_path, flash: { warning: "Warn them"})
end
end
# model, say PurchaseChecker
def call
# do your code
rescue MyError => e
store_error(e)
e # return the error so that the controller can do something with it
end

Disable autoincrementing id inside of ActiveRecord::Base.transaction

I have a FormObject for registration which creates a user and a lot of models for him inside create method.
def create
ActiveRecord::Base.transaction do
#user = User.create(#params[:user])
process_bonuses_for_user
process_actions_for_user
# et.c., giant amount of methods
end
#user.persisted? # to return the true of false to controller
end
I met the strange behaviour of my FormObject. Even it ran successfull (create a lot of models) or unsuccessful (not saving them), the id of User model is autoincrementing. So, every trying to save something using my FormObject increment the value of next id for User. This is normal situation when User created successfully, but not normal when user makes a mistake on registration form.
How can I disable this unpredictable behaviour?
P.S. I know that everything is work when I write #user = User.new(#params[:user]) at the start of create method and #user.save at the end, but there are a lot of associations, and I don't want to write a lot of autosave or inverse_of in my models.
P.P.S. I'm postgresql-9.4 user
Your transaction is not working because you're using create. You need to use the bang version (create!) to raise an exception on failure which triggers the rollback. Do note that you'll need to rescue the InvalidRecord exception yourself.
In my opinion, it could be something like this:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
process_bonuses_for_user
process_actions_for_user
# et.c., giant amount of methods
...
format.html { redirect_to ... }
end
end

Handling an ActiveRecord error if database is empty

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?

Redirect on catching an exception in a method in the model

I am using Authlogic-connect to connect various service providers. There is a method in user.rb
def complete_oauth_transaction
token = token_class.new(oauth_token_and_secret)
old_token = token_class.find_by_key_or_token(token.key, token.token)
token = old_token if old_token
if has_token?(oauth_provider)
self.errors.add(:tokens, "you have already created an account using your #{token_class.service_name} account, so it")
else
self.access_tokens << token
end
end
When a service provider is already added it gives the error as stated in the has_token? method and the page breaks. I need to redirect the app to the same page and flash the error. How do i do this? I have overridden the method in my own user.rb so that I can change the code.
Hmm, well you could put a method that handles the error that has_token? throws, and tell your controller to redirect that exact error. something like this in your controller:
rescue_from OauthError::RecordNotFound, :with => :deny_access
then you can put
def deny_access
redirect_to your_view_path, :alert => "Too bad sucker" #some flash message
end
Or you could do something like this in the controller:
if complete_oauth_transaction.errors.present?
redirect_to your_view_path
else
# continue on with the normal code here
end
This is how you could generically handle errors. Your exact code will vary, as this is all we have to go off of.

Resources