Rails Devise rollback transaction doesn't give error - ruby-on-rails

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

Related

rails 5 - before_destroy - rescue exception

In my app, I want to avoid records being deleted when they are used in associated records. For example if a business partner has documents (as sender OR receiver), it should not be destroyed and the user should get a flash message.
I use below code, yet get an exception.
before_destroy do |business_partner|
if Document.where(:sender_id => business_partner).exists? ||
Document.where(:receiver_id => business_partner).exists? ||
Annotation.where(:sender_id => business_partner).exists? ||
Annotation.where(:receiver_id => business_partner).exists?
raise "#{business_partner.name} has ongoing activity and cannot be deleted."
end
end
Tried several alternatives - like flash[:notice] and message:
How should it be done?
You get an exception, because you raise it.
You can change it to redirection somewhere with an alert:
redirect_to root_path, alert: "#{business_partner.name} has ongoing activity and cannot be deleted."
You might also want to take a look into associations, dependent: :restrict_with_error:
:restrict_with_error causes an error to be added to the owner if
there are any associated objects.

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

How to rescue from bad url

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

don't understand some code from a RailsCast tutorial

I watched the RailCasts tutorial #274 on Remember Me and Reset Password. The code he adds is the following inside user.rb
def send_password_reset
generate_token(:password_reset_token)
save!
UserMailer.password_reset(self).deliver
end
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
Here what I don't understand is why the save! call inside send_password_reset? Also, I'm not familiar with the syntax in generate_token: self[column]=. Is this the way to set a column inside a database table?
Here's the create action of the password_resets_controller
def create
user = User.find_by_email(params[:email])
user.send_password_reset if user
redirect_to root_path, notice: "Email sent with password reset instructions."
end
save! saves the object and raises an exception if it fails.
self[column]=, is a slight meta-programming.
Usually, when you know the column name, you'd do: self.password_reset_token=. Which is the same as self[:password_reset_token]= or self["password_reset_token"]=.
So it's easy to abstract it a bit passing column name as string/symbol.
Clearer?
1) save! is like save, but raise a RecordInvalid exception instead of returning false if the record is not valid.
Example from my console:
User.new().save # => false
User.new().save! # ActiveRecord::RecordInvalid: Validation failed: Password can't be blank, Email can't be blank
2) self[column]= there for setting users column.

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