Handle and convert your Money!

Think of Coinage as a weedy, overworked guy. Amongst other things, this man enjoys exercising Biro’s so that his collection’s ink levels, when ordered, form a sine wave. So yeah, he also likes the finer points of money. So does Coinage.

Here’s what you want to know. Coinage allows for: simple conversion between currencies; reliable monetary arithmetic; multi-national input parsing; easy Rails & Merb money handling.

This is not meant as a replacement for ActiveMerchant, it is, however, meant as a replacement for Money.

WARNING: To quote 1998, this software is still very much “under construction”. I really wouldn’t recommend using it in production until it reaches 1.0.

Install

sudo gem install coinage

Usage

require ‘coinage’ a = 5.dollars b = Money.new(5, :aud) a + b

Ok, lets work through this example. First off, you have to require Coinage. a is then created as a new Money instance with the value of $5. Whenever new Money instances are created without specifying a currency (like in the second line) the currency defaults to United States Dollars. This can be changed as follows:

Money.default_currency = :aud

The third line creates a new Money instance but this time in Australian Dollars. Note that a does not necessarily equal b as it is unlikely that the two currencies will ever reach parity.

Finally, a + b adds the two variables together. Out of the box this should return a new Money object with value USD$10. It is in US Dollars as any arithmetic operator sets the result’s currency to the first operand’s currency. The result has a value of 10 dollars because by default, Coinage uses the Coinage::Exchange::None exchange, which always provides an exchange rate of 1. This default will change as the other exchanges become more stable.

The default exchange can be changed:

Money.default_exchange = Coinage::Exchange::Xurrency.new

ActiveRecord

For the moment, its not nat pretty integrating Coinage with money, but its quite easy:

composed_of :price, :class_name => ‘Money’, :mapping => %w(price_in_cents currency) do |price| price.to_money end

That creates a virtual property, value, which maps to the database columns price_in_cents and currency. Coinage stores all values as cents. It also creates a setter method, value= which can accept a string so in your view you can have something like:

<%= f.text_field :value %>

That will work with mass assignment (read: out of the box) and deal with storing all monetary values. Automagic!

Defining Custom Exchanges

Say you have MaiSpecialAPI giving you currency rates and you want to use that instead of Xurrency or Yahoo. Just create a new exchange like the one below and put all your code in the rate method.

class MaiSpecialExchange include Coinage::Exchange::Base def rate(current, target) super(current, target) # Verifies that both currencies are supported by your exchange MaiSpecial.get(current, target) end def supported_currencies [ :lolz, :catz ] end end

Then just set

Money.default_exchange = MaiSpecialExchange.new

and Wallah! Your all set.

Notes

  • Namespaces: While all of Coinage’s internal classes are defined in the Coinage namespace, the Money class is defined in the global namespace.
  • Class Extension_: Numeric and String are given methods such as @:tomoney@ and :cents.
  • Commutativity: With two Money instances, a + b != b + a if a and b have different currencies. This is because currencies are not quantitative and favors the instance acted upon (a) rather than the passed instance (b).
  • Supported Currencies: Are currently hard coded. Not the friendliest interface to work with, but it should work for the most part.

Plans

  • Cache exchange calls: This will provide a major speed-up. Cache to a few different back-ends (flat file, memcached, db) so that calls only have to be made at a defined frequency, rather than every time a conversion is needed. Rake tasks will also be provided if you want to update this cache externally.
  • More exchange APIs: At the moment it only supports the Xurrency API. I plan on adding support for Yahoo and XE Premium (when I get a sample response). It is very easy to add your own API back-ends.
  • Use libxml-ruby: By far the fastest XML parser out there, it currently is fairly undocumented. Definitely switch to it for parsing exchange data.
  • Rails 2.2 Integration: When 2.2 comes out Coinage will be there supporting the new i18n & L10n features in Rails core.
  • ActiveRecord & DataMapper support: Provide a class method which will make it easier to transform ordinary fields into Money objects.
  • String Parsing: Be able to parse a wide variety of user input such as: ‘AUD$50’, ‘$50 AUD’, ‘€50’, ‘€50,99’, ‘50,000.00’ and ‘50.000,00’.

Authors

Contribute

You can checkout the source or fork it yourself from Github.

git clone git://github.com/chrislloyd/coinage.git

If you submit a successful patch then you’ll be given full commit rights to the project.

Bugs

Bug tracking is handled by Ditz.

License

Copyright © 2008 Chris Lloyd.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.