3scale/porta

View on GitHub
app/services/import_countries_service.rb

Summary

Maintainability
A
35 mins
Test Coverage
# frozen_string_literal: true

require 'open-uri'

class ImportCountriesService
  # Ignore these invalid/obsolete countries:
  INVALID_COUNTRY_CODES = %w[CS XK AQ UM] # Serbia and Montenegro, Kosovo (not there yet), Antartica

  class CountryData
    def self.from_hash(hash)
      values = hash.with_indifferent_access.values_at(*%i[code name currency])
      new(*values)
    end

    def initialize(code, name, currency)
      @code = code
      @name = name
      @currency = currency
    end

    attr_reader :code, :name, :currency

    def to_h
      { code: code, name: name, currency: currency }
    end

    alias attributes to_h

    def attributes_for_update
      attributes.slice(:name, :currency).merge(updated_at: Time.zone.now)
    end

    def valid?
      !INVALID_COUNTRY_CODES.include?(code)
    end

    def to_s
      "#{name} (#{code},#{currency})"
    end
  end

  class GeonamesLoader
    def self.load_countries
      doc = URI.open('http://download.geonames.org/export/dump/countryInfo.txt')
      lines = doc.readlines.reject { |line| line.starts_with?('#') } # comment.
      lines.map do |line|
        row = line.split("\t")
        CountryData.new(row[0], row[4], row[10])
      end
    end
  end

  def self.call!
    new.call!
  end

  def initialize
    @countries = load_countries
  end

  attr_reader :countries

  def call!
    countries.each do |country_data|
      next unless country_data.valid?
      country = Country.find_by(code: country_data.code)
      if country
        info "Updating #{country_data})"
        country.update!(country_data.attributes_for_update)
      else
        info "Creating #{country_data})"
        Country.create!(country_data.attributes)
      end
    end

    delete_other_countries
  end

  protected

  def load_countries
    info 'Downloading countries from geonames.org...'
    GeonamesLoader.load_countries
  rescue OpenURI::HTTPError, Timeout::Error
    error 'Download countries failed. Using local static source...'
    load_static_countries
  end

  def delete_other_countries
    Country.where.not(code: countries.map(&:code)).delete_all
  end

  def load_static_countries
    json = JSON.parse(File.read(file_path))
    json['countries'].map(&CountryData.method(:from_hash))
  end

  delegate :info, :error, to: 'Rails.logger'

  private

  def file_path
    Rails.root.join('public', 'countries.json')
  end
end