spree_core 2.1.3 → 2.1.4
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.
- checksums.yaml +4 -4
- data/app/helpers/spree/admin/images_helper.rb +1 -1
- data/app/helpers/spree/base_helper.rb +5 -2
- data/app/models/spree/address.rb +9 -1
- data/app/models/spree/adjustment.rb +2 -2
- data/app/models/spree/calculator/default_tax.rb +5 -1
- data/app/models/spree/credit_card.rb +2 -0
- data/app/models/spree/gateway.rb +1 -1
- data/app/models/spree/inventory_unit.rb +5 -4
- data/app/models/spree/legacy_user.rb +2 -11
- data/app/models/spree/line_item.rb +2 -3
- data/app/models/spree/log_entry.rb +4 -0
- data/app/models/spree/option_type.rb +6 -0
- data/app/models/spree/option_value.rb +12 -1
- data/app/models/spree/order.rb +34 -16
- data/app/models/spree/order/checkout.rb +4 -0
- data/app/models/spree/order_inventory.rb +1 -1
- data/app/models/spree/payment.rb +10 -2
- data/app/models/spree/payment/processing.rb +5 -4
- data/app/models/spree/payment_method.rb +2 -0
- data/app/models/spree/price.rb +5 -0
- data/app/models/spree/product.rb +6 -5
- data/app/models/spree/product/scopes.rb +12 -6
- data/app/models/spree/product_property.rb +1 -1
- data/app/models/spree/promotion.rb +1 -8
- data/app/models/spree/promotion/rules/user_logged_in.rb +1 -3
- data/app/models/spree/property.rb +8 -0
- data/app/models/spree/shipment.rb +9 -14
- data/app/models/spree/shipping_method.rb +3 -2
- data/app/models/spree/shipping_rate.rb +7 -9
- data/app/models/spree/stock/estimator.rb +21 -14
- data/app/models/spree/stock/package.rb +1 -1
- data/app/models/spree/stock/packer.rb +1 -1
- data/app/models/spree/stock/quantifier.rb +11 -2
- data/app/models/spree/stock_item.rb +2 -2
- data/app/models/spree/stock_location.rb +8 -0
- data/app/models/spree/stock_movement.rb +3 -1
- data/app/models/spree/taxon.rb +2 -2
- data/app/models/spree/variant.rb +19 -4
- data/app/models/spree/zone.rb +1 -1
- data/app/views/spree/shared/_routes.html.erb +1 -1
- data/config/locales/en.yml +15 -1
- data/db/default/spree/countries.rb +7 -7
- data/db/migrate/20130417120034_add_index_to_source_columns_on_adjustments.rb +5 -0
- data/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb +5 -2
- data/db/migrate/20131026154747_add_track_inventory_to_variant.rb +5 -0
- data/db/migrate/20131120234456_add_updated_at_to_variants.rb +5 -0
- data/db/migrate/20131211192741_unique_shipping_method_categories.rb +24 -0
- data/db/migrate/20140120160805_add_index_to_variant_id_and_currency_on_prices.rb +5 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +14 -3
- data/lib/generators/spree/dummy/templates/rails/database.yml +10 -0
- data/lib/spree/core.rb +3 -0
- data/lib/spree/core/controller_helpers/order.rb +4 -1
- data/lib/spree/core/controller_helpers/ssl.rb +5 -7
- data/lib/spree/core/controller_helpers/strong_parameters.rb +6 -0
- data/lib/spree/core/delegate_belongs_to.rb +16 -10
- data/lib/spree/core/engine.rb +11 -2
- data/lib/spree/core/mail_method.rb +27 -0
- data/lib/spree/core/mail_settings.rb +33 -38
- data/lib/spree/core/permalinks.rb +5 -1
- data/lib/spree/core/s3_support.rb +1 -1
- data/lib/spree/core/user_address.rb +30 -0
- data/lib/spree/core/validators/email.rb +9 -3
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/i18n.rb +1 -0
- data/lib/spree/migrations.rb +55 -0
- data/lib/spree/money.rb +171 -1
- data/lib/spree/permitted_attributes.rb +6 -6
- data/lib/spree/testing_support/capybara_ext.rb +6 -5
- data/lib/spree/testing_support/controller_requests.rb +20 -4
- data/lib/spree/testing_support/factories/product_factory.rb +4 -0
- data/lib/spree/testing_support/factories/variant_factory.rb +15 -0
- metadata +158 -164
@@ -0,0 +1,27 @@
|
|
1
|
+
module Spree
|
2
|
+
module Core
|
3
|
+
class MailMethod
|
4
|
+
def initialize(options={})
|
5
|
+
end
|
6
|
+
|
7
|
+
def deliver!(mail)
|
8
|
+
if Config.enable_mail_delivery
|
9
|
+
mailer.deliver!(mail)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def mailer
|
14
|
+
mailer_class.new(mail_server_settings)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def mailer_class
|
19
|
+
Rails.env.test?? Mail::TestMailer : Mail::SMTP
|
20
|
+
end
|
21
|
+
|
22
|
+
def mail_server_settings
|
23
|
+
MailSettings.new.mail_server_settings
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -8,53 +8,48 @@ module Spree
|
|
8
8
|
# This makes it possible to configure the mail settings through an admin
|
9
9
|
# interface instead of requiring changes to the Rails envrionment file
|
10
10
|
def self.init
|
11
|
-
|
11
|
+
override! if override?
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.override?
|
15
15
|
Config.override_actionmailer_config
|
16
16
|
end
|
17
17
|
|
18
|
-
def override!
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
def self.override!
|
19
|
+
ActionMailer::Base.delivery_method = :spree
|
20
|
+
ActionMailer::Base.default_url_options[:host] ||= Config.site_url
|
21
|
+
end
|
22
|
+
|
23
|
+
def mail_server_settings
|
24
|
+
settings = if need_authentication?
|
25
|
+
basic_settings.merge(user_credentials)
|
26
|
+
else
|
27
|
+
basic_settings
|
28
|
+
end
|
29
|
+
|
30
|
+
settings.merge :enable_starttls_auto => secure_connection?
|
26
31
|
end
|
27
32
|
|
28
33
|
private
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
:authentication => Config.mail_auth_type }
|
49
|
-
end
|
50
|
-
|
51
|
-
def need_authentication?
|
52
|
-
Config.mail_auth_type != 'None'
|
53
|
-
end
|
54
|
-
|
55
|
-
def secure_connection?
|
56
|
-
Config.secure_connection_type == 'TLS'
|
57
|
-
end
|
34
|
+
def user_credentials
|
35
|
+
{ :user_name => Config.smtp_username,
|
36
|
+
:password => Config.smtp_password }
|
37
|
+
end
|
38
|
+
|
39
|
+
def basic_settings
|
40
|
+
{ :address => Config.mail_host,
|
41
|
+
:domain => Config.mail_domain,
|
42
|
+
:port => Config.mail_port,
|
43
|
+
:authentication => Config.mail_auth_type }
|
44
|
+
end
|
45
|
+
|
46
|
+
def need_authentication?
|
47
|
+
Config.mail_auth_type != 'None'
|
48
|
+
end
|
49
|
+
|
50
|
+
def secure_connection?
|
51
|
+
Config.secure_connection_type == 'TLS'
|
52
|
+
end
|
58
53
|
end
|
59
54
|
end
|
60
55
|
end
|
@@ -33,6 +33,10 @@ module Spree
|
|
33
33
|
permalink_options[:prefix] || ""
|
34
34
|
end
|
35
35
|
|
36
|
+
def permalink_length
|
37
|
+
permalink_options[:length] || 9
|
38
|
+
end
|
39
|
+
|
36
40
|
def permalink_order
|
37
41
|
order = permalink_options[:order]
|
38
42
|
"#{order} ASC," if order
|
@@ -40,7 +44,7 @@ module Spree
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def generate_permalink
|
43
|
-
"#{self.class.permalink_prefix}#{Array.new(
|
47
|
+
"#{self.class.permalink_prefix}#{Array.new(self.class.permalink_length){rand(9)}.join}"
|
44
48
|
end
|
45
49
|
|
46
50
|
def save_permalink(permalink_value=self.to_param)
|
@@ -15,7 +15,7 @@ module Spree
|
|
15
15
|
self.attachment_definitions[field][:s3_credentials] = s3_creds
|
16
16
|
self.attachment_definitions[field][:s3_headers] = ActiveSupport::JSON.decode(config[:s3_headers])
|
17
17
|
self.attachment_definitions[field][:bucket] = config[:s3_bucket]
|
18
|
-
self.attachment_definitions[field][:s3_protocol] = config[:s3_protocol].
|
18
|
+
self.attachment_definitions[field][:s3_protocol] = config[:s3_protocol].blank? ? '' : config[:s3_protocol].downcase
|
19
19
|
self.attachment_definitions[field][:s3_host_alias] = config[:s3_host_alias] unless config[:s3_host_alias].blank?
|
20
20
|
end
|
21
21
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Spree
|
2
|
+
module Core
|
3
|
+
module UserAddress
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
belongs_to :bill_address, foreign_key: :bill_address_id, class_name: 'Spree::Address'
|
8
|
+
alias_attribute :billing_address, :bill_address
|
9
|
+
|
10
|
+
belongs_to :ship_address, foreign_key: :ship_address_id, class_name: 'Spree::Address'
|
11
|
+
alias_attribute :shipping_address, :ship_address
|
12
|
+
|
13
|
+
def persist_order_address(order)
|
14
|
+
b_address = self.bill_address || self.build_bill_address
|
15
|
+
b_address.attributes = order.bill_address.attributes.except('id', 'updated_at', 'created_at')
|
16
|
+
b_address.save
|
17
|
+
self.update_attributes(bill_address_id: b_address.id)
|
18
|
+
|
19
|
+
# May not be present if delivery step has been removed
|
20
|
+
if order.ship_address
|
21
|
+
s_address = self.ship_address || self.build_ship_address
|
22
|
+
s_address.attributes = order.ship_address.attributes.except('id', 'updated_at', 'created_at')
|
23
|
+
s_address.save
|
24
|
+
self.update_attributes(ship_address_id: s_address.id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -3,10 +3,16 @@
|
|
3
3
|
require 'mail'
|
4
4
|
class EmailValidator < ActiveModel::EachValidator
|
5
5
|
def validate_each(record,attribute,value)
|
6
|
+
unless valid?(value)
|
7
|
+
record.errors.add(attribute, :invalid, {:value => value}.merge!(options))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid?(email)
|
6
12
|
begin
|
7
|
-
m = Mail::Address.new(
|
13
|
+
m = Mail::Address.new(email)
|
8
14
|
# We must check that value contains a domain and that value is an email address
|
9
|
-
r = m.domain && m.address ==
|
15
|
+
r = m.domain && m.address == email
|
10
16
|
t = m.__send__(:tree)
|
11
17
|
# We need to dig into treetop
|
12
18
|
# A valid domain must have dot_atom_text elements size > 1
|
@@ -18,6 +24,6 @@ class EmailValidator < ActiveModel::EachValidator
|
|
18
24
|
rescue Exception => e
|
19
25
|
r = false
|
20
26
|
end
|
21
|
-
|
27
|
+
r
|
22
28
|
end
|
23
29
|
end
|
data/lib/spree/core/version.rb
CHANGED
data/lib/spree/i18n.rb
CHANGED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Spree
|
2
|
+
class Migrations
|
3
|
+
attr_reader :config, :engine_name
|
4
|
+
|
5
|
+
# Takes the engine config block and engine name
|
6
|
+
def initialize(config, engine_name)
|
7
|
+
@config, @engine_name = config, engine_name
|
8
|
+
end
|
9
|
+
|
10
|
+
# Puts warning when any engine migration is not present on the Rails app
|
11
|
+
# db/migrate dir
|
12
|
+
#
|
13
|
+
# First split:
|
14
|
+
#
|
15
|
+
# ["20131128203548", "update_name_fields_on_spree_credit_cards.spree.rb"]
|
16
|
+
#
|
17
|
+
# Second split should give the engine_name of the migration
|
18
|
+
#
|
19
|
+
# ["update_name_fields_on_spree_credit_cards", "spree.rb"]
|
20
|
+
#
|
21
|
+
# Shouldn't run on test mode because migrations inside engine don't have
|
22
|
+
# engine name on the file name
|
23
|
+
def check
|
24
|
+
if File.exists?("config/spree.yml") && File.directory?("db/migrate")
|
25
|
+
engine_in_app = app_migrations.map do |file_name|
|
26
|
+
name, engine = file_name.split(".", 2)
|
27
|
+
next unless engine == "#{engine_name}.rb"
|
28
|
+
name
|
29
|
+
end.compact! || []
|
30
|
+
|
31
|
+
unless (engine_migrations.sort - engine_in_app.sort).empty?
|
32
|
+
puts "[#{engine_name.capitalize} WARNING] Missing migrations." \
|
33
|
+
"Run `bundle exec rake railties:install:migrations` to get them.\n\n"
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def engine_migrations
|
41
|
+
Dir.entries("#{config.root}/db/migrate").map do |file_name|
|
42
|
+
name = file_name.split("_", 2).last.split(".", 2).first
|
43
|
+
name.empty? ? next : name
|
44
|
+
end.compact! || []
|
45
|
+
end
|
46
|
+
|
47
|
+
def app_migrations
|
48
|
+
Dir.entries("db/migrate").map do |file_name|
|
49
|
+
next if [".", ".."].include? file_name
|
50
|
+
name = file_name.split("_", 2).last
|
51
|
+
name.empty? ? next : name
|
52
|
+
end.compact! || []
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/spree/money.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'money'
|
2
4
|
|
3
5
|
module Spree
|
@@ -7,7 +9,7 @@ module Spree
|
|
7
9
|
delegate :cents, :to => :money
|
8
10
|
|
9
11
|
def initialize(amount, options={})
|
10
|
-
@money =
|
12
|
+
@money = self.class.parse([amount, (options[:currency] || Spree::Config[:currency])].join)
|
11
13
|
@options = {}
|
12
14
|
@options[:with_currency] = Spree::Config[:display_currency]
|
13
15
|
@options[:symbol_position] = Spree::Config[:currency_symbol_position].to_sym
|
@@ -20,6 +22,174 @@ module Spree
|
|
20
22
|
@options[:symbol_position] = @options[:symbol_position].to_sym
|
21
23
|
end
|
22
24
|
|
25
|
+
# This method is being deprecated in Money 6.1.0, so now lives here.
|
26
|
+
def self.parse(input, currency = nil)
|
27
|
+
i = input.to_s.strip
|
28
|
+
|
29
|
+
# raise Money::Currency.table.collect{|c| c[1][:symbol]}.inspect
|
30
|
+
|
31
|
+
# Check the first character for a currency symbol, alternatively get it
|
32
|
+
# from the stated currency string
|
33
|
+
c = if ::Money.assume_from_symbol && i =~ /^(\$|€|£)/
|
34
|
+
case i
|
35
|
+
when /^\$/ then "USD"
|
36
|
+
when /^€/ then "EUR"
|
37
|
+
when /^£/ then "GBP"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
i[/[A-Z]{2,3}/]
|
41
|
+
end
|
42
|
+
|
43
|
+
# check that currency passed and embedded currency are the same,
|
44
|
+
# and negotiate the final currency
|
45
|
+
if currency.nil? and c.nil?
|
46
|
+
currency = ::Money.default_currency
|
47
|
+
elsif currency.nil?
|
48
|
+
currency = c
|
49
|
+
elsif c.nil?
|
50
|
+
currency = currency
|
51
|
+
elsif currency != c
|
52
|
+
# TODO: ParseError
|
53
|
+
raise ArgumentError, "Mismatching Currencies"
|
54
|
+
end
|
55
|
+
currency = ::Money::Currency.wrap(currency)
|
56
|
+
|
57
|
+
fractional = extract_cents(i, currency)
|
58
|
+
::Money.new(fractional, currency)
|
59
|
+
end
|
60
|
+
|
61
|
+
# This method is being deprecated in Money 6.1.0, so now lives here.
|
62
|
+
def self.extract_cents(input, currency = Money.default_currency)
|
63
|
+
# remove anything that's not a number, potential thousands_separator, or minus sign
|
64
|
+
num = input.gsub(/[^\d.,'-]/, '')
|
65
|
+
|
66
|
+
# set a boolean flag for if the number is negative or not
|
67
|
+
negative = num =~ /^-|-$/ ? true : false
|
68
|
+
|
69
|
+
# decimal mark character
|
70
|
+
decimal_char = currency.decimal_mark
|
71
|
+
|
72
|
+
# if negative, remove the minus sign from the number
|
73
|
+
# if it's not negative, the hyphen makes the value invalid
|
74
|
+
if negative
|
75
|
+
num = num.sub(/^-|-$/, '')
|
76
|
+
end
|
77
|
+
|
78
|
+
raise ArgumentError, "Invalid currency amount (hyphen)" if num.include?('-')
|
79
|
+
|
80
|
+
#if the number ends with punctuation, just throw it out. If it means decimal,
|
81
|
+
#it won't hurt anything. If it means a literal period or comma, this will
|
82
|
+
#save it from being mis-interpreted as a decimal.
|
83
|
+
num.chop! if num.match(/[\.|,]$/)
|
84
|
+
|
85
|
+
# gather all decimal_marks within the result number
|
86
|
+
used_delimiters = num.scan(/[^\d]/)
|
87
|
+
|
88
|
+
# determine the number of unique decimal_marks within the number
|
89
|
+
#
|
90
|
+
# e.g.
|
91
|
+
# $1,234,567.89 would return 2 (, and .)
|
92
|
+
# $125,00 would return 1
|
93
|
+
# $199 would return 0
|
94
|
+
# $1 234,567.89 would raise an error (decimal_marks are space, comma, and period)
|
95
|
+
case used_delimiters.uniq.length
|
96
|
+
# no decimal_mark or thousands_separator; major (dollars) is the number, and minor (cents) is 0
|
97
|
+
when 0 then major, minor = num, 0
|
98
|
+
|
99
|
+
# two decimal_marks, so we know the last item in this array is the
|
100
|
+
# major/minor thousands_separator and the rest are decimal_marks
|
101
|
+
when 2
|
102
|
+
thousands_separator, decimal_mark = used_delimiters.uniq
|
103
|
+
|
104
|
+
# remove all thousands_separator, split on the decimal_mark
|
105
|
+
major, minor = num.gsub(thousands_separator, '').split(decimal_mark)
|
106
|
+
min = 0 unless min
|
107
|
+
when 1
|
108
|
+
# we can't determine if the comma or period is supposed to be a decimal_mark or a thousands_separator
|
109
|
+
# e.g.
|
110
|
+
# 1,00 - comma is a thousands_separator
|
111
|
+
# 1.000 - period is a thousands_separator
|
112
|
+
# 1,000 - comma is a decimal_mark
|
113
|
+
# 1,000,000 - comma is a decimal_mark
|
114
|
+
# 10000,00 - comma is a thousands_separator
|
115
|
+
# 1000,000 - comma is a thousands_separator
|
116
|
+
|
117
|
+
# assign first decimal_mark for reusability
|
118
|
+
decimal_mark = used_delimiters.first
|
119
|
+
|
120
|
+
# When we have identified the decimal mark character
|
121
|
+
if decimal_char == decimal_mark
|
122
|
+
major, minor = num.split(decimal_char)
|
123
|
+
|
124
|
+
else
|
125
|
+
# decimal_mark is used as a decimal_mark when there are multiple instances, always
|
126
|
+
if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
|
127
|
+
major, minor = num.gsub(decimal_mark, ''), 0
|
128
|
+
else
|
129
|
+
# ex: 1,000 - 1.0000 - 10001.000
|
130
|
+
# split number into possible major (dollars) and minor (cents) values
|
131
|
+
possible_major, possible_minor = num.split(decimal_mark)
|
132
|
+
possible_major ||= "0"
|
133
|
+
possible_minor ||= "00"
|
134
|
+
|
135
|
+
# if the minor (cents) length isn't 3, assign major/minor from the possibles
|
136
|
+
# e.g.
|
137
|
+
# 1,00 => 1.00
|
138
|
+
# 1.0000 => 1.00
|
139
|
+
# 1.2 => 1.20
|
140
|
+
if possible_minor.length != 3 # thousands_separator
|
141
|
+
major, minor = possible_major, possible_minor
|
142
|
+
else
|
143
|
+
# minor length is three
|
144
|
+
# let's try to figure out intent of the thousands_separator
|
145
|
+
|
146
|
+
# the major length is greater than three, which means
|
147
|
+
# the comma or period is used as a thousands_separator
|
148
|
+
# e.g.
|
149
|
+
# 1000,000
|
150
|
+
# 100000,000
|
151
|
+
if possible_major.length > 3
|
152
|
+
major, minor = possible_major, possible_minor
|
153
|
+
else
|
154
|
+
# number is in format ###{sep}### or ##{sep}### or #{sep}###
|
155
|
+
# handle as , is sep, . is thousands_separator
|
156
|
+
if decimal_mark == '.'
|
157
|
+
major, minor = possible_major, possible_minor
|
158
|
+
else
|
159
|
+
major, minor = "#{possible_major}#{possible_minor}", 0
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
else
|
166
|
+
# TODO: ParseError
|
167
|
+
raise ArgumentError, "Invalid currency amount"
|
168
|
+
end
|
169
|
+
|
170
|
+
# build the string based on major/minor since decimal_mark/thousands_separator have been removed
|
171
|
+
# avoiding floating point arithmetic here to ensure accuracy
|
172
|
+
cents = (major.to_i * currency.subunit_to_unit)
|
173
|
+
# Because of an bug in JRuby, we can't just call #floor
|
174
|
+
minor = minor.to_s
|
175
|
+
minor = if minor.size < currency.decimal_places
|
176
|
+
(minor + ("0" * currency.decimal_places))[0,currency.decimal_places].to_i
|
177
|
+
elsif minor.size > currency.decimal_places
|
178
|
+
if minor[currency.decimal_places,1].to_i >= 5
|
179
|
+
minor[0,currency.decimal_places].to_i+1
|
180
|
+
else
|
181
|
+
minor[0,currency.decimal_places].to_i
|
182
|
+
end
|
183
|
+
else
|
184
|
+
minor.to_i
|
185
|
+
end
|
186
|
+
|
187
|
+
cents += minor
|
188
|
+
|
189
|
+
# if negative, multiply by -1; otherwise, return positive cents
|
190
|
+
negative ? cents * -1 : cents
|
191
|
+
end
|
192
|
+
|
23
193
|
def to_s
|
24
194
|
@money.format(@options)
|
25
195
|
end
|