Skip to content

Commit

Permalink
merge FooBarWidget branch
Browse files Browse the repository at this point in the history
  • Loading branch information
nofxx committed Mar 14, 2009
1 parent f487557 commit 16c15ba
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 25 deletions.
2 changes: 1 addition & 1 deletion lib/money.rb
Expand Up @@ -25,5 +25,5 @@
require 'money/core_extensions'

class Money
VERSION = "2.2.2"
VERSION = "2.3.0"
end
4 changes: 2 additions & 2 deletions lib/money/acts_as_money.rb
Expand Up @@ -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

Expand Down
103 changes: 94 additions & 9 deletions lib/money/core_extensions.rb
Expand Up @@ -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
32 changes: 26 additions & 6 deletions lib/money/money.rb
Expand Up @@ -185,17 +185,37 @@ def with_tax(tax)
# html:
#
# Money.ca_dollar(570).format(:html => true, :with_currency => true) => "$5.70 <span class=\"currency\">CAD</span>"
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 << '<span class="currency">' if rules[:html]
Expand Down
4 changes: 2 additions & 2 deletions 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

Expand Down
22 changes: 17 additions & 5 deletions spec/money/acts_as_money_spec.rb
Expand Up @@ -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
Expand All @@ -19,26 +19,38 @@ 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
@account.value.should be_instance_of(Money)
end

it "should write to the db" do
p @account
@account.value.to_s.should eql("10.00")
end

Expand Down
5 changes: 5 additions & 0 deletions spec/money/core_extensions_spec.rb
Expand Up @@ -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") }
Expand All @@ -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

0 comments on commit 16c15ba

Please sign in to comment.