more_money 0.0.5
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.
- data/History.txt +0 -0
- data/Manifest.txt +11 -0
- data/README.txt +3 -0
- data/Rakefile +50 -0
- data/lib/more_money/core_extensions.rb +32 -0
- data/lib/more_money/more_money.rb +291 -0
- data/lib/more_money/version.rb +9 -0
- data/lib/more_money.rb +1 -0
- data/setup.rb +1585 -0
- data/test/more_money_test.rb +117 -0
- data/test/test_helper.rb +2 -0
- metadata +64 -0
data/History.txt
ADDED
File without changes
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'more_money', 'version')
|
13
|
+
|
14
|
+
AUTHOR = "blank" # can also be an array of Authors
|
15
|
+
EMAIL = "your contact email for bug fixes and info"
|
16
|
+
DESCRIPTION = "handles money objects using just integer as backend"
|
17
|
+
GEM_NAME = "more_money" # what ppl will type to install your gem
|
18
|
+
RUBYFORGE_PROJECT = "more_money" # The unix name for your project
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
RELEASE_TYPES = %w( gem ) # can use: gem, tar, zip
|
21
|
+
|
22
|
+
|
23
|
+
NAME = "more_money"
|
24
|
+
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
25
|
+
VERS = ENV['VERSION'] || (MoreMoney::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
26
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
27
|
+
RDOC_OPTS = ['--quiet', '--title', "more_money documentation",
|
28
|
+
"--opname", "index.html",
|
29
|
+
"--line-numbers",
|
30
|
+
"--main", "README",
|
31
|
+
"--inline-source"]
|
32
|
+
|
33
|
+
# Generate all the Rake tasks
|
34
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
35
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
36
|
+
p.author = AUTHOR
|
37
|
+
p.description = DESCRIPTION
|
38
|
+
p.email = EMAIL
|
39
|
+
p.summary = DESCRIPTION
|
40
|
+
p.url = HOMEPATH
|
41
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
42
|
+
p.test_globs = ["test/**/*_test.rb"]
|
43
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
44
|
+
|
45
|
+
# == Optional
|
46
|
+
#p.changes - A description of the release's latest changes.
|
47
|
+
#p.extra_deps - An array of rubygem dependencies.
|
48
|
+
#p.spec_extras - A hash of extra values to set in the gemspec.
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Allows Writing of 100.to_money for +Numeric+ types
|
2
|
+
# 100.to_money => #<Money @cents=10000>
|
3
|
+
# 100.37.to_money => #<Money @cents=10037>
|
4
|
+
class Numeric
|
5
|
+
def to_money
|
6
|
+
Money.new(self * 100)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Allows Writing of '100'.to_money for +String+ types
|
11
|
+
# Excess characters will be discarded
|
12
|
+
# '100'.to_money => #<Money @cents=10000>
|
13
|
+
# '100.37'.to_money => #<Money @cents=10037>
|
14
|
+
class String
|
15
|
+
def to_money
|
16
|
+
# Get the currency
|
17
|
+
matches = scan(/([A-Z]{2,3})/)
|
18
|
+
currency = matches[0] ? matches[0][0] : MoreMoney::Money.default_currency
|
19
|
+
|
20
|
+
# Get the cents amount
|
21
|
+
matches = scan(/(\-?\d+(\.(\d+))?)/)
|
22
|
+
cents = matches[0] ? (matches[0][0].to_f * 100) : 0
|
23
|
+
|
24
|
+
MoreMoney::Money.new(cents, currency)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class NilClass
|
29
|
+
def to_money(currency = MoreMoney::Money.default_currency)
|
30
|
+
MoreMoney::Money.new(nil, currency)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
# === Usage with ActiveRecord
|
2
|
+
#
|
3
|
+
# Use the compose_of helper to let active record deal with embedding the money
|
4
|
+
# object in your models. The following example requires a cents and a currency field.
|
5
|
+
#
|
6
|
+
# class ProductUnit < ActiveRecord::Base
|
7
|
+
# belongs_to :product
|
8
|
+
# composed_of :price, :class_name => "Money", :mapping => [ %w(cents cents), %w(currency currency) ]
|
9
|
+
#
|
10
|
+
# private
|
11
|
+
# validate :cents_not_zero
|
12
|
+
#
|
13
|
+
# def cents_not_zero
|
14
|
+
# errors.add("cents", "cannot be zero or less") unless cents > 0
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# validates_presence_of :sku, :currency
|
18
|
+
# validates_uniqueness_of :sku
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
#
|
22
|
+
module MoreMoney
|
23
|
+
class MoneyMissingExchangeRate < StandardError# :nodoc:
|
24
|
+
end
|
25
|
+
|
26
|
+
class MoneyInvalidCurrency < StandardError# :nodoc:
|
27
|
+
end
|
28
|
+
|
29
|
+
class Money
|
30
|
+
include Comparable
|
31
|
+
|
32
|
+
attr_reader :currency
|
33
|
+
|
34
|
+
CURRENCIES = {}
|
35
|
+
|
36
|
+
DEFAULT_FORMATS = {}
|
37
|
+
|
38
|
+
EXCHANGE_RATES = {}
|
39
|
+
|
40
|
+
#the current default currency
|
41
|
+
def self.default_currency
|
42
|
+
@default_currency
|
43
|
+
end
|
44
|
+
|
45
|
+
#set the default currency
|
46
|
+
def self.default_currency=(currency)
|
47
|
+
@default_currency = currency
|
48
|
+
end
|
49
|
+
|
50
|
+
#set the exchange rate from a currency to anothe one
|
51
|
+
#
|
52
|
+
# MoreMoney::Money.set_exchange_rate('USD', 'BGP', 0.51)
|
53
|
+
#
|
54
|
+
#the rate can be specified as a callable object which will be called
|
55
|
+
#at the moment of the conversion
|
56
|
+
def self.set_exchange_rate(starting_currency, destination_currency, rate)
|
57
|
+
EXCHANGE_RATES[[starting_currency, destination_currency]] = rate
|
58
|
+
end
|
59
|
+
|
60
|
+
#set a formatting default which will be used for any currency which has
|
61
|
+
#no formatting defaults otherwise specified
|
62
|
+
#
|
63
|
+
# MoreMoney::Money.set_default_format(0, 'free')
|
64
|
+
#
|
65
|
+
def self.set_default_format(cents, default_format)
|
66
|
+
DEFAULT_FORMATS[cents] = default_format
|
67
|
+
end
|
68
|
+
|
69
|
+
#returns the exchange rate from the first currency to the second one
|
70
|
+
#
|
71
|
+
# MoreMoney::Money.get_exchange_rate(starting_currency, destination_currency)
|
72
|
+
#
|
73
|
+
def self.get_exchange_rate(starting_currency, destination_currency)
|
74
|
+
rate = EXCHANGE_RATES[[starting_currency, destination_currency]]
|
75
|
+
return rate.respond_to?(:call) ? rate.call : rate
|
76
|
+
end
|
77
|
+
|
78
|
+
#adds a currency to the set of available ones.
|
79
|
+
#
|
80
|
+
# MoreMoney::Money.add_currency({:code => 'USD', :symbol => '$', :description => 'us_dollar'})
|
81
|
+
#
|
82
|
+
#the line above will add the US dollar currency, the description will be
|
83
|
+
#used to create the method MoreMoney::Money.us_dollar(cents) which can be
|
84
|
+
#used to instantiate new Money object with USD as a currency
|
85
|
+
#
|
86
|
+
#2 optional parameters can be specified: :format, :format_defaults
|
87
|
+
#
|
88
|
+
#:format option can be specified to
|
89
|
+
#control the formatting method for the specific currency
|
90
|
+
#the :format parameter will be called passing 4 arguments:
|
91
|
+
# cents, currency, currency symbol, rules
|
92
|
+
#
|
93
|
+
#:format_defaults need to be an hash with the cents value as keys and the
|
94
|
+
#custom string format to use
|
95
|
+
#
|
96
|
+
# MoreMoney::Money.add_currency({:code => 'USD', :symbol => '$', :description => 'us_dollar'})
|
97
|
+
#
|
98
|
+
# MoreMoney::Money.new(0, 'USD').format
|
99
|
+
#
|
100
|
+
# => '$ 0.00'
|
101
|
+
#
|
102
|
+
# MoreMoney::Money.add_currency({:code => 'USD', :symbol => '$', :description => 'us_dollar', :format_defaults => { 0 => 'free' }})
|
103
|
+
#
|
104
|
+
# MoreMoney::Money.new(0, 'USD').format
|
105
|
+
#
|
106
|
+
# => 'free'
|
107
|
+
#
|
108
|
+
def self.add_currency(currency_hash)
|
109
|
+
code = currency_hash.delete(:code)
|
110
|
+
CURRENCIES[code]= currency_hash
|
111
|
+
if currency_hash[:description]
|
112
|
+
(class << self; self; end).module_eval do
|
113
|
+
define_method(currency_hash[:description]) do |cents|
|
114
|
+
Money.new(cents, code)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
#loads some currencies, see the code to know which one
|
121
|
+
def self.load_std_currencies
|
122
|
+
add_currency({:code => 'USD', :symbol => '$', :description => 'us_dollar'})
|
123
|
+
add_currency({:code => 'GBP', :symbol => '£', :description => 'gb_pound'})
|
124
|
+
add_currency({:code => 'EUR', :symbol => '€', :description => 'euro'})
|
125
|
+
add_currency({:code => 'AUD', :symbol => '$', :description => 'au_dollar'})
|
126
|
+
add_currency({:code => 'CAD', :symbol => '$', :description => 'ca_dollar'})
|
127
|
+
add_currency({:code => 'CHF', :symbol => 'CHF', :description => 'ch_franc'})
|
128
|
+
add_currency({:code => 'JPY', :symbol => '¥', :description => 'jp_yen'})
|
129
|
+
end
|
130
|
+
|
131
|
+
# Creates a new money object.
|
132
|
+
#
|
133
|
+
# Money.new(100)
|
134
|
+
#
|
135
|
+
def initialize(cents, currency = Money.default_currency)
|
136
|
+
raise MoneyInvalidCurrency, "#{currency} is not defined as a currency" unless CURRENCIES.key?(currency)
|
137
|
+
@currency = currency
|
138
|
+
@cents = cents.nil? ? nil : cents.round
|
139
|
+
end
|
140
|
+
|
141
|
+
# Do two money objects equal? Only works if both objects are of the same currency
|
142
|
+
def eql?(other_money)
|
143
|
+
cents == other_money.cents && currency == other_money.currency
|
144
|
+
end
|
145
|
+
|
146
|
+
def <=>(other_money)
|
147
|
+
if cents == nil
|
148
|
+
return true if other_money.cents == nil
|
149
|
+
elsif currency == other_money.currency
|
150
|
+
return 0 <=> other_money.cents if cents == nil
|
151
|
+
return cents <=> 0 if other_money.cents == nil
|
152
|
+
|
153
|
+
cents <=> other_money.cents
|
154
|
+
else
|
155
|
+
cents <=> other_money.exchange_to(currency).cents
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#sums two money objects
|
160
|
+
def +(other_money)
|
161
|
+
return other_money.dup if cents.nil? || cents.zero?
|
162
|
+
return dup if other_money.cents.nil? || other_money.cents.zero?
|
163
|
+
|
164
|
+
if currency == other_money.currency
|
165
|
+
Money.new(cents + other_money.cents, other_money.currency)
|
166
|
+
else
|
167
|
+
Money.new(cents + other_money.exchange_to(currency).cents,currency)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#subtracts a money object from another one
|
172
|
+
def -(other_money)
|
173
|
+
|
174
|
+
return Money.new(0 - other_money.cents, other_money.currency) if self.cents.nil? || self.cents.zero?
|
175
|
+
return self.dup if other_money.cents.nil? || other_money.cents.zero?
|
176
|
+
|
177
|
+
if currency == other_money.currency
|
178
|
+
Money.new(cents - other_money.cents, other_money.currency)
|
179
|
+
else
|
180
|
+
Money.new(cents - other_money.exchange_to(currency).cents, currency)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
# multiply money by fixnum
|
186
|
+
def *(fixnum)
|
187
|
+
return self.dup if self.cents.nil?
|
188
|
+
Money.new(cents * fixnum, currency)
|
189
|
+
end
|
190
|
+
|
191
|
+
# divide money by fixnum
|
192
|
+
def /(fixnum)
|
193
|
+
return self.dup if self.cents.nil?
|
194
|
+
Money.new(cents / fixnum, currency)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Test if the money amount is zero
|
198
|
+
def zero?
|
199
|
+
return false if cents.nil?
|
200
|
+
cents == 0
|
201
|
+
end
|
202
|
+
|
203
|
+
# get the cents value of the object
|
204
|
+
def cents
|
205
|
+
@cents.nil? ? nil : @cents.to_i
|
206
|
+
end
|
207
|
+
|
208
|
+
# Format the price according rules
|
209
|
+
# Currently supported are :with_currency, :with_thousands, :no_cents and :html
|
210
|
+
#
|
211
|
+
# with_currency:
|
212
|
+
#
|
213
|
+
# Money.ca_dollar(100).format => "$ 1.00"
|
214
|
+
# Money.ca_dollar(100).format(:with_currency) => "$ 1.00 CAD"
|
215
|
+
# Money.us_dollar(85).format(:with_currency) => "$ 0.85 USD"
|
216
|
+
#
|
217
|
+
# with_thousands:
|
218
|
+
#
|
219
|
+
# Money.us_dollar(100000).format() => "$ 1000.00"
|
220
|
+
# Money.us_dollar(100000).format(:with_thousands) => "$ 1,000.00"
|
221
|
+
#
|
222
|
+
# no_cents:
|
223
|
+
#
|
224
|
+
# Money.ca_dollar(100).format(:no_cents) => "$ 1"
|
225
|
+
# Money.ca_dollar(599).format(:no_cents) => "$ 5"
|
226
|
+
#
|
227
|
+
# Money.ca_dollar(570).format(:no_cents, :with_currency) => "$ 5 CAD"
|
228
|
+
# Money.ca_dollar(39000).format(:no_cents) => "$ 390"
|
229
|
+
#
|
230
|
+
# html:
|
231
|
+
#
|
232
|
+
# Money.ca_dollar(570).format(:html, :with_currency) => "$ 5.70 <span class=\"currency\">CAD</span>"
|
233
|
+
def format(*rules)
|
234
|
+
return CURRENCIES[currency][:format_defaults][cents] if CURRENCIES[currency][:format_defaults] && CURRENCIES[currency][:format_defaults][cents]
|
235
|
+
rules = rules.flatten
|
236
|
+
return CURRENCIES[currency][:format].call(cents, currency, CURRENCIES[currency][:symbol], rules) if CURRENCIES[currency][:format]
|
237
|
+
return DEFAULT_FORMATS[cents] if DEFAULT_FORMATS[cents]
|
238
|
+
|
239
|
+
|
240
|
+
if rules.include?(:no_cents)
|
241
|
+
formatted = sprintf("%d", cents.to_f / 100 )
|
242
|
+
else
|
243
|
+
formatted = sprintf("%.2f", cents.to_f / 100 )
|
244
|
+
end
|
245
|
+
|
246
|
+
if rules.include?(:with_thousands)
|
247
|
+
formatted = formatted.reverse!.split('')
|
248
|
+
newstr = []
|
249
|
+
3.times { newstr << formatted.shift } unless rules.include?(:no_cents)
|
250
|
+
formatted.each_with_index do |num, index|
|
251
|
+
newstr << ',' if index !=0 && index % 3 == 0
|
252
|
+
newstr << num
|
253
|
+
end
|
254
|
+
formatted = newstr.to_s.reverse!
|
255
|
+
end
|
256
|
+
|
257
|
+
#if rules.include?(:with_symbol)
|
258
|
+
formatted = "#{CURRENCIES[currency][:symbol]} #{formatted}"
|
259
|
+
#end
|
260
|
+
|
261
|
+
if rules.include?(:with_currency)
|
262
|
+
formatted << " "
|
263
|
+
formatted << '<span class="currency">' if rules.include?(:html)
|
264
|
+
formatted << currency
|
265
|
+
formatted << '</span>' if rules.include?(:html)
|
266
|
+
end
|
267
|
+
formatted
|
268
|
+
end
|
269
|
+
|
270
|
+
# Money.new(100, 'USD').to_s => "1.00"
|
271
|
+
def to_s
|
272
|
+
sprintf("%.2f", cents.to_f / 100 )
|
273
|
+
end
|
274
|
+
|
275
|
+
# Recieve the amount of this money object in another currency
|
276
|
+
def exchange_to(other_currency)
|
277
|
+
return self if other_currency == currency
|
278
|
+
raise MoneyMissingExchangeRate if Money.get_exchange_rate(currency, other_currency).nil?
|
279
|
+
Money.new(cents * Money.get_exchange_rate(currency, other_currency), other_currency)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Create a new money object with value 0
|
283
|
+
def self.empty(currency = default_currency)
|
284
|
+
Money.new(0, currency)
|
285
|
+
end
|
286
|
+
# Conversation to self
|
287
|
+
def to_money
|
288
|
+
self
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
data/lib/more_money.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), 'more_money/**/*.rb')].sort.each { |lib| require lib }
|