From 9208cda2ffa16122645df31a602471223433ddc4 Mon Sep 17 00:00:00 2001 From: Marcos Augusto Date: Sun, 15 Mar 2009 07:14:01 +0800 Subject: [PATCH] merge FooBarWidget branch Signed-off-by: Renan Tomal Fernandes --- lib/money.rb | 2 +- lib/money/acts_as_money.rb | 4 +- lib/money/core_extensions.rb | 103 ++++++++++++++++++++++++++--- lib/money/money.rb | 32 +++++++-- spec/db/schema.rb | 4 +- spec/money/acts_as_money_spec.rb | 22 ++++-- spec/money/core_extensions_spec.rb | 5 ++ 7 files changed, 147 insertions(+), 25 deletions(-) diff --git a/lib/money.rb b/lib/money.rb index 5b94dce5ad..fd08046993 100644 --- a/lib/money.rb +++ b/lib/money.rb @@ -25,5 +25,5 @@ require 'money/core_extensions' class Money - VERSION = "2.2.2" + VERSION = "2.3.0" end diff --git a/lib/money/acts_as_money.rb b/lib/money/acts_as_money.rb index ff92b74be2..adef03710d 100644 --- a/lib/money/acts_as_money.rb +++ b/lib/money/acts_as_money.rb @@ -22,14 +22,14 @@ module ClassMethods # :with_currency => true # def has_money(*attributes) - config = {:with_currency => true, :converter => lambda { |m| m.to_money }, + config = {:with_currency => true, :converter => lambda { |m| m ||=0; m.to_money }, :allow_nil => false }.update(attributes.extract_options!) attributes.each do |attr| mapping = [[config[:cents] || "#{attr}_cents", 'cents']] mapping << [config[:currency] || "#{attr}_currency", 'currency'] if config[:with_currency] - composed_of attr, :class_name => 'Money',:allow_nil => config[:allow_nil], + composed_of attr, :class_name => 'Money', :allow_nil => config[:allow_nil], :mapping => mapping, :converter => config[:converter] end diff --git a/lib/money/core_extensions.rb b/lib/money/core_extensions.rb index 80dd59b363..7b7554a880 100644 --- a/lib/money/core_extensions.rb +++ b/lib/money/core_extensions.rb @@ -22,18 +22,103 @@ def to_money # Get the currency. matches = scan /([A-Z]{2,3})/ currency = matches[0] ? matches[0][0] : Money.default_currency + cents = calculate_cents(self) + Money.new(cents, currency) + end + + private + + def calculate_cents(number) + # remove anything that's not a number, potential delimiter, or minus sign + num = number.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip + + # set a boolean flag for if the number is negative or not + negative = num.split(//).first == "-" + + # if negative, remove the minus sign from the number + num = num.gsub(/^-/, '') if negative + + # gather all separators within the result number + used_separators = num.scan /[^\d]/ + + # determine the number of unique separators within the number + # + # e.g. + # $1,234,567.89 would return 2 (, and .) + # $125,00 would return 1 + # $199 would return 0 + # $1 234,567.89 would raise an error (separators are space, comma, and period) + case used_separators.uniq.length + # no separator or delimiter; major (dollars) is the number, and minor (cents) is 0 + when 0 then major, minor = num, 0 - # Get the cents amount - sans_spaces = gsub(/\s+/, '') - matches = sans_spaces.scan /(\-?\d+(?:[\.,]\d+)?)/ - cents = if matches[0] - value = matches[0][0].gsub(/,/, '.') - value.to_f * 100 + # two separators, so we know the last item in this array is the + # major/minor delimiter and the rest are separators + when 2 + separator, delimiter = used_separators.uniq + # remove all separators, split on the delimiter + major, minor = num.gsub(separator, '').split(delimiter) + min = 0 unless min + when 1 + # we can't determine if the comma or period is supposed to be a separator or a delimiter + # e.g. + # 1,00 - comma is a delimiter + # 1.000 - period is a delimiter + # 1,000 - comma is a separator + # 1,000,000 - comma is a separator + # 10000,00 - comma is a delimiter + # 1000,000 - comma is a delimiter + + # assign first separator for reusability + separator = used_separators.first + + # separator is used as a separator when there are multiple instances, always + if num.scan(separator).length > 1 # multiple matches; treat as separator + major, minor = num.gsub(separator, ''), 0 + else + # ex: 1,000 - 1.0000 - 10001.000 + # split number into possible major (dollars) and minor (cents) values + possible_major, possible_minor = num.split(separator) + + # if the minor (cents) length isn't 3, assign major/minor from the possibles + # e.g. + # 1,00 => 1.00 + # 1.0000 => 1.00 + # 1.2 => 1.20 + if possible_minor.length != 3 # delimiter + major, minor = possible_major, possible_minor + else + # minor length is three + # let's try to figure out intent of the delimiter + + # the major length is greater than three, which means + # the comma or period is used as a delimiter + # e.g. + # 1000,000 + # 100000,000 + if possible_major.length > 3 + major, minor = possible_major, possible_minor + else + # number is in format ###{sep}### or ##{sep}### or #{sep}### + # handle as , is sep, . is delimiter + if separator == '.' + major, minor = possible_major, possible_minor else - 0 + major, minor = "#{possible_major}#{possible_minor}", 0 end + end + end + end + else + raise ArgumentError, "Invalid currency amount" + end - Money.new(cents, currency) + # build the string based on major/minor since separator/delimiters have been removed + # transform to a float, multiply by 100 to convert to cents + cents = "#{major}.#{minor}".to_f * 100 + + # if negative, multiply by -1; otherwise, return positive cents + negative ? cents * -1 : cents end -end +end diff --git a/lib/money/money.rb b/lib/money/money.rb index 675dcf6c23..d58604aad5 100644 --- a/lib/money/money.rb +++ b/lib/money/money.rb @@ -185,17 +185,37 @@ def with_tax(tax) # html: # # Money.ca_dollar(570).format(:html => true, :with_currency => true) => "$5.70 CAD" - def format(rules = {}) - return "free" if cents == 0 + def format(*rules) + # support for old format parameters + rules = normalize_formatting_rules(rules) + + if cents == 0 + if rules[:display_free].respond_to?(:to_str) + return rules[:display_free] + elsif rules[:display_free] + return "free" + end + end - rules = rules.flatten + if rules.has_key?(:symbol) + if rules[:symbol] + symbol = rules[:symbol] + else + symbol = "" + end + else + symbol = "$" + end - if rules.include?(:no_cents) - formatted = sprintf("$%d", cents.to_f / 100 ) + if rules[:no_cents] + formatted = sprintf("#{symbol}%d", cents.to_f / 100) else - formatted = sprintf("$%.2f", cents.to_f / 100 ) + formatted = sprintf("#{symbol}%.2f", cents.to_f / 100) end + # Commify ("10000" => "10,000") + formatted.gsub!(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2') + if rules[:with_currency] formatted << " " formatted << '' if rules[:html] diff --git a/spec/db/schema.rb b/spec/db/schema.rb index 4576916447..86b2ffe6f4 100644 --- a/spec/db/schema.rb +++ b/spec/db/schema.rb @@ -1,12 +1,12 @@ ActiveRecord::Schema.define() do create_table :accounts, :force => true do |t| - t.integer :value_in_cents, :total_in_cents + t.integer :value_cents, :total_cents t.string :value_currency, :total_currency end create_table :products, :force => true do |t| - t.integer :value_in_cents, :tax_pennys + t.integer :value_cents, :tax_pennys t.string :value_currency end diff --git a/spec/money/acts_as_money_spec.rb b/spec/money/acts_as_money_spec.rb index 8704005a69..81ce59b905 100644 --- a/spec/money/acts_as_money_spec.rb +++ b/spec/money/acts_as_money_spec.rb @@ -9,7 +9,7 @@ class Account < ActiveRecord::Base end class Product < ActiveRecord::Base - has_money :value + has_money :value, :allow_nil => false has_money :tax, :cents => "pennys", :with_currency => false validates_presence_of :value @@ -19,19 +19,30 @@ class Product < ActiveRecord::Base it "should require money" do @account = Account.create(:value => nil) - pending - @account.value_in_cents.should eql(0) + p @account.errors + @account.should have(1).errors + end + + it "should require money" do + @product = Product.create(:value => nil) + @product.should have(1).errors end it "should require money" do @product_fake = Product.create(:value => nil) - @product_fake.value_in_cents.should eql(0) + @product_fake.value_cents.should eql(0) end + it "should create" do + @account = Account.create!(:value => 10, :total => "20 BRL") + @account.should be_valid + end + + describe "Account" do before(:each) do - @account = Account.create(:value => 10, :total => "20 BRL") + @account = Account.create!(:value => 10, :total => "20 BRL") end it "should return an instance of Money" do @@ -39,6 +50,7 @@ class Product < ActiveRecord::Base end it "should write to the db" do + p @account @account.value.to_s.should eql("10.00") end diff --git a/spec/money/core_extensions_spec.rb b/spec/money/core_extensions_spec.rb index 2a9d2e2964..75b749b76a 100644 --- a/spec/money/core_extensions_spec.rb +++ b/spec/money/core_extensions_spec.rb @@ -20,6 +20,8 @@ it { "100.37".to_money.should == Money.new(100_37) } it { "100,37".to_money.should == Money.new(100_37) } it { "100 000".to_money.should == Money.new(100_000_00) } + it { "100.000,45".to_money.should == Money.new(100_000_45) } + it { "-100.100,45".to_money.should == Money.new(-100_100_45) } it { "100 USD".to_money.should == Money.new(100_00, "USD") } it { "-100 USD".to_money.should == Money.new(-100_00, "USD") } @@ -34,6 +36,9 @@ it { "EUR 100,37".to_money.should == Money.new(100_37, "EUR") } it { "EUR -100,37".to_money.should == Money.new(-100_37, "EUR") } + it { "BRL 100,37".to_money.should == Money.new(100_37, "BRL") } + it { "BRL -100,37".to_money.should == Money.new(-100_37, "BRL") } + it {"$100 USD".to_money.should == Money.new(100_00, "USD") } end end