bai-money 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
@@ -0,0 +1,87 @@
1
+ This library aids one in handling money and different currencies. Features:
2
+
3
+ * Provides a Money class which encapsulates all information about an certain amount of money, such as its value and its currency.
4
+ * Represents monetary values as integers, in cents. This avoids floating point rounding errors.
5
+ * Provides APIs for exchanging money from one currency to another.
6
+ * Has the ability to parse a money string into a Money object.
7
+
8
+ ### Note on Patches/Pull Requests
9
+
10
+ * Fork the project.
11
+ * Make your feature addition or bug fix.
12
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
14
+ * Send me a pull request. Bonus points for topic branches.
15
+
16
+ ### Usage
17
+
18
+ #### Synopsis
19
+
20
+ require 'money'
21
+
22
+ # 10.00 USD
23
+ money = Money.new(1000, "USD")
24
+ money.cents # => 1000
25
+ money.currency # => "USD"
26
+
27
+ Money.new(1000, "USD") == Money.new(1000, "USD") # => true
28
+ Money.new(1000, "USD") == Money.new(100, "USD") # => false
29
+ Money.new(1000, "USD") == Money.new(1000, "EUR") # => false
30
+
31
+ #### Currency Exchange
32
+
33
+ Exchanging money is performed through an exchange bank object. The default exchange bank object requires one to manually specify the exchange rate. Here’s an example of how it works:
34
+
35
+ Money.add_rate("USD", "CAD", 1.24515)
36
+ Money.add_rate("CAD", "USD", 0.803115)
37
+
38
+ Money.new(100, "USD").exchange_to("CAD") # => Money.new(124, "CAD")
39
+ Money.new(100, "CAD").exchange_to("USD") # => Money.new(80, "USD")
40
+
41
+ Comparison and arithmetic operations work as expected:
42
+
43
+ Money.new(1000, "USD") <=> Money.new(900, "USD") # => 1; 9.00 USD is smaller
44
+ Money.new(1000, "EUR") + Money.new(10, "EUR") == Money.new(1010, "EUR")
45
+
46
+ Money.add_rate("USD", "EUR", 0.5)
47
+ Money.new(1000, "EUR") + Money.new(1000, "USD") == Money.new(1500, "EUR")
48
+
49
+ There is nothing stopping you from creating bank objects which scrapes www.xe.com for the current rates or just returns rand(2):
50
+
51
+ Money.default_bank = ExchangeBankWhichScrapesXeDotCom.new
52
+
53
+ #### Ruby on Rails
54
+
55
+ Use the compose_of helper to let Active Record deal with embedding the money object in your models. The following example requires a cents and a currency field.
56
+
57
+ class ProductUnit < ActiveRecord::Base
58
+ belongs_to :product
59
+ composed_of :price, :class_name => "Money", :mapping => [%w(cents cents), %w(currency currency)]
60
+
61
+ private
62
+ validate :cents_not_zero
63
+
64
+ def cents_not_zero
65
+ errors.add("cents", "cannot be zero or less") unless cents > 0
66
+ end
67
+
68
+ validates_presence_of :sku, :currency
69
+ validates_uniqueness_of :sku
70
+ end
71
+
72
+ #### Default Currency
73
+
74
+ By default Money defaults to USD as its currency. This can be overwritten using
75
+
76
+ Money.default_currency = "CAD"
77
+
78
+ If you use Rails, then initializer is a very good place to put this.
79
+
80
+ ### Credits & License
81
+
82
+ Highly based on:
83
+
84
+ * http://github.com/FooBarWidget/money
85
+ * http://github.com/collectiveidea/money
86
+
87
+ This library inherits license of FooBarWidget-money.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
2
+ require 'money/money'
3
+ require 'money/symbols'
4
+ require 'money/core_extensions'
@@ -0,0 +1,136 @@
1
+ class Numeric
2
+ # Converts this numeric to a Money object in the default currency. It
3
+ # multiplies the numeric value by 100 and treats that as cents.
4
+ #
5
+ # 100.to_money => #<Money @cents=10000>
6
+ # 100.37.to_money => #<Money @cents=10037>
7
+ def to_money
8
+ Money.new(self * 100)
9
+ end
10
+ end
11
+
12
+ class String
13
+ # Parses the current string and converts it to a Money object.
14
+ # Excess characters will be discarded.
15
+ #
16
+ # '100'.to_money # => #<Money @cents=10000>
17
+ # '100.37'.to_money # => #<Money @cents=10037>
18
+ # '100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
19
+ # 'USD 100'.to_money # => #<Money @cents=10000, @currency="USD">
20
+ # '$100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
21
+ # 'hello 2000 world'.to_money # => #<Money @cents=200000 @currency="USD")>
22
+ def to_money
23
+ # Get the currency.
24
+ matches = scan /([A-Z]{2,3})/
25
+ currency = matches[0] ? matches[0][0] : Money.default_currency
26
+ cents = calculate_cents(self)
27
+ Money.new(cents, currency)
28
+ end
29
+
30
+ private
31
+ def calculate_cents(number)
32
+ # remove anything that's not a number, potential delimiter, or minus sign
33
+ num = number.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip
34
+
35
+ # set a boolean flag for if the number is negative or not
36
+ negative = num.split(//).first == "-"
37
+
38
+ # if negative, remove the minus sign from the number
39
+ num = num.gsub(/^-/, '') if negative
40
+
41
+ # gather all separators within the result number
42
+ used_separators = num.scan /[^\d]/
43
+
44
+ # determine the number of unique separators within the number
45
+ #
46
+ # e.g.
47
+ # $1,234,567.89 would return 2 (, and .)
48
+ # $125,00 would return 1
49
+ # $199 would return 0
50
+ # $1 234,567.89 would raise an error (separators are space, comma, and period)
51
+ case used_separators.uniq.length
52
+ # no separator or delimiter; major (dollars) is the number, and minor (cents) is 0
53
+ when 0 then major, minor = num, 0
54
+
55
+ # two separators, so we know the last item in this array is the
56
+ # major/minor delimiter and the rest are separators
57
+ when 2
58
+ separator, delimiter = used_separators.uniq
59
+ # remove all separators, split on the delimiter
60
+ major, minor = num.gsub(separator, '').split(delimiter)
61
+ min = 0 unless min
62
+ when 1
63
+ # we can't determine if the comma or period is supposed to be a separator or a delimiter
64
+ # e.g.
65
+ # 1,00 - comma is a delimiter
66
+ # 1.000 - period is a delimiter
67
+ # 1,000 - comma is a separator
68
+ # 1,000,000 - comma is a separator
69
+ # 10000,00 - comma is a delimiter
70
+ # 1000,000 - comma is a delimiter
71
+
72
+ # assign first separator for reusability
73
+ separator = used_separators.first
74
+
75
+ # separator is used as a separator when there are multiple instances, always
76
+ if num.scan(separator).length > 1 # multiple matches; treat as separator
77
+ major, minor = num.gsub(separator, ''), 0
78
+ else
79
+ # ex: 1,000 - 1.0000 - 10001.000
80
+ # split number into possible major (dollars) and minor (cents) values
81
+ possible_major, possible_minor = num.split(separator)
82
+ possible_major ||= "0"
83
+ possible_minor ||= "00"
84
+
85
+ # if the minor (cents) length isn't 3, assign major/minor from the possibles
86
+ # e.g.
87
+ # 1,00 => 1.00
88
+ # 1.0000 => 1.00
89
+ # 1.2 => 1.20
90
+ if possible_minor.length != 3 # delimiter
91
+ major, minor = possible_major, possible_minor
92
+ else
93
+ # minor length is three
94
+ # let's try to figure out intent of the delimiter
95
+
96
+ # the major length is greater than three, which means
97
+ # the comma or period is used as a delimiter
98
+ # e.g.
99
+ # 1000,000
100
+ # 100000,000
101
+ if possible_major.length > 3
102
+ major, minor = possible_major, possible_minor
103
+ else
104
+ # number is in format ###{sep}### or ##{sep}### or #{sep}###
105
+ # handle as , is sep, . is delimiter
106
+ if separator == '.'
107
+ major, minor = possible_major, possible_minor
108
+ else
109
+ major, minor = "#{possible_major}#{possible_minor}", 0
110
+ end
111
+ end
112
+ end
113
+ end
114
+ else
115
+ raise ArgumentError, "Invalid currency amount"
116
+ end
117
+
118
+ # build the string based on major/minor since separator/delimiters have been removed
119
+ # avoiding floating point arithmetic here to ensure accuracy
120
+ cents = (major.to_i * 100)
121
+ # add the minor number as well. this may have any number of digits,
122
+ # so we treat minor as a string and truncate or right-fill it with zeroes
123
+ # until it becomes a two-digit number string, which we add to cents.
124
+ minor = minor.to_s
125
+ truncated_minor = minor[0..1]
126
+ truncated_minor << "0" * (2 - truncated_minor.size) if truncated_minor.size < 2
127
+ cents += truncated_minor.to_i
128
+ # respect rounding rules
129
+ if minor.size >= 3 && minor[2..2].to_i >= 5
130
+ cents += 1
131
+ end
132
+
133
+ # if negative, multiply by -1; otherwise, return positive cents
134
+ negative ? cents * -1 : cents
135
+ end
136
+ end
@@ -0,0 +1,4 @@
1
+ class Money
2
+ class UnknownRate < StandardError # :nodoc:
3
+ end
4
+ end
@@ -0,0 +1,252 @@
1
+ require 'money/variable_exchange_bank'
2
+
3
+ # Represents an amount of money in a certain currency.
4
+ class Money
5
+ include Comparable
6
+
7
+ attr_reader :cents, :currency, :bank
8
+
9
+ class << self
10
+ # Each Money object is associated to a bank object, which is responsible
11
+ # for currency exchange. This property allows one to specify the default
12
+ # bank object.
13
+ #
14
+ # bank1 = MyBank.new
15
+ # bank2 = MyOtherBank.new
16
+ #
17
+ # Money.default_bank = bank1
18
+ # money1 = Money.new(10)
19
+ # money1.bank # => bank1
20
+ #
21
+ # Money.default_bank = bank2
22
+ # money2 = Money.new(10)
23
+ # money2.bank # => bank2
24
+ # money1.bank # => bank1
25
+ #
26
+ # The default value for this property is an instance if VariableExchangeBank.
27
+ # It allows one to specify custom exchange rates:
28
+ #
29
+ # Money.default_bank.add_rate("USD", "CAD", 1.24515)
30
+ # Money.default_bank.add_rate("CAD", "USD", 0.803115)
31
+ # Money.new(100, "USD").exchange_to("CAD") # => Money.new(124, "CAD")
32
+ # Money.new(100, "CAD").exchange_to("USD") # => Money.new(80, "USD")
33
+ attr_accessor :default_bank
34
+
35
+ # The default currency, which is used when <tt>Money.new</tt> is called
36
+ # without an explicit currency argument. The default value is "USD".
37
+ attr_accessor :default_currency
38
+ end
39
+
40
+ self.default_bank = VariableExchangeBank.instance
41
+ self.default_currency = "USD"
42
+
43
+ # Create a new money object with value 0.
44
+ def self.empty(currency = default_currency)
45
+ Money.new(0, currency)
46
+ end
47
+
48
+ def self.add_rate(from_currency, to_currency, rate)
49
+ Money.default_bank.add_rate(from_currency, to_currency, rate)
50
+ end
51
+
52
+ # Creates a new money object.
53
+ # Money.new(100)
54
+ #
55
+ # Alternativly you can use the convinience methods like
56
+ # Money.ca_dollar and Money.us_dollar
57
+ def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
58
+ @cents = cents.round
59
+ @currency = currency
60
+ @bank = bank
61
+ end
62
+
63
+ # Do two money objects equal? Only works if both objects are of the same currency
64
+ def ==(other_money)
65
+ cents == other_money.cents && bank.same_currency?(currency, other_money.currency)
66
+ end
67
+
68
+ def <=>(other_money)
69
+ if bank.same_currency?(currency, other_money.currency)
70
+ cents <=> other_money.cents
71
+ else
72
+ cents <=> other_money.exchange_to(currency).cents
73
+ end
74
+ end
75
+
76
+ def +(other_money)
77
+ if currency == other_money.currency
78
+ Money.new(cents + other_money.cents, other_money.currency)
79
+ else
80
+ Money.new(cents + other_money.exchange_to(currency).cents,currency)
81
+ end
82
+ end
83
+
84
+ def -(other_money)
85
+ if currency == other_money.currency
86
+ Money.new(cents - other_money.cents, other_money.currency)
87
+ else
88
+ Money.new(cents - other_money.exchange_to(currency).cents, currency)
89
+ end
90
+ end
91
+
92
+ # Get the cents value of the object
93
+ def cents
94
+ @cents
95
+ end
96
+
97
+ # Multiply money by fixnum
98
+ def *(fixnum)
99
+ Money.new(cents * fixnum, currency)
100
+ end
101
+
102
+ # Divide money by fixnum
103
+ def /(fixnum)
104
+ Money.new(cents / fixnum, currency)
105
+ end
106
+
107
+ # Test if the money amount is zero
108
+ def zero?
109
+ cents == 0
110
+ end
111
+
112
+ # Creates a formatted price string according to several rules. The following
113
+ # options are supported: :display_free, :with_currency, :no_cents, :symbol
114
+ # and :html.
115
+ #
116
+ # === +:display_free+
117
+ #
118
+ # Whether a zero amount of money should be formatted of "free" or as the
119
+ # supplied string.
120
+ #
121
+ # Money.us_dollar(0).format(:display_free => true) => "free"
122
+ # Money.us_dollar(0).format(:display_free => "gratis") => "gratis"
123
+ # Money.us_dollar(0).format => "$0.00"
124
+ #
125
+ # === +:with_currency+
126
+ #
127
+ # Whether the currency name should be appended to the result string.
128
+ #
129
+ # Money.ca_dollar(100).format => "$1.00"
130
+ # Money.ca_dollar(100).format(:with_currency => true) => "$1.00 CAD"
131
+ # Money.us_dollar(85).format(:with_currency => true) => "$0.85 USD"
132
+ #
133
+ # === +:no_cents+
134
+ #
135
+ # Whether cents should be omitted.
136
+ #
137
+ # Money.ca_dollar(100).format(:no_cents => true) => "$1"
138
+ # Money.ca_dollar(599).format(:no_cents => true) => "$5"
139
+ #
140
+ # Money.ca_dollar(570).format(:no_cents => true, :with_currency => true) => "$5 CAD"
141
+ # Money.ca_dollar(39000).format(:no_cents => true) => "$390"
142
+ #
143
+ # === +:symbol+
144
+ #
145
+ # Whether a money symbol should be prepended to the result string. The default is true.
146
+ # This method attempts to pick a symbol that's suitable for the given currency.
147
+ #
148
+ # Money.new(100, :currency => "USD") => "$1.00"
149
+ # Money.new(100, :currency => "GBP") => "£1.00"
150
+ # Money.new(100, :currency => "EUR") => "€1.00"
151
+ #
152
+ # # Same thing.
153
+ # Money.new(100, :currency => "USD").format(:symbol => true) => "$1.00"
154
+ # Money.new(100, :currency => "GBP").format(:symbol => true) => "£1.00"
155
+ # Money.new(100, :currency => "EUR").format(:symbol => true) => "€1.00"
156
+ #
157
+ # You can specify a false expression or an empty string to disable prepending
158
+ # a money symbol:
159
+ #
160
+ # Money.new(100, :currency => "USD").format(:symbol => false) => "1.00"
161
+ # Money.new(100, :currency => "GBP").format(:symbol => nil) => "1.00"
162
+ # Money.new(100, :currency => "EUR").format(:symbol => "") => "1.00"
163
+ #
164
+ #
165
+ # If the symbol for the given currency isn't known, then it will default
166
+ # to "$" as symbol:
167
+ #
168
+ # Money.new(100, :currency => "AWG").format(:symbol => true) => "$1.00"
169
+ #
170
+ # You can specify a string as value to enforce using a particular symbol:
171
+ #
172
+ # Money.new(100, :currency => "AWG").format(:symbol => "ƒ") => "ƒ1.00"
173
+ #
174
+ # === +:html+
175
+ #
176
+ # Whether the currency should be HTML-formatted. Only useful in combination with +:with_currency+.
177
+ #
178
+ # Money.ca_dollar(570).format(:html => true, :with_currency => true)
179
+ # => "$5.70 <span class=\"currency\">CAD</span>"
180
+ def format(*rules)
181
+ # support for old format parameters
182
+ rules = normalize_formatting_rules(rules)
183
+
184
+ if cents == 0
185
+ if rules[:display_free].respond_to?(:to_str)
186
+ return rules[:display_free]
187
+ elsif rules[:display_free]
188
+ return "free"
189
+ end
190
+ end
191
+
192
+ if rules.has_key?(:symbol)
193
+ if rules[:symbol] === true
194
+ symbol = SYMBOLS[currency[:currency]] || "$"
195
+ elsif rules[:symbol]
196
+ symbol = rules[:symbol]
197
+ else
198
+ symbol = ""
199
+ end
200
+ else
201
+ symbol = "$"
202
+ end
203
+
204
+ if rules[:no_cents]
205
+ formatted = sprintf("#{symbol}%d", cents.to_f / 100)
206
+ else
207
+ formatted = sprintf("#{symbol}%.2f", cents.to_f / 100)
208
+ end
209
+
210
+ # Commify ("10000" => "10,000")
211
+ formatted.gsub!(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2')
212
+
213
+ if rules[:with_currency]
214
+ formatted << " "
215
+ formatted << '<span class="currency">' if rules[:html]
216
+ formatted << currency
217
+ formatted << '</span>' if rules[:html]
218
+ end
219
+ formatted
220
+ end
221
+
222
+ # Returns the amount of money as a string.
223
+ #
224
+ # Money.ca_dollar(100).to_s => "1.00"
225
+ def to_s
226
+ sprintf("%.2f", cents / 100.00)
227
+ end
228
+
229
+ # Recieve the amount of this money object in another currency.
230
+ def exchange_to(other_currency)
231
+ Money.new(@bank.exchange(self.cents, currency, other_currency), other_currency)
232
+ end
233
+
234
+ # Conversation to self
235
+ def to_money
236
+ self
237
+ end
238
+
239
+ private
240
+ def normalize_formatting_rules(rules)
241
+ if rules.size == 1
242
+ rules = rules.pop
243
+ rules = { rules => true } if rules.is_a?(Symbol)
244
+ else
245
+ rules = rules.inject({}) do |h,s|
246
+ h[s] = true
247
+ h
248
+ end
249
+ end
250
+ rules
251
+ end
252
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ # Add more from http://www.xe.com/symbols.php
4
+ Money::SYMBOLS = {
5
+ "GBP" => "£",
6
+ "JPY" => "¥",
7
+ "EUR" => "€",
8
+ "ZWD" => "Z$",
9
+ "CNY" => "¥",
10
+ "INR" => "₨",
11
+ "NPR" => "₨",
12
+ "SCR" => "₨",
13
+ "LKR" => "₨",
14
+ "SEK" => "kr",
15
+ "GHC" => "¢"
16
+
17
+ # Everything else defaults to $
18
+ }
@@ -0,0 +1,72 @@
1
+ require 'thread'
2
+ require 'money/errors'
3
+
4
+ # Class for aiding in exchanging money between different currencies.
5
+ # By default, the Money class uses an object of this class (accessible through
6
+ # Money#bank) for performing currency exchanges.
7
+ #
8
+ # By default, VariableExchangeBank has no knowledge about conversion rates.
9
+ # One must manually specify them with +add_rate+, after which one can perform
10
+ # exchanges with +exchange+. For example:
11
+ #
12
+ # bank = Money::VariableExchangeBank.new
13
+ # bank.add_rate("USD", "CAD", 1.24515)
14
+ # bank.add_rate("CAD", "USD", 0.803115)
15
+ #
16
+ # # Exchange 100 CAD to USD:
17
+ # bank.exchange(100_00, "CAD", "USD") # => 124
18
+ # # Exchange 100 USD to CAD:
19
+ # bank.exchange(100_00, "USD", "CAD") # => 80
20
+ class Money
21
+ class VariableExchangeBank
22
+ # Returns the singleton instance of VariableExchangeBank.
23
+ #
24
+ # By default, <tt>Money.default_bank</tt> returns the same object.
25
+ def self.instance
26
+ @@singleton
27
+ end
28
+
29
+ def initialize
30
+ @rates = {}
31
+ @mutex = Mutex.new
32
+ end
33
+
34
+ # Registers a conversion rate. +from+ and +to+ are both currency names.
35
+ def add_rate(from, to, rate)
36
+ @mutex.synchronize do
37
+ @rates["#{from}_TO_#{to}".upcase] = rate
38
+ end
39
+ end
40
+
41
+ # Gets the rate for exchanging the currency named +from+ to the currency
42
+ # named +to+. Returns nil if the rate is unknown.
43
+ def get_rate(from, to)
44
+ @mutex.synchronize do
45
+ @rates["#{from}_TO_#{to}".upcase]
46
+ end
47
+ end
48
+
49
+ # Given two currency names, checks whether they're both the same currency.
50
+ #
51
+ # bank = VariableExchangeBank.new
52
+ # bank.same_currency?("usd", "USD") # => true
53
+ # bank.same_currency?("usd", "EUR") # => false
54
+ def same_currency?(currency1, currency2)
55
+ currency1.upcase == currency2.upcase
56
+ end
57
+
58
+ # Exchange the given amount of cents in +from_currency+ to +to_currency+.
59
+ # Returns the amount of cents in +to_currency+ as an integer, rounded down.
60
+ #
61
+ # If the conversion rate is unknown, then Money::UnknownRate will be raised.
62
+ def exchange(cents, from_currency, to_currency)
63
+ rate = get_rate(from_currency, to_currency)
64
+ if !rate
65
+ raise Money::UnknownRate, "No conversion rate known for '#{from_currency}' -> '#{to_currency}'"
66
+ end
67
+ (cents * rate).floor
68
+ end
69
+
70
+ @@singleton = VariableExchangeBank.new
71
+ end
72
+ end
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{money}
5
+ s.version = "0.5.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["beawesomeinstead"]
9
+ s.date = %q{2009-08-04}
10
+ s.description = %q{Library for dealing with money and currency conversion.}
11
+ s.email = %q{beawesomeinstead@yahoo.com}
12
+ s.extra_rdoc_files = [
13
+ "README.markdown"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "README.markdown",
18
+ "VERSION",
19
+ "lib/money.rb",
20
+ "lib/money/core_extensions.rb",
21
+ "lib/money/errors.rb",
22
+ "lib/money/money.rb",
23
+ "lib/money/symbols.rb",
24
+ "lib/money/variable_exchange_bank.rb",
25
+ "money.gemspec",
26
+ "rakefile",
27
+ "test/core_extensions_test.rb",
28
+ "test/money_test.rb",
29
+ "test/test_helper.rb",
30
+ "test/variable_exchange_bank_test.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/bai/money}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.3.5}
36
+ s.summary = %q{Library for dealing with money and currency conversion.}
37
+ s.test_files = [
38
+ "test/variable_exchange_bank_test.rb",
39
+ "test/money_test.rb",
40
+ "test/test_helper.rb",
41
+ "test/core_extensions_test.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ else
50
+ end
51
+ else
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "money"
8
+ gem.summary = %Q{Library for dealing with money and currency conversion.}
9
+ gem.description = %Q{Library for dealing with money and currency conversion.}
10
+ gem.email = "beawesomeinstead@yahoo.com"
11
+ gem.homepage = "http://github.com/bai/money"
12
+ gem.authors = ["beawesomeinstead"]
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
16
+ end
17
+
18
+ require 'rake/testtask'
19
+ Rake::TestTask.new(:test) do |test|
20
+ test.libs << 'lib' << 'test'
21
+ test.pattern = 'test/**/*_test.rb'
22
+ test.verbose = true
23
+ end
24
+
25
+ begin
26
+ require 'rcov/rcovtask'
27
+ Rcov::RcovTask.new do |test|
28
+ test.libs << 'test'
29
+ test.pattern = 'test/**/*_test.rb'
30
+ test.verbose = true
31
+ end
32
+ rescue LoadError
33
+ task :rcov do
34
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
35
+ end
36
+ end
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ if File.exist?('VERSION.yml')
41
+ config = YAML.load(File.read('VERSION.yml'))
42
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
43
+ elsif File.exist?('VERSION')
44
+ version = File.read('VERSION')
45
+ else
46
+ version = ""
47
+ end
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "money #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
@@ -0,0 +1,74 @@
1
+ require 'test_helper'
2
+
3
+ class CoreExtensionsTest < Test::Unit::TestCase
4
+ context "Money core extensions" do
5
+ should "transform Numberic to Money with Numberic#to_money" do
6
+ money = 1234.to_money
7
+ assert_equal 1234_00, money.cents
8
+ assert_equal Money.default_currency, money.currency
9
+
10
+ money = 100.37.to_money
11
+ assert_equal 100_37, money.cents
12
+ assert_equal Money.default_currency, money.currency
13
+ end
14
+
15
+ should "transform String to Money with String#to_money" do
16
+ assert_equal Money.new(20_15), "20.15".to_money
17
+ assert_equal Money.new(100_00), "100".to_money
18
+ assert_equal Money.new(100_37), "100.37".to_money
19
+ assert_equal Money.new(100_37), "100,37".to_money
20
+ assert_equal Money.new(100_000_00), "100 000".to_money
21
+ assert_equal Money.new(100_000_00), "100,000.00".to_money
22
+ assert_equal Money.new(1_000_00), "1,000".to_money
23
+ assert_equal Money.new(-1_000_00), "-1,000".to_money
24
+ assert_equal Money.new(1_000_50), "1,000.5".to_money
25
+ assert_equal Money.new(1_000_51), "1,000.51".to_money
26
+ assert_equal Money.new(1_000_51), "1,000.505".to_money
27
+ assert_equal Money.new(1_000_50), "1,000.504".to_money
28
+ assert_equal Money.new(1_000_00), "1,000.0000".to_money
29
+ assert_equal Money.new(1_000_50), "1,000.5000".to_money
30
+ assert_equal Money.new(1_000_51), "1,000.5099".to_money
31
+ assert_equal Money.new(1_55), "1.550".to_money
32
+ assert_equal Money.new(25_00), "25.".to_money
33
+ assert_equal Money.new(75), ".75".to_money
34
+
35
+ assert_equal Money.new(100_00, "USD"), "100 USD".to_money
36
+ assert_equal Money.new(-100_00, "USD"), "-100 USD".to_money
37
+ assert_equal Money.new(100_00, "EUR"), "100 EUR".to_money
38
+ assert_equal Money.new(100_37, "EUR"), "100.37 EUR".to_money
39
+ assert_equal Money.new(100_37, "EUR"), "100,37 EUR".to_money
40
+ assert_equal Money.new(100_000_00, "USD"), "100,000.00 USD".to_money
41
+ assert_equal Money.new(100_000_00, "EUR"), "100.000,00 EUR".to_money
42
+ assert_equal Money.new(1_000_00, "USD"), "1,000 USD".to_money
43
+ assert_equal Money.new(-1_000_00, "USD"), "-1,000 USD".to_money
44
+ assert_equal Money.new(1_000_55, "USD"), "1,000.5500 USD".to_money
45
+ assert_equal Money.new(-1_000_65, "USD"), "-1,000.6500 USD".to_money
46
+ assert_equal Money.new(1_55, "USD"), "1.550 USD".to_money
47
+
48
+ assert_equal Money.new(100_00, "USD"), "USD 100".to_money
49
+ assert_equal Money.new(100_00, "EUR"), "EUR 100".to_money
50
+ assert_equal Money.new(100_37, "EUR"), "EUR 100.37".to_money
51
+ assert_equal Money.new(-100_37, "CAD"), "CAD -100.37".to_money
52
+ assert_equal Money.new(100_37, "EUR"), "EUR 100,37".to_money
53
+ assert_equal Money.new(-100_37, "EUR"), "EUR -100,37".to_money
54
+ assert_equal Money.new(100_000_00, "USD"), "USD 100,000.00".to_money
55
+ assert_equal Money.new(100_000_00, "EUR"), "EUR 100.000,00".to_money
56
+ assert_equal Money.new(1_000_00, "USD"), "USD 1,000".to_money
57
+ assert_equal Money.new(-1_000_00, "USD"), "USD -1,000".to_money
58
+ assert_equal Money.new(1_000_90, "USD"), "USD 1,000.9000".to_money
59
+ assert_equal Money.new(-1_000_09, "USD"), "USD -1,000.090".to_money
60
+ assert_equal Money.new(1_55, "USD"), "USD 1.5500".to_money
61
+
62
+ assert_equal Money.new(100_00, "USD"), "$100 USD".to_money
63
+ assert_equal Money.new(1_194_59, "USD"), "$1,194.59 USD".to_money
64
+ assert_equal Money.new(-1_955_00, "USD"), "$-1,955 USD".to_money
65
+ assert_equal Money.new(1_194_59, "USD"), "$1,194.5900 USD".to_money
66
+ assert_equal Money.new(-1_955_00, "USD"), "$-1,955.000 USD".to_money
67
+ assert_equal Money.new(1_99, "USD"), "$1.99000 USD".to_money
68
+ end
69
+
70
+ should "ignore unrecognized data" do
71
+ assert_equal Money.new(2000_00), "hello 2000 world".to_money
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,215 @@
1
+ require 'test_helper'
2
+
3
+ class MoneyTest < Test::Unit::TestCase
4
+ context "Money" do
5
+ should "be associated to the singleton instance of VariableExchangeBank by default" do
6
+ money_bank_object_id = Money.new(0).bank.object_id
7
+ bank_instance_object_id = Money::VariableExchangeBank.instance.object_id
8
+ assert_equal bank_instance_object_id, money_bank_object_id
9
+ end
10
+
11
+ should "round the given cents to an integer" do
12
+ assert_equal 1, Money.new(1.00, "USD").cents
13
+ assert_equal 1, Money.new(1.01, "USD").cents
14
+ assert_equal 2, Money.new(1.50, "USD").cents
15
+ end
16
+
17
+ should "have 0 cents of default currency when created with Money.empty" do
18
+ assert_equal Money.new(0), Money.empty
19
+ end
20
+
21
+ should "add new currency rates" do
22
+ Money.add_rate("EUR", "USD", 10)
23
+
24
+ assert_equal Money.new(100_00, "USD"), Money.new(10_00, "EUR").exchange_to("USD")
25
+ end
26
+
27
+ should "return the amount of cents passed to the constructor with #cents" do
28
+ assert_equal 200_00, Money.new(200_00, "USD").cents
29
+ end
30
+
31
+ should "return the currency passed to the constructor with #currency" do
32
+ assert_equal "USD", Money.new(200_00, "USD").currency
33
+ end
34
+
35
+ should "return whether the amount is 0 with #zero?" do
36
+ assert_equal true, Money.new(0, "USD").zero?
37
+ assert_equal true, Money.new(0, "EUR").zero?
38
+ assert_equal false, Money.new(1, "USD").zero?
39
+ assert_equal false, Money.new(10, "YEN").zero?
40
+ assert_equal false, Money.new(-1, "EUR").zero?
41
+ end
42
+
43
+ should "exchange the amount via its exchange bank and do it properly with #exchange_to" do
44
+ Money.add_rate("USD", "EUR", 10)
45
+
46
+ money = Money.new(100_00, "USD")
47
+ assert_equal Money.new(1000_00, "EUR"), money.exchange_to("EUR")
48
+ end
49
+
50
+ should "return true if and only if their amount and currency are equal with #==" do
51
+ assert_equal Money.new(1_00, "USD"), Money.new(1_00, "USD")
52
+ assert_not_equal Money.new(1_00, "USD"), Money.new(1_00, "EUR")
53
+ assert_not_equal Money.new(1_00, "USD"), Money.new(2_00, "USD")
54
+ assert_not_equal Money.new(1_00, "USD"), Money.new(99_00, "EUR")
55
+ end
56
+
57
+ should "multiply the money's amount by the multiplier while retaining the currency with #*" do
58
+ assert_equal Money.new(10_00, "USD"), Money.new(1_00, "USD") * 10
59
+ end
60
+
61
+ should "divide the money's amount by the divisor while retaining the currency with #/" do
62
+ assert_equal Money.new(1_00, "USD"), Money.new(10_00, "USD") / 10
63
+ end
64
+
65
+ context "#format" do
66
+ should "return the monetary value as a string" do
67
+ assert_equal "$1.00", Money.new(100, "CAD").format
68
+ end
69
+
70
+ context "if the monetary value is 0" do
71
+ setup do
72
+ @money = Money.new(0, "USD")
73
+ end
74
+
75
+ should "return 'free' when :display_free is true" do
76
+ assert_equal 'free', @money.format(:display_free => true)
77
+ end
78
+
79
+ should "return '$0.00' when :display_free is false or not given" do
80
+ assert_equal '$0.00', @money.format
81
+ assert_equal '$0.00', @money.format(:display_free => false)
82
+ assert_equal '$0.00', @money.format(:display_free => nil)
83
+ end
84
+
85
+ should "return the value specified by :display_free if it's a string-like object" do
86
+ assert_equal 'gratis', @money.format(:display_free => 'gratis')
87
+ end
88
+ end
89
+
90
+ should "work as documented when :with_currency option is true" do
91
+ assert_equal "$1.00 CAD", Money.new(100, "CAD").format(:with_currency => true)
92
+ assert_equal "$0.85 USD", Money.new(85, "USD").format(:with_currency => true)
93
+ end
94
+
95
+ should "work as documented when :with_currency option is present" do
96
+ assert_equal "$1.00 CAD", Money.new(100, "CAD").format(:with_currency)
97
+ assert_equal "$0.85 USD", Money.new(85, "USD").format(:with_currency)
98
+ end
99
+
100
+ should "work as documented when :no_cents option is true" do
101
+ assert_equal "$1", Money.new(100, "CAD").format(:no_cents => true)
102
+ assert_equal "$5", Money.new(599, "CAD").format(:no_cents => true)
103
+ assert_equal "$5 CAD", Money.new(570, "CAD").format(:no_cents => true, :with_currency => true)
104
+ assert_equal "$390", Money.new(39000, "CAD").format(:no_cents => true)
105
+ end
106
+
107
+ should "work as documented when :no_cents option is present" do
108
+ assert_equal "$1", Money.new(100, "CAD").format(:no_cents)
109
+ assert_equal "$5", Money.new(599, "CAD").format(:no_cents)
110
+ assert_equal "$5 CAD", Money.new(570, "CAD").format(:no_cents, :with_currency)
111
+ assert_equal "$390", Money.new(39000, "CAD").format(:no_cents)
112
+ end
113
+
114
+ should "use a given :symbol value as the money symbol" do
115
+ assert_equal "£1.00", Money.new(100, :currency => "GBP").format(:symbol => "£")
116
+ end
117
+
118
+ should "return symbol based on the given currency code when :symbol option is true" do #!!!!!
119
+ one = Proc.new { |currency| Money.new(100, :currency => currency).format(:symbol => true) }
120
+
121
+ # Pounds
122
+ assert_equal "£1.00", one["GBP"]
123
+
124
+ # Dollars
125
+ assert_equal "$1.00", one["USD"]
126
+ assert_equal "$1.00", one["CAD"]
127
+ assert_equal "$1.00", one["AUD"]
128
+ assert_equal "$1.00", one["NZD"]
129
+ assert_equal "Z$1.00", one["ZWD"]
130
+
131
+ # Yen
132
+ assert_equal "¥1.00", one["JPY"]
133
+ assert_equal "¥1.00", one["CNY"]
134
+
135
+ # Euro
136
+ assert_equal "€1.00", one["EUR"]
137
+
138
+ # Rupees
139
+ assert_equal "₨1.00", one["INR"]
140
+ assert_equal "₨1.00", one["NPR"]
141
+ assert_equal "₨1.00", one["SCR"]
142
+ assert_equal "₨1.00", one["LKR"]
143
+
144
+ # Other
145
+ assert_equal "kr1.00", one["SEK"]
146
+ assert_equal "¢1.00", one["GHC"]
147
+ end
148
+
149
+ should "return $ when currency code is not recognized and :symbol option is true" do
150
+ assert_equal "$1.00", Money.new(100, :currency => "XYZ").format(:symbol => true)
151
+ end
152
+
153
+ should "return symbol based on the given currency code when :symbol option is some non-Boolean value that evaluates to true" do
154
+ assert_equal "£1.00", Money.new(100, :currency => "GBP").format(:symbol => true) #!!!!
155
+ assert_equal "€1.00", Money.new(100, :currency => "EUR").format(:symbol => true)
156
+ assert_equal "kr1.00", Money.new(100, :currency => "SEK").format(:symbol => true)
157
+ end
158
+
159
+ should "return the amount without a symbol when :symbol option is "", nil or false" do
160
+ money = Money.new(100, :currency => "GBP")
161
+ assert_equal "1.00", money.format(:symbol => "")
162
+ assert_equal "1.00", money.format(:symbol => nil)
163
+ assert_equal "1.00", money.format(:symbol => false)
164
+ end
165
+
166
+ should "work as documented when :html option is true" do
167
+ string = Money.new(570, "CAD").format(:html => true, :with_currency => true)
168
+ assert_equal "$5.70 <span class=\"currency\">CAD</span>", string
169
+ end
170
+
171
+ should "insert commas into the result if the amount is sufficiently large" do
172
+ assert_equal "$1,000,000,000.12", Money.new(1_000_000_000_12, "USD").format
173
+ assert_equal "$1,000,000,000", Money.new(1_000_000_000_12, "USD").format(:no_cents => true)
174
+ end
175
+ end
176
+ end
177
+
178
+ context "Actions involving two Money objects" do
179
+ context "if the other Money object has the same currency" do
180
+ should "compare the two objects' amounts with #<=>" do
181
+ assert_operator 0, :>, Money.new(1_00, "USD") <=> Money.new(2_00, "USD")
182
+ assert_operator 0, :==, Money.new(1_00, "USD") <=> Money.new(1_00, "USD")
183
+ assert_operator 0, :<, Money.new(1_00, "USD") <=> Money.new(99, "USD")
184
+ end
185
+
186
+ should "add the other object's amount to the current object's amount while retaining the currency with #+" do
187
+ assert_equal Money.new(10_90, "USD"), Money.new(10_00, "USD") + Money.new(90, "USD")
188
+ end
189
+
190
+ should "substract the other object's amount from the current object's amount while retaining the currency with #-" do
191
+ assert_equal Money.new(9_10, "USD"), Money.new(10_00, "USD") - Money.new(90, "USD")
192
+ end
193
+ end
194
+
195
+ context "if the other Money object has a different currency" do
196
+ setup do
197
+ Money.add_rate("EUR", "USD", 10)
198
+ end
199
+
200
+ should "compare the two objects' amount after converting the other object's amount to its own currency with #<=>" do
201
+ assert_operator 0, :>, Money.new(100_00, "USD") <=> Money.new(11_00, "EUR")
202
+ assert_operator 0, :==, Money.new(100_00, "USD") <=> Money.new(10_00, "EUR")
203
+ assert_operator 0, :<, Money.new(100_00, "USD") <=> Money.new(5_00, "EUR")
204
+ end
205
+
206
+ should "add the other object's amount, converted to this object's currency, to this object's amount while retaining its currency with #+" do
207
+ assert_equal Money.new(150_00, "USD"), Money.new(100_00, "USD") + Money.new(5_00, "EUR")
208
+ end
209
+
210
+ should "substract the other object's amount, converted to this object's currency, to this object's amount while retaining its currency with #-" do
211
+ assert_equal Money.new(50_00, "USD"), Money.new(100_00, "USD") - Money.new(5_00, "EUR")
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'money'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ class VariableExchangeBankTest < Test::Unit::TestCase
4
+ context "Money::VariableExchangeBank" do
5
+ setup do
6
+ @bank = Money::VariableExchangeBank.new
7
+ end
8
+
9
+ should "return the previously specified conversion rate" do
10
+ @bank.add_rate("USD", "EUR", 0.788332676)
11
+ @bank.add_rate("EUR", "YEN", 122.631477)
12
+ assert_equal 0.788332676, @bank.get_rate("USD", "EUR")
13
+ assert_equal 122.631477, @bank.get_rate("EUR", "YEN")
14
+ end
15
+
16
+ should "treat currency names case-insensitively" do
17
+ @bank.add_rate("usd", "eur", 1)
18
+ assert_equal 1, @bank.get_rate("USD", "EUR")
19
+ assert @bank.same_currency?("USD", "usd")
20
+ assert !@bank.same_currency?("EUR", "usd")
21
+ end
22
+
23
+ should "return nil if the conversion rate is unknown" do
24
+ assert_nil @bank.get_rate("American Pesos", "EUR")
25
+ end
26
+
27
+ should "exchange money from one currency to another according to the specified conversion rates" do
28
+ @bank.add_rate("USD", "EUR", 0.5)
29
+ @bank.add_rate("EUR", "YEN", 10)
30
+ assert_equal 5_00, @bank.exchange(10_00, "USD", "EUR")
31
+ assert_equal 5000_00, @bank.exchange(500_00, "EUR", "YEN")
32
+ end
33
+
34
+ should "round the exchanged result down" do
35
+ @bank.add_rate("USD", "EUR", 0.788332676)
36
+ @bank.add_rate("EUR", "YEN", 122.631477)
37
+ assert_equal 788, @bank.exchange(10_00, "USD", "EUR")
38
+ assert_equal 6131573, @bank.exchange(500_00, "EUR", "YEN")
39
+ end
40
+
41
+ should "raise Money::UnknownRate upon conversion if the conversion rate is unknown" do
42
+ assert_raise Money::UnknownRate do
43
+ @bank.exchange(10, "USD", "EUR")
44
+ end
45
+ end
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bai-money
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - beawesomeinstead
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-04 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Library for dealing with money and currency conversion.
17
+ email: beawesomeinstead@yahoo.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - .gitignore
26
+ - README.markdown
27
+ - VERSION
28
+ - lib/money.rb
29
+ - lib/money/core_extensions.rb
30
+ - lib/money/errors.rb
31
+ - lib/money/money.rb
32
+ - lib/money/symbols.rb
33
+ - lib/money/variable_exchange_bank.rb
34
+ - money.gemspec
35
+ - rakefile
36
+ - test/core_extensions_test.rb
37
+ - test/money_test.rb
38
+ - test/test_helper.rb
39
+ - test/variable_exchange_bank_test.rb
40
+ has_rdoc: false
41
+ homepage: http://github.com/bai/money
42
+ licenses:
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Library for dealing with money and currency conversion.
67
+ test_files:
68
+ - test/variable_exchange_bank_test.rb
69
+ - test/money_test.rb
70
+ - test/test_helper.rb
71
+ - test/core_extensions_test.rb