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 ADDED
File without changes
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ setup.rb
6
+ lib/more_money.rb
7
+ lib/more_money/version.rb
8
+ lib/more_money/core_extensions.rb
9
+ lib/more_money/more_money.rb
10
+ test/test_helper.rb
11
+ test/more_money_test.rb
data/README.txt ADDED
@@ -0,0 +1,3 @@
1
+ README for more_money
2
+ =====================
3
+
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
@@ -0,0 +1,9 @@
1
+ module MoreMoney #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 5
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ 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 }