diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5bd856f79d9..9f6844d4994 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -166,7 +166,6 @@ Layout/SpaceAroundOperators: - "backend/spec/features/admin/orders/order_details_spec.rb" - "bin/__rspec" - "bin/rspec" - - "core/lib/spree/money.rb" - "core/spec/models/spree/order/number_generator_spec.rb" # Offense count: 8 @@ -413,7 +412,7 @@ Rails/OutputSafety: - "core/app/helpers/spree/base_helper.rb" - "core/app/helpers/spree/checkout_helper.rb" - "core/app/helpers/spree/products_helper.rb" - - "core/lib/spree/money.rb" + - "core/app/models/spree/money.rb" # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). diff --git a/core/app/models/spree/money.rb b/core/app/models/spree/money.rb new file mode 100644 index 00000000000..046a4aa2e0b --- /dev/null +++ b/core/app/models/spree/money.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +module Spree + # Spree::Money is a relatively thin wrapper around Monetize which handles + # formatting via Spree::Config. + class Money + include Comparable + DifferentCurrencyError = Class.new(StandardError) + + class << self + attr_accessor :default_formatting_rules + + def parse(amount, currency = Spree::Config[:currency]) + new(parse_to_money(amount, currency)) + end + + # @api private + def parse_to_money(amount, currency) + ::Monetize.parse(amount, currency) + end + end + self.default_formatting_rules = { + # Ruby money currently has this as false, which is wrong for the vast + # majority of locales. + sign_before_symbol: true + } + + attr_reader :money + + delegate :cents, :currency, :to_d, :zero?, to: :money + + # @param amount [Money, #to_s] the value of the money object + # @param options [Hash] the default options for formatting the money object See #format + def initialize(amount, options = {}) + if amount.is_a?(::Money) + @money = amount + else + currency = options[:currency] || Spree::Config[:currency] + + @money = Monetize.from_string(amount, currency) + end + @options = Spree::Money.default_formatting_rules.merge(options) + end + + # @return [String] the value of this money object formatted according to + # its options + def to_s + format + end + + # @param options [Hash, String] the options for formatting the money object + # @option options [Boolean] with_currency when true, show the currency + # @option options [Boolean] no_cents when true, round to the closest dollar + # @option options [String] decimal_mark the mark for delimiting the + # decimals + # @option options [String, false, nil] thousands_separator the character to + # delimit powers of 1000, if one is desired, otherwise false or nil + # @option options [Boolean] sign_before_symbol when true the sign of the + # value comes before the currency symbol + # @option options [:before, :after] symbol_position the position of the + # currency symbol + # @return [String] the value of this money object formatted according to + # its options + def format(options = {}) + @money.format(@options.merge(options)) + end + + # @note If you pass in options, ensure you pass in the { html_wrap: true } as well. + # @param options [Hash] additional formatting options + # @return [String] the value of this money object formatted according to + # its options and any additional options, by default with html_wrap. + def to_html(options = { html_wrap: true }) + output = format(options) + # Maintain compatibility by checking html option renamed to html_wrap. + if options[:html_wrap] + output = output.html_safe + end + output + end + + # (see #to_s) + def as_json(*) + to_s + end + + def <=>(other) + if !other.respond_to?(:money) + raise TypeError, "Can't compare #{other.class} to Spree::Money" + end + if currency != other.currency + # By default, ::Money will try to run a conversion on `other.money` and + # try a comparison on that. We do not want any currency conversion to + # take place so we'll catch this here and raise an error. + raise( + DifferentCurrencyError, + "Can't compare #{currency} with #{other.currency}" + ) + end + @money <=> other.money + end + + # Delegates comparison to the internal ruby money instance. + # + # @see http://www.rubydoc.info/gems/money/Money/Arithmetic#%3D%3D-instance_method + def ==(other) + raise TypeError, "Can't compare #{other.class} to Spree::Money" if !other.respond_to?(:money) + @money == other.money + end + + def -(other) + raise TypeError, "Can't subtract #{other.class} to Spree::Money" if !other.respond_to?(:money) + self.class.new(@money - other.money) + end + + def +(other) + raise TypeError, "Can't add #{other.class} to Spree::Money" if !other.respond_to?(:money) + self.class.new(@money + other.money) + end + end +end diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb index e5c3fe42794..95475bf1eda 100644 --- a/core/lib/spree/core.rb +++ b/core/lib/spree/core.rb @@ -105,7 +105,6 @@ class GatewayError < RuntimeError; end require 'spree/i18n' require 'spree/localized_number' -require 'spree/money' require 'spree/permitted_attributes' require 'spree/core/importer' diff --git a/core/lib/spree/money.rb b/core/lib/spree/money.rb index 435bc80429f..72f6898b795 100644 --- a/core/lib/spree/money.rb +++ b/core/lib/spree/money.rb @@ -1,120 +1,7 @@ # frozen_string_literal: true -module Spree - # Spree::Money is a relatively thin wrapper around Monetize which handles - # formatting via Spree::Config. - class Money - include Comparable - DifferentCurrencyError = Class.new(StandardError) - - class <(other) - if !other.respond_to?(:money) - raise TypeError, "Can't compare #{other.class} to Spree::Money" - end - if currency != other.currency - # By default, ::Money will try to run a conversion on `other.money` and - # try a comparison on that. We do not want any currency conversion to - # take place so we'll catch this here and raise an error. - raise( - DifferentCurrencyError, - "Can't compare #{currency} with #{other.currency}" - ) - end - @money <=> other.money - end - - # Delegates comparison to the internal ruby money instance. - # - # @see http://www.rubydoc.info/gems/money/Money/Arithmetic#%3D%3D-instance_method - def ==(other) - raise TypeError, "Can't compare #{other.class} to Spree::Money" if !other.respond_to?(:money) - @money == other.money - end - - def -(other) - raise TypeError, "Can't subtract #{other.class} to Spree::Money" if !other.respond_to?(:money) - self.class.new(@money - other.money) - end - - def +(other) - raise TypeError, "Can't add #{other.class} to Spree::Money" if !other.respond_to?(:money) - self.class.new(@money + other.money) - end - end -end +Spree.deprecator.warn( + <<~MSG + The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded. + MSG +) diff --git a/core/spec/lib/spree/money_spec.rb b/core/spec/models/spree/money_spec.rb similarity index 100% rename from core/spec/lib/spree/money_spec.rb rename to core/spec/models/spree/money_spec.rb