more_money 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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 }