Veske
Veske

Reputation: 557

Including .xml file to rails and using it

So I have this currency .xml file:

http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml

Now, I am wondering, how can I make my rails application read it? Where do I even have to put it and how do I include it?
I am basically making a currency exchange rate calculator.

And I am going to make the dropdown menu have the currency names from the .xml table appear in it and be usable.

Upvotes: 1

Views: 139

Answers (1)

struthersneil
struthersneil

Reputation: 2750

First of all you're going to have to be able to read the file--I assume you want the very latest from that site, so you'll be making an HTTP request (otherwise, just store the file anywhere in your app and read it with File.read with a relative path). Here I use Net::HTTP, but you could use HTTParty or whatever you prefer.

It looks like it changes on a daily basis, so maybe you'll only want to make one HTTP request every day and cache the file somewhere along with a timestamp.

Let's say you have a directory in your application called rates where we store the cached xml files, the heart of the functionality could look like this (kind of clunky but I want the behaviour to be obvious):

def get_rates
  today_path = Rails.root.join 'rates', "#{Date.today.to_s}.xml"

  xml_content = if File.exists? today_path
                  # Read it from local storage
                  File.read today_path
                else
                  # Go get it and store it!
                  xml = Net::HTTP.get URI 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
                  File.write today_path, xml
                  xml
                end

  # Now convert that XML to a hash. Lots of ways to do this, but this is very simple xml.
  currency_list = Hash.from_xml(xml_content)["Envelope"]["Cube"]["Cube"]["Cube"]

  # Now currency_list is an Array of hashes e.g. [{"currency"=>"USD", "rate"=>"1.3784"}, ...]
  # Let's say you want a single hash like "USD" => "1.3784", you could do a conversion like this
  Hash[currency_list.map &:values]
end

The important part there is Hash.from_xml. Where you have XML that is essentially key/value pairs, this is your friend. For anything more complicated you will want to look for an XML library like Nokogiri. The ["Envelope"]["Cube"]["Cube"]["Cube"] is digging through the hash to get to the important part.

Now, you can see how sensitive this will be to any changes in the XML structure, and you should make the endpoint configurable, and that hash is probably small enough to cache up in memory, but this is the basic idea.

To get your list of currencies out of the hash just say get_rates.keys.

As long as you understand what's going on, you can make that smaller:

def get_rates
  today_path = Rails.root.join 'rates', "#{Date.today.to_s}.xml"

  Hash[Hash.from_xml(if File.exists? today_path
                       File.read today_path
                     else
                       xml = Net::HTTP.get URI 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
                       File.write today_path, xml
                       xml
                     end)["Envelope"]["Cube"]["Cube"]["Cube"].map &:values]
end

If you do choose to cache the xml you will probably want to automatically clear out old versions of the cached XML file, too. If you want to cache other conversion lists consider a naming scheme derived automatically from the URI, e.g. eurofxref-daily-2013-10-28.xml.

Edit: let's say you want to cache the converted xml in memory--why not!

module CurrencyRetrieval
  def get_rates
    if defined?(@@rates_retrieved) && (@@rates_retrieved == Date.today)
      @@rates
    else
      @@rates_retrieved = Date.today
      @@rates = Hash[Hash.from_xml(Net::HTTP.get URI 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')["Envelope"]["Cube"]["Cube"]["Cube"].map &:values]
    end
  end
end

Now just include CurrencyRetrieval wherever you need it and you're golden. @@rates and @@rates_retrieved will be stored as class variables in whatever class you include this module within. You must test that this persists between calls in your production setup (otherwise fall back to the file-based approach or store those values elsewhere).

Note, if the XML structure changes, or the XML is unavailable today, you'll want to invalidate @@rates and handle exceptions in some nice way...better safe than sorry.

Upvotes: 2

Related Questions