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

Fix thousand separator issue in validation #712

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Gemfile.lock
.rvmrc
.rbenv-version
.ruby-version
.ruby-gemset

# dummy dbs
*.sqlite3
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ platforms :jruby do
end

platforms :ruby do
gem "sqlite3"
gem "sqlite3", "~> 1.4"
end

platform :mri do
Expand Down
3 changes: 1 addition & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ def run_with_gemfile(gemfile)
Bundler.with_original_env do
begin
sh "BUNDLE_GEMFILE='#{gemfile}' bundle install --quiet"
Rake.application['app:db:create'].invoke
Rake.application['app:db:test:prepare'].invoke
Rake.application['prepare_test_env'].invoke
sh "BUNDLE_GEMFILE='#{gemfile}' bundle exec rake spec"
ensure
Rake.application['app:db:drop:all'].execute
Expand Down
81 changes: 35 additions & 46 deletions lib/money-rails/active_model/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,44 @@ def abs_raw_value
def decimal_pieces
@decimal_pieces ||= abs_raw_value.split(decimal_mark)
end

def has_too_many_decimal_points?
decimal_pieces.length > 2
end

def thousand_separator_after_decimal_mark?
return false unless thousands_separator.present?

decimal_pieces.length == 2 && decimal_pieces[1].include?(thousands_separator)
end

def invalid_thousands_separation?
pieces_array = decimal_pieces[0].split(thousands_separator.presence)

return false if pieces_array.length <= 1
return true if pieces_array[0].length > 3

pieces_array[1..-1].any? do |thousands_group|
thousands_group.length != 3
end
end

# Remove thousands separators, normalize decimal mark,
# remove whitespaces and _ (E.g. 99 999 999 or 12_300_200.20)
def normalize
raw_value.to_s
.gsub(thousands_separator, '')
.gsub(decimal_mark, '.')
.gsub(/[\s_]/, '')
end
end

def validate_each(record, attr, _value)
subunit_attr = record.class.monetized_attributes[attr.to_s]
currency = record.public_send("currency_for_#{attr}")

# WARNING: Currently this is only defined in ActiveRecord extension!
before_type_cast = :"#{attr}_money_before_type_cast"
raw_value = record.try(before_type_cast)

# If raw value is nil and changed subunit is nil, then
# nil is a assigned value, else we should treat the
# subunit value as the one assigned.
if raw_value.nil? && record.public_send(subunit_attr)
subunit_value = record.public_send(subunit_attr)
raw_value = subunit_value.to_f / currency.subunit_to_unit
end
raw_value = record.try(before_type_cast) || record.public_send(attr)

return if options[:allow_nil] && raw_value.nil?

Expand All @@ -44,17 +65,16 @@ def validate_each(record, attr, _value)
# Cache abs_raw_value before normalizing because it's used in
# many places and relies on the original raw_value.
details = generate_details(raw_value, currency)
normalized_raw_value = normalize(details)
normalized_raw_value = details.normalize

super(record, attr, normalized_raw_value)

return unless stringy
return if record_already_has_error?(record, attr, normalized_raw_value)

add_error!(record, attr, details) if
value_has_too_many_decimal_points(details) ||
thousand_separator_after_decimal_mark(details) ||
invalid_thousands_separation(details)
add_error!(record, attr, details) if details.has_too_many_decimal_points? ||
details.thousand_separator_after_decimal_mark? ||
details.invalid_thousands_separation?
end

private
Expand Down Expand Up @@ -87,37 +107,6 @@ def add_error!(record, attr, details)
)
end

def value_has_too_many_decimal_points(details)
![1, 2].include?(details.decimal_pieces.length)
end

def thousand_separator_after_decimal_mark(details)
details.thousands_separator.present? &&
details.decimal_pieces.length == 2 &&
details.decimal_pieces[1].include?(details.thousands_separator)
end

def invalid_thousands_separation(details)
pieces_array = details.decimal_pieces[0].split(details.thousands_separator.presence)

return false if pieces_array.length <= 1
return true if pieces_array[0].length > 3

pieces_array[1..-1].any? do |thousands_group|
thousands_group.length != 3
end
end

# Remove thousands separators, normalize decimal mark,
# remove whitespaces and _ (E.g. 99 999 999 or 12_300_200.20)
def normalize(details)
details.raw_value
.to_s
.gsub(details.thousands_separator, '')
.gsub(details.decimal_mark, '.')
.gsub(/[\s_]/, '')
end

def lookup(key, currency)
if locale_backend
locale_backend.lookup(key, currency) || DEFAULTS[key]
Expand Down
Loading