Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Spree::Money autoloadable #6040

Merged
merged 2 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down
120 changes: 120 additions & 0 deletions core/app/models/spree/money.rb
Original file line number Diff line number Diff line change
@@ -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
1 change: 0 additions & 1 deletion core/lib/spree/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ class GatewayError < RuntimeError; end

require 'spree/i18n'
require 'spree/localized_number'
require 'spree/money'
tvdeyen marked this conversation as resolved.
Show resolved Hide resolved
require 'spree/permitted_attributes'

require 'spree/core/importer'
Expand Down
123 changes: 5 additions & 118 deletions core/lib/spree/money.rb
Original file line number Diff line number Diff line change
@@ -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 <<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
Spree.deprecator.warn(
<<~MSG
The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded.
MSG
)
Loading