Rails: How do I add into the controller an error that comes from a before_save in the model?

436 Views Asked by At

I have an app where you submit a link and some info is extracted from it using "net/http" and Nokogiri. The way it works is that a user submits an URL and before the post is saved, the info is extracted from the URL and added to different properties of the model: title, description, etc...

Sometimes, when there is a problem with the link, the before_save fails, like for example when the SSL certificate verification fails like this:

OpenSSL::SSL::SSLError (SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate))

How do I make sure this or any error is presented to the user so that they know what's going on?

I am using Rails 7 and Hotwire, currently an error like this fails silently. I can see it in the console but nothing is shown in the frontend.

Here is my create action:

def create
  notice   = "Company created."
  @company = Company.new(company_params)
  @company.user_id = current_user.id
  
  if @company.save
    respond_to do |format|
      format.html { redirect_to company_path(@company), notice: notice }
      format.turbo_stream { flash.now[:notice] = notice }
    end
  else
    render :new, status: :unprocessable_entity
  end
end

I tried also adding a notice for "else", like the following but still nothing is shown when the saving fails.

def create
  @company = Company.new(company_params)
  @company.user_id = current_user.id
  
  if @company.save
    notice   = "Company created."
    respond_to do |format|
      format.html { redirect_to company_path(@company), notice: notice }
      format.turbo_stream { flash.now[:notice] = notice }
    end
  else
    notice  = "Company could not be created!"
    respond_to do |format|
      format.html { render :new, status: :unprocessable_entity }
      format.turbo_stream { flash.now[:notice] = notice }
    end
  end
end

To parse the url I use:

require "net/http"
noko = Nokogiri::HTML(Net::HTTP.get(URI("https://sample-url.com")))

Then for example to extract the title I do:

noko.title

I have a before_save in the model that assigns the title like:

self.title = noko.title

Everything works as expected, except when the before_save fails, like in the example above. Do you have any suggestions on either:

  1. how to make sure those errors show in the front?
  2. how would you implement something similar if you wouldn't use the before_save option?
  3. Any other suggestion is greatly appreciated!

Thanks a lot in advance!

1

There are 1 best solutions below

3
Mehmet Adil İstikbal On

I could solve this problem with 2 different approach. First adding errors in before_save method or raise an exception.

class Company << ApplicationRecord
  before_save :extract_data1
  before_save :extract_data2

  # first approach
  def extract_data1
    # do something
    if something_wrong
      errors.add(:base, message: 'Something went wrong')
    end

    # do another thing
    if another_thing_wrong
      errors.add(:base, message: 'Another thing went wrong')
    end
  rescue OpenSSL::SSL::SSLError => e
    errors.add(:base, message: e.message)
  end

  # second approach

  def extract_data2
    # do something
    raise StandardError, message: 'Something went wrong' if something_wrong
    
    # do another thing
    raise StandardError, message: 'Another thing went wrong' if another_error
  end

end

First approach prevents record from saving. It adds errors to the object which can be access by object.errors.details. If object.save return false then in the else block you can show the errors to user.

Second approach is raising exceptions instead of handling errors more silently. You can rescue this exceptions in your controller and show this errors to the user.

I would suggest the first approach because it is easy to implement, raising StandardError not a best thing to do here. I would suggest you to define custom exception classes for your application if you are going to choose the second way.

Edit: I suggested fastest implementation based on your question. If you are looking for more clean solution then you can create a service class and move all the logic to the service which includes creating a company and extracting data. Above 2 approach still viable in this service class so pick whatever you like.

Links that can help you:

acticle about handling exceptions

documentation about active model errors.

acticle about service object approach.