Search code examples
ruby-on-railsrubyexceptionenumeratorrescue

How do I rescue an exception from inside an Enumerator?


I'm writing an application in Rails and I want to get a huge amount of information from an API – which I'm streaming through an Enumerator object as a CSV export. I want to rescue an error that is called within the Enumerator.

CONTROLLER: Enumerator

def csv_lines( url )
    Enumerator.new do |y|
        per_page = 200

        # Parse parameters and get shelf information
        _params = BrowseScraper.get_params(url)
        shelf = BrowseScraper.get_preso( _params, 0 )
            total_items = shelf['response']['total_results']['all'].to_i
            total_pages = ( total_items / per_page.to_f ).ceil
            shelf_info  = BrowseScraper.crawl_ids( shelf['response']['query']['category'] )

        y << BrowseScraper.csv_header(url, shelf_info, total_items, ["Tool ID", "Name", "Price", "URL"])

        total_pages.times { |i| y << BrowseScraper.csv_body( _params, per_page, i+1) }
    end
end

The following functions are raising errors, but I can't catch them outside of the Enumerator:

MODEL: methods

def self.get_params
  response = open(url)
  raise if response.code != 200
end

CONTROLLER: Display

def export
    url = params[:url]
    raise StandardError, "Please enter a Browse URL below" if !url || url.empty?

    respond_to do |format|
        format.csv do
            render_csv(url)
        end
        format.html { render_csv(url) }
    end
rescue => e
    flash[:error] = e.message
    redirect_to scraper_path
end

private
    def render_csv( url )
        set_file_headers
        set_streaming_headers

        response.status = 200

        # Rails should iterate this enumerator
        self.response_body = csv_lines(url)
    end

    def set_file_headers( name = "browse_export" )
        headers["Content-Type"] ||= 'text/csv'
        headers["Content-Disposition"] = "attachment; filename=\"#{name}.csv\""
        headers["Content-Transfer-Encoding"] = "binary"
        headers["Last-Modified"] = Time.now.ctime.to_s
    end

    def set_streaming_headers
        #nginx doc: Setting this to "no" will allow unbuffered responses suitable for Comet and HTTP streaming applications
        headers['X-Accel-Buffering'] = 'no'
        headers["Cache-Control"] ||= "no-cache"
        headers.delete("Content-Length")
    end

Rescuing the error raised in export works. Rescuing an error within the Enumerator works (example:

Enumerator do |y|
  begin
    y << BrowseScraper.get_params(_params)
  rescue => e
    Rails.logger.error "Failed to get parameters: #{e.message}"
  end
end

How can I rescue an exception outside of the Enumerator so I can properly redirect the user with a flash message? How do I pass the exception from within the Enumerator object? What is it about the Enumerator that isn't letting me rescue it with:

def method
    Enumerator do |y|
        y << BrowseScraper.get_params(_params)
    end
rescue => e
    Rails.logger.error "Error in Enumerator is #{e.message}"
end

Solution

  • I think I've figured out what's going on here. When you write code in an Enumerator, the block isn't actually executed within the Enumerator. Therefore, if I add a rescue within the Enumerator, it doesn't matter.

    This is because the |y| in Enumerator is actually a yielder object which does the yielding (more on that in the Enumerator documentation or the Enumerator::Yielder documentation.

    You have to rescue things beforehand.