Class: Spree::Money

Inherits:
Object
  • Object
show all
Defined in:
lib/spree/money.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(amount, options = {}) ⇒ Money

Returns a new instance of Money.


11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/spree/money.rb', line 11

def initialize(amount, options={})
  @money = self.class.parse([amount, (options[:currency] || Spree::Config[:currency])].join)
  @options = {}
  @options[:with_currency] = Spree::Config[:display_currency]
  @options[:symbol_position] = Spree::Config[:currency_symbol_position].to_sym
  @options[:no_cents] = Spree::Config[:hide_cents]
  @options[:decimal_mark] = Spree::Config[:currency_decimal_mark]
  @options[:thousands_separator] = Spree::Config[:currency_thousands_separator]
  @options[:sign_before_symbol] = Spree::Config[:currency_sign_before_symbol]
  @options.merge!(options)
  # Must be a symbol because the Money gem doesn't do the conversion
  @options[:symbol_position] = @options[:symbol_position].to_sym
end

Instance Attribute Details

#moneyObject (readonly)

Returns the value of attribute money


7
8
9
# File 'lib/spree/money.rb', line 7

def money
  @money
end

Class Method Details

.extract_cents(input, currency = Money.default_currency) ⇒ Object

This method is being deprecated in Money 6.1.0, so now lives here.

Raises:

  • (ArgumentError)

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/spree/money.rb', line 62

def self.extract_cents(input, currency = Money.default_currency)
  # remove anything that's not a number, potential thousands_separator, or minus sign
  num = input.gsub(/[^\d.,'-]/, '')

  # set a boolean flag for if the number is negative or not
  negative = num =~ /^-|-$/ ? true : false

  # decimal mark character
  decimal_char = currency.decimal_mark

  # if negative, remove the minus sign from the number
  # if it's not negative, the hyphen makes the value invalid
  if negative
    num = num.sub(/^-|-$/, '')
  end

  raise ArgumentError, "Invalid currency amount (hyphen)" if num.include?('-')

  #if the number ends with punctuation, just throw it out.  If it means decimal,
  #it won't hurt anything.  If it means a literal period or comma, this will
  #save it from being mis-interpreted as a decimal.
  num.chop! if num.match(/[\.|,]$/)

  # gather all decimal_marks within the result number
  used_delimiters = num.scan(/[^\d]/)

  # determine the number of unique decimal_marks 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 (decimal_marks are space, comma, and period)
  case used_delimiters.uniq.length
    # no decimal_mark or thousands_separator; major (dollars) is the number, and minor (cents) is 0
  when 0 then major, minor = num, 0

    # two decimal_marks, so we know the last item in this array is the
    # major/minor thousands_separator and the rest are decimal_marks
  when 2
    thousands_separator, decimal_mark = used_delimiters.uniq

    # remove all thousands_separator, split on the decimal_mark
    major, minor = num.gsub(thousands_separator, '').split(decimal_mark)
    min = 0 unless min
  when 1
    # we can't determine if the comma or period is supposed to be a decimal_mark or a thousands_separator
    # e.g.
    # 1,00 - comma is a thousands_separator
    # 1.000 - period is a thousands_separator
    # 1,000 - comma is a decimal_mark
    # 1,000,000 - comma is a decimal_mark
    # 10000,00 - comma is a thousands_separator
    # 1000,000 - comma is a thousands_separator

    # assign first decimal_mark for reusability
    decimal_mark = used_delimiters.first

    # When we have identified the decimal mark character
    if decimal_char == decimal_mark
      major, minor = num.split(decimal_char)

    else
      # decimal_mark is used as a decimal_mark when there are multiple instances, always
      if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
        major, minor = num.gsub(decimal_mark, ''), 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(decimal_mark)
        possible_major ||= "0"
        possible_minor ||= "00"

        # 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 # thousands_separator
          major, minor = possible_major, possible_minor
        else
          # minor length is three
          # let's try to figure out intent of the thousands_separator

          # the major length is greater than three, which means
          # the comma or period is used as a thousands_separator
          # 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 thousands_separator
            if decimal_mark == '.'
              major, minor = possible_major, possible_minor
            else
              major, minor = "#{possible_major}#{possible_minor}", 0
            end
          end
        end
      end
    end
  else
    # TODO: ParseError
    raise ArgumentError, "Invalid currency amount"
  end

  # build the string based on major/minor since decimal_mark/thousands_separator have been removed
  # avoiding floating point arithmetic here to ensure accuracy
  cents = (major.to_i * currency.subunit_to_unit)
  # Because of an bug in JRuby, we can't just call #floor
  minor = minor.to_s
  minor = if minor.size < currency.decimal_places
            (minor + ("0" * currency.decimal_places))[0,currency.decimal_places].to_i
          elsif minor.size > currency.decimal_places
            if minor[currency.decimal_places,1].to_i >= 5
              minor[0,currency.decimal_places].to_i+1
            else
              minor[0,currency.decimal_places].to_i
            end
          else
            minor.to_i
          end

  cents += minor

  # if negative, multiply by -1; otherwise, return positive cents
  negative ? cents * -1 : cents
end

.parse(input, currency = nil) ⇒ Object

This method is being deprecated in Money 6.1.0, so now lives here.


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/spree/money.rb', line 26

def self.parse(input, currency = nil)
  i = input.to_s.strip

  # raise Money::Currency.table.collect{|c| c[1][:symbol]}.inspect

  # Check the first character for a currency symbol, alternatively get it
  # from the stated currency string
  c = if ::Monetize.assume_from_symbol && i =~ /^(\$|€|£)/
    case i
    when /^\$/ then "USD"
    when /^€/ then "EUR"
    when // then "GBP"
    end
  else
    i[/[A-Z]{2,3}/]
  end

  # check that currency passed and embedded currency are the same,
  # and negotiate the final currency
  if currency.nil? and c.nil?
    currency = ::Money.default_currency
  elsif currency.nil?
    currency = c
  elsif c.nil?
    currency = currency
  elsif currency != c
    # TODO: ParseError
    raise ArgumentError, "Mismatching Currencies"
  end
  currency = ::Money::Currency.wrap(currency)

  fractional = extract_cents(i, currency)
  ::Money.new(fractional, currency)
end

Instance Method Details

#==(obj) ⇒ Object


211
212
213
# File 'lib/spree/money.rb', line 211

def ==(obj)
  @money == obj.money
end

#as_jsonObject


207
208
209
# File 'lib/spree/money.rb', line 207

def as_json(*)
  to_s
end

#to_html(options = { :html => true }) ⇒ Object


197
198
199
200
201
202
203
204
205
# File 'lib/spree/money.rb', line 197

def to_html(options = { :html => true })
  output = @money.format(@options.merge(options))
  if options[:html]
    # 1) prevent blank, breaking spaces
    # 2) prevent escaping of HTML character entities
    output = output.sub(" ", "&nbsp;").html_safe
  end
  output
end

#to_sObject


193
194
195
# File 'lib/spree/money.rb', line 193

def to_s
  @money.format(@options)
end