AgileVentures/MetPlus_PETS

View on GitHub
app/services/cruncher_service.rb

Summary

Maintainability
A
1 hr
Test Coverage
# Interfaces to Cruncher service API
class CruncherService
  require 'rest-client'
  require 'json'

  @@auth_token = nil

  def self.service_url
    # Changed to use constant instead of class var as Rails reloads all
    # classes on each request (unless config.cache_classes = true)
    CRUNCHER_URL
  end

  def self.upload_file(file, file_name, file_id)
    mime_type = MIME::Types.type_for(URI.escape(file_name))

    raise "Invalid MIME type for file: #{file_name}" if mime_type.empty?
    raise "Unsupported file type for: #{file_name}" unless
              Resume::MIMETYPES.include? mime_type.first.content_type

    retry_upload = true
    begin
      result = RestClient.post(service_url + '/resume/upload',
                               { 'file' => file,
                                 'name' => file_name,
                                 'userId' => file_id },
                               'Accept' => 'application/json',
                               'X-Auth-Token' => auth_token,
                               'Content-Type' => mime_type.first.content_type)

      return JSON.parse(result)['resultCode'] == 'SUCCESS'

    rescue RestClient::Unauthorized # most likely expired token
      # Retry and force refresh of cached auth_token
      self.auth_token = nil
      if retry_upload
        retry_upload = false
        file = file.open # reopen as .post closes the file
        retry
      end
      raise
    end
  end

  def self.download_file(file_id)
    # Download the contents of the file which was stored in the Cruncher
    # using the file_id (e.g. resume.id).
    # Save the contents to a Tempfile, and close file.
    # Return the Tempfile instance if successful, nil otherwise.

    retry_download = true
    begin
      # Successful call returns RestClient::Response instance.
      # That quacks like a string but also contains other info, see:
      # https://github.com/rest-client/rest-client#result-handling

      contents = RestClient.get(service_url + "/resume/#{file_id}",
                                'X-Auth-Token' => auth_token)

      # If successful, :content_disposition will be set in the header
      return nil unless contents.headers[:content_disposition]

      tempfile = Tempfile.new("file_id_#{file_id}_", './tmp',
                              encoding: contents.encoding)
      tempfile.write contents
      tempfile.close

      return tempfile

    rescue RestClient::Unauthorized # most likely expired token
      # Retry and force refresh of cached auth_token
      self.auth_token = nil
      if retry_download
        retry_download = false
        retry
      end
      raise
    end
  end

  def self.create_job(job_id, title, description)
    retry_create = true
    begin
      result = RestClient.post(service_url + '/job/create',
                               { 'jobId'   => job_id,
                                 'title'   => title,
                                 'description' => description },
                               'Accept' => 'application/json',
                               'X-Auth-Token' => auth_token)

      return JSON.parse(result)['resultCode'] == 'SUCCESS'

    rescue RestClient::Unauthorized # most likely expired token
      # Retry and force refresh of cached auth_token
      self.auth_token = nil
      if retry_create
        retry_create = false
        retry
      end
      raise
    end
  end

  def self.update_job(job_id, title, description)
    retry_update = true
    begin
      result = RestClient.patch(service_url + "/job/#{job_id}/update",
                                { 'jobId'   => job_id,
                                  'title'   => title,
                                  'description' => description },
                                'Accept' => 'application/json',
                                'X-Auth-Token' => auth_token)

      return JSON.parse(result)['resultCode'] == 'SUCCESS'

    rescue RestClient::Unauthorized # most likely expired token
      # Retry and force refresh of cached auth_token
      self.auth_token = nil
      if retry_update
        retry_update = false
        retry
      end
      raise
    end
  end

  def self.match_resume_and_job(resume_id, job_id)
    # Matches given resume to given job and returns match scores
    # from all available matchers.
    # Returns a hash with these 2 key-value pairs:
    #  status: 'SUCCESS' or 'ERROR'
    #  stars: (if status==SUCCESS) hash with one match score per cruncher
    #         matcher, e.g.: {"NaiveBayes"=>3.1, "ExpressionCruncher"=>2.9}
    #  message (f status==ERROR) string indicating specific error
    #         'No resume found with id: <resume_id>', or,
    #         'No job found with id: <job_id>'

    retry_match = true

    begin
      result = RestClient.get(service_url +
                          "/resume/#{resume_id}/compare/#{job_id}",
                              'X-Auth-Token': auth_token)

      return { status: 'SUCCESS', stars: JSON.parse(result)['stars'] }

    rescue RestClient::Unauthorized # most likely expired token
      # Retry and force refresh of cached auth_token
      self.auth_token = nil
      if retry_match
        retry_match = false
        retry
      end
      raise

    rescue RestClient::ResourceNotFound => e
      return { status: 'ERROR', message: JSON.parse(e.response)['message'] }
    end
  end

  def self.match_jobs(resume_id)
    # Define retry_search to true outside the begin block
    # otherwise retry_search would be set to true everytime
    # we retry the block. Can also set it inside the begin block
    # like retry_search ||= true so that its set only the first
    # time

    retry_search = true

    begin
      result = RestClient.get(service_url + '/job/match/' + resume_id.to_s,
                              'X-Auth-Token' => auth_token)

      result_hash = JSON.parse(result)

      # Set matching jobs to nil if resume couldn't be found
      matching_jobs = nil

      if result_hash['resultCode'] == 'SUCCESS'
        # May or may not contain jobs matching the resume
        matching_jobs = result_hash['jobs']
      end
    rescue RestClient::Unauthorized
      self.auth_token = nil
      if retry_search
        retry_search = false
        retry
      end
      raise
    end
    matching_jobs
  end

  def self.match_resumes(job_id)
    retry_match = true

    begin
      result = RestClient.get(service_url + '/resume/match/' + job_id.to_s,
                              'X-Auth-Token' => auth_token)

      result_hash = JSON.parse(result)

      # Set matching resumes to nil if job couldn't be found
      matching_resumes = nil

      if result_hash['resultCode'] == 'SUCCESS'
        matching_resumes = result_hash['resumes']
      end

    rescue RestClient::Unauthorized
      if retry_match
        self.auth_token = nil
        retry_match = false
        retry
      end
      raise
    end
    matching_resumes
  end

  def self.auth_token
    return @@auth_token if @@auth_token
    begin
      result = RestClient.post(service_url + '/authenticate', {},
                               'X-Auth-Username' => ENV['CRUNCHER_SERVICE_USERNAME'],
                               'X-Auth-Password' => ENV['CRUNCHER_SERVICE_PASSWORD'])
    rescue Exception => e
      raise 'Invalid credentials for Cruncher access' if
                    e.class == RestClient::Unauthorized
      raise
    end

    self.auth_token = JSON.parse(result)['token']
  end

  def self.auth_token=(token)
    @@auth_token = token
  end
end