shopify-money 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +14 -0
- data/README.md +1 -1
- data/lib/money/helpers.rb +6 -6
- data/lib/money/version.rb +1 -1
- data/lib/money_column/active_record_hooks.rb +79 -47
- data/spec/core_extensions_spec.rb +1 -1
- data/spec/helpers_spec.rb +4 -4
- data/spec/money_column_spec.rb +27 -1
- data/spec/money_parser_spec.rb +1 -1
- data/spec/money_spec.rb +8 -12
- metadata +3 -3
- data/circle.yml +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 964eb834ae37a4f224f6ce79f5dcd75ae544d8a6
|
4
|
+
data.tar.gz: 400e4c5737f8960c44ebb8d89b78f8fcc7506db2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11c8e67a85e4709618df2f7443bd6102929e12883339fa8ad55ea7c9a2b2c3b951fe980250ef421b0b7d8066770eac6bad9fe6ea74493095722b00d610ae2b28
|
7
|
+
data.tar.gz: 2b4abc9631217467ac967254cd41e5921635acacc351fd69d949fb51e342a6fe8bcf437a0c75d01b078dc5182bd598c157e5c7a1b3089df128832f648e93d4a6
|
data/.travis.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
cache: bundler
|
4
|
+
branches:
|
5
|
+
only:
|
6
|
+
- master
|
7
|
+
rvm:
|
8
|
+
- 2.5
|
9
|
+
- 2.4
|
10
|
+
- 2.3
|
11
|
+
before_install:
|
12
|
+
# https://github.com/travis-ci/travis-ci/issues/8978#issuecomment-354036443
|
13
|
+
- gem update --system
|
14
|
+
- gem install bundler
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# money
|
2
2
|
|
3
|
-
[![Build Status](https://
|
3
|
+
[![Build Status](https://travis-ci.org/Shopify/money.svg?branch=master)](https://travis-ci.org/Shopify/money) [![codecov](https://codecov.io/gh/Shopify/money/branch/master/graph/badge.svg)](https://codecov.io/gh/Shopify/money)
|
4
4
|
|
5
5
|
|
6
6
|
money_column expects a decimal 8,3 database field.
|
data/lib/money/helpers.rb
CHANGED
@@ -6,7 +6,7 @@ class Money
|
|
6
6
|
module_function
|
7
7
|
|
8
8
|
NUMERIC_REGEX = /\A\s*[\+\-]?\d*(\.\d+)?\s*\z/
|
9
|
-
DECIMAL_ZERO = BigDecimal
|
9
|
+
DECIMAL_ZERO = BigDecimal(0).freeze
|
10
10
|
MAX_DECIMAL = 21
|
11
11
|
|
12
12
|
def value_to_decimal(num)
|
@@ -19,11 +19,11 @@ class Money
|
|
19
19
|
when nil, 0, ''
|
20
20
|
DECIMAL_ZERO
|
21
21
|
when Integer
|
22
|
-
BigDecimal
|
22
|
+
BigDecimal(num)
|
23
23
|
when Float
|
24
|
-
BigDecimal
|
24
|
+
BigDecimal(num, Float::DIG)
|
25
25
|
when Rational
|
26
|
-
BigDecimal
|
26
|
+
BigDecimal(num, MAX_DECIMAL)
|
27
27
|
when String
|
28
28
|
string_to_decimal(num)
|
29
29
|
else
|
@@ -57,12 +57,12 @@ class Money
|
|
57
57
|
|
58
58
|
def string_to_decimal(num)
|
59
59
|
if num =~ NUMERIC_REGEX
|
60
|
-
return BigDecimal
|
60
|
+
return BigDecimal(num)
|
61
61
|
end
|
62
62
|
|
63
63
|
Money.deprecate("using Money.new('#{num}') is deprecated and will raise an ArgumentError in the next major release")
|
64
64
|
begin
|
65
|
-
BigDecimal
|
65
|
+
BigDecimal(num)
|
66
66
|
rescue ArgumentError
|
67
67
|
DECIMAL_ZERO
|
68
68
|
end
|
data/lib/money/version.rb
CHANGED
@@ -5,7 +5,7 @@ module MoneyColumn
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def reload(*)
|
8
|
-
clear_money_column_cache
|
8
|
+
clear_money_column_cache if persisted?
|
9
9
|
super
|
10
10
|
end
|
11
11
|
|
@@ -17,7 +17,7 @@ module MoneyColumn
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def clear_money_column_cache
|
20
|
-
@money_column_cache.clear
|
20
|
+
@money_column_cache.clear
|
21
21
|
end
|
22
22
|
|
23
23
|
def init_internals
|
@@ -25,69 +25,101 @@ module MoneyColumn
|
|
25
25
|
super
|
26
26
|
end
|
27
27
|
|
28
|
+
def read_money_attribute(column)
|
29
|
+
column = column.to_s
|
30
|
+
options = self.class.money_column_options[column]
|
31
|
+
|
32
|
+
return @money_column_cache[column] if @money_column_cache[column]
|
33
|
+
|
34
|
+
value = self[column]
|
35
|
+
|
36
|
+
return if value.nil? && !options[:coerce_null]
|
37
|
+
|
38
|
+
@money_column_cache[column] = Money.new(value, options[:currency] || send(options[:currency_column]))
|
39
|
+
end
|
40
|
+
|
41
|
+
def write_money_attribute(column, money)
|
42
|
+
column = column.to_s
|
43
|
+
options = self.class.money_column_options[column]
|
44
|
+
|
45
|
+
@money_column_cache[column] = nil
|
46
|
+
|
47
|
+
if money.blank?
|
48
|
+
return self[column] = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
currency_raw_source = options[:currency] || (send(options[:currency_column]) rescue nil)
|
52
|
+
currency_source = Money::Helpers.value_to_currency(currency_raw_source)
|
53
|
+
|
54
|
+
if !money.is_a?(Money)
|
55
|
+
return self[column] = Money.new(money, currency_source).value
|
56
|
+
end
|
57
|
+
|
58
|
+
if currency_raw_source && !currency_source.compatible?(money.currency)
|
59
|
+
Money.deprecate("[money_column] currency mismatch between #{currency_source} and #{money.currency}.")
|
60
|
+
end
|
61
|
+
|
62
|
+
self[column] = money.value
|
63
|
+
self[options[:currency_column]] = money.currency.to_s unless options[:currency_read_only] || money.no_currency?
|
64
|
+
end
|
65
|
+
|
28
66
|
module ClassMethods
|
67
|
+
attr_reader :money_column_options
|
68
|
+
|
29
69
|
def money_column(*columns, currency_column: nil, currency: nil, currency_read_only: false, coerce_null: false)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
70
|
+
@money_column_options ||= {}
|
71
|
+
|
72
|
+
options = normalize_money_column_options(
|
73
|
+
currency_column: currency_column,
|
74
|
+
currency: currency,
|
75
|
+
currency_read_only: currency_read_only,
|
76
|
+
coerce_null: coerce_null
|
77
|
+
)
|
78
|
+
|
79
|
+
if options[:currency_column]
|
80
|
+
clear_cache_on_currency_change(options[:currency_column])
|
39
81
|
end
|
40
|
-
currency_column = currency_column.to_s.freeze
|
41
82
|
|
42
83
|
columns.flatten.each do |column|
|
43
84
|
column_string = column.to_s.freeze
|
85
|
+
|
86
|
+
@money_column_options[column_string] = options
|
87
|
+
|
44
88
|
attribute(column_string, MoneyColumn::ActiveRecordType.new)
|
45
|
-
|
46
|
-
|
89
|
+
|
90
|
+
define_method column do
|
91
|
+
read_money_attribute(column_string)
|
92
|
+
end
|
93
|
+
|
94
|
+
define_method "#{column}=" do |money|
|
95
|
+
write_money_attribute(column_string, money)
|
96
|
+
end
|
47
97
|
end
|
48
98
|
end
|
49
99
|
|
50
100
|
private
|
51
101
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
102
|
+
def normalize_money_column_options(options)
|
103
|
+
raise ArgumentError, 'cannot set both :currency_column and :currency options' if options[:currency] && options[:currency_column]
|
104
|
+
raise ArgumentError, 'must set one of :currency_column or :currency options' unless options[:currency] || options[:currency_column]
|
105
|
+
|
106
|
+
if options[:currency]
|
107
|
+
options[:currency] = Money::Currency.find!(options[:currency]).to_s.freeze
|
108
|
+
options[:currency_read_only] = true
|
56
109
|
end
|
57
|
-
end
|
58
110
|
|
59
|
-
|
60
|
-
|
61
|
-
return @money_column_cache[column] if @money_column_cache[column]
|
62
|
-
value = read_attribute(column)
|
63
|
-
return if value.nil? && !coerce_null
|
64
|
-
iso = currency_iso || send(currency_column)
|
65
|
-
@money_column_cache[column] = Money.new(value, iso)
|
111
|
+
if options[:currency_column]
|
112
|
+
options[:currency_column] = options[:currency_column].to_s.freeze
|
66
113
|
end
|
114
|
+
options
|
67
115
|
end
|
68
116
|
|
69
|
-
def
|
70
|
-
|
71
|
-
@money_column_cache[column] = nil
|
72
|
-
|
73
|
-
if money.blank?
|
74
|
-
write_attribute(column, nil)
|
75
|
-
return nil
|
76
|
-
end
|
77
|
-
|
78
|
-
currency_raw_source = currency_iso || (send(currency_column) rescue nil)
|
79
|
-
currency_source = Money::Helpers.value_to_currency(currency_raw_source)
|
80
|
-
|
81
|
-
if !money.is_a?(Money)
|
82
|
-
return write_attribute(column, Money.new(money, currency_source).value)
|
83
|
-
end
|
84
|
-
|
85
|
-
if currency_raw_source && !currency_source.compatible?(money.currency)
|
86
|
-
Money.deprecate("[money_column] currency mismatch between #{currency_source} and #{money.currency}.")
|
87
|
-
end
|
117
|
+
def clear_cache_on_currency_change(currency_column)
|
118
|
+
return if money_column_options.any? { |_, opt| opt[:currency_column] == currency_column }
|
88
119
|
|
89
|
-
|
90
|
-
|
120
|
+
define_method "#{currency_column}=" do |value|
|
121
|
+
clear_money_column_cache
|
122
|
+
super(value)
|
91
123
|
end
|
92
124
|
end
|
93
125
|
end
|
data/spec/helpers_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe Money::Helpers do
|
4
4
|
|
5
5
|
describe 'value_to_decimal' do
|
6
|
-
let (:amount) { BigDecimal
|
6
|
+
let (:amount) { BigDecimal('1.23') }
|
7
7
|
let (:money) { Money.new(amount) }
|
8
8
|
|
9
9
|
it 'returns the value of a money object' do
|
@@ -11,7 +11,7 @@ RSpec.describe Money::Helpers do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'returns itself if it is already a big decimal' do
|
14
|
-
expect(subject.value_to_decimal(BigDecimal
|
14
|
+
expect(subject.value_to_decimal(BigDecimal('1.23'))).to eq(amount)
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'returns zero when nil' do
|
@@ -23,7 +23,7 @@ RSpec.describe Money::Helpers do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'returns the bigdecimal version of a integer' do
|
26
|
-
expect(subject.value_to_decimal(1)).to eq(BigDecimal
|
26
|
+
expect(subject.value_to_decimal(1)).to eq(BigDecimal('1'))
|
27
27
|
end
|
28
28
|
|
29
29
|
it 'returns the bigdecimal version of a float' do
|
@@ -60,7 +60,7 @@ RSpec.describe Money::Helpers do
|
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'returns regular zero for a negative zero value' do
|
63
|
-
expect(subject.value_to_decimal(-BigDecimal
|
63
|
+
expect(subject.value_to_decimal(-BigDecimal(0))).to eq(BigDecimal(0))
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
data/spec/money_column_spec.rb
CHANGED
@@ -38,6 +38,17 @@ class MoneyWithDelegatedCurrency < ActiveRecord::Base
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
class MoneyWithCustomAccessors < ActiveRecord::Base
|
42
|
+
self.table_name = 'money_records'
|
43
|
+
money_column :price, currency_column: 'currency'
|
44
|
+
def price
|
45
|
+
read_money_attribute(:price)
|
46
|
+
end
|
47
|
+
def price=(value)
|
48
|
+
write_money_attribute(:price, value + 1)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
41
52
|
RSpec.describe 'MoneyColumn' do
|
42
53
|
let(:amount) { 1.23 }
|
43
54
|
let(:currency) { 'EUR' }
|
@@ -167,7 +178,7 @@ RSpec.describe 'MoneyColumn' do
|
|
167
178
|
end
|
168
179
|
|
169
180
|
it 'raises an ArgumentError' do
|
170
|
-
expect { subject }.to raise_error(ArgumentError, 'cannot set both currency_column and
|
181
|
+
expect { subject }.to raise_error(ArgumentError, 'cannot set both :currency_column and :currency options')
|
171
182
|
end
|
172
183
|
end
|
173
184
|
|
@@ -295,4 +306,19 @@ RSpec.describe 'MoneyColumn' do
|
|
295
306
|
expect(MoneyRecord.find_by(price: nil)).to eq(record)
|
296
307
|
end
|
297
308
|
end
|
309
|
+
|
310
|
+
describe 'money column attribute accessors' do
|
311
|
+
it 'allows to overwrite the setter' do
|
312
|
+
amount = Money.new(1, 'USD')
|
313
|
+
expect(MoneyWithCustomAccessors.new(price: amount).price).to eq(MoneyRecord.new(price: amount).price + 1)
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'correctly assigns the money_column_cache' do
|
317
|
+
amount = Money.new(1, 'USD')
|
318
|
+
object = MoneyWithCustomAccessors.new(price: amount)
|
319
|
+
expect(object.instance_variable_get(:@money_column_cache)['price']).to eql(nil)
|
320
|
+
expect(object.price).to eql(amount + 1)
|
321
|
+
expect(object.instance_variable_get(:@money_column_cache)['price']).to eql(amount + 1)
|
322
|
+
end
|
323
|
+
end
|
298
324
|
end
|
data/spec/money_parser_spec.rb
CHANGED
data/spec/money_spec.rb
CHANGED
@@ -71,15 +71,15 @@ RSpec.describe "Money" do
|
|
71
71
|
end
|
72
72
|
|
73
73
|
it "is constructable with a BigDecimal" do
|
74
|
-
expect(Money.new(BigDecimal
|
74
|
+
expect(Money.new(BigDecimal("1.23"))).to eq(Money.new(1.23))
|
75
75
|
end
|
76
76
|
|
77
|
-
it "is constructable with
|
77
|
+
it "is constructable with an Integer" do
|
78
78
|
expect(Money.new(3)).to eq(Money.new(3.00))
|
79
79
|
end
|
80
80
|
|
81
81
|
it "is construcatable with a Float" do
|
82
|
-
expect(Money.new(3.00)).to eq(Money.new(BigDecimal
|
82
|
+
expect(Money.new(3.00)).to eq(Money.new(BigDecimal('3.00')))
|
83
83
|
end
|
84
84
|
|
85
85
|
it "is construcatable with a String" do
|
@@ -282,7 +282,7 @@ RSpec.describe "Money" do
|
|
282
282
|
end
|
283
283
|
|
284
284
|
it "supports to_d" do
|
285
|
-
expect(Money.new(1.29).to_d).to eq(BigDecimal
|
285
|
+
expect(Money.new(1.29).to_d).to eq(BigDecimal('1.29'))
|
286
286
|
end
|
287
287
|
|
288
288
|
it "supports to_f" do
|
@@ -434,7 +434,7 @@ RSpec.describe "Money" do
|
|
434
434
|
end
|
435
435
|
|
436
436
|
it "returns cents as a decimal value = 1.00" do
|
437
|
-
expect(money.value).to eq(BigDecimal
|
437
|
+
expect(money.value).to eq(BigDecimal("1.00"))
|
438
438
|
end
|
439
439
|
|
440
440
|
it "returns cents as 100 cents" do
|
@@ -445,10 +445,6 @@ RSpec.describe "Money" do
|
|
445
445
|
expect(money.subunits).to eq(100)
|
446
446
|
end
|
447
447
|
|
448
|
-
it "returns cents as a Fixnum" do
|
449
|
-
expect(money.subunits).to be_an_instance_of(Fixnum)
|
450
|
-
end
|
451
|
-
|
452
448
|
it "is greater than $0" do
|
453
449
|
expect(money).to be > Money.new(0.00)
|
454
450
|
end
|
@@ -625,7 +621,7 @@ RSpec.describe "Money" do
|
|
625
621
|
let (:money) { Money.new(1.125) }
|
626
622
|
|
627
623
|
it "rounds 3rd decimal place" do
|
628
|
-
expect(money.value).to eq(BigDecimal
|
624
|
+
expect(money.value).to eq(BigDecimal("1.13"))
|
629
625
|
end
|
630
626
|
end
|
631
627
|
|
@@ -673,7 +669,7 @@ RSpec.describe "Money" do
|
|
673
669
|
it "accepts numeric values" do
|
674
670
|
expect(Money.from_amount(1)).to eq Money.from_cents(1_00)
|
675
671
|
expect(Money.from_amount(1.0)).to eq Money.from_cents(1_00)
|
676
|
-
expect(Money.from_amount(BigDecimal
|
672
|
+
expect(Money.from_amount(BigDecimal("1"))).to eq Money.from_cents(1_00)
|
677
673
|
end
|
678
674
|
|
679
675
|
it "accepts string values" do
|
@@ -688,7 +684,7 @@ RSpec.describe "Money" do
|
|
688
684
|
expect { Money.from_amount(1, "CAD") }.to_not raise_error
|
689
685
|
end
|
690
686
|
|
691
|
-
it "accepts
|
687
|
+
it "accepts Rational number" do
|
692
688
|
expect(Money.from_amount(Rational("999999999999999999.999")).value).to eql(BigDecimal.new("1000000000000000000", Money::Helpers::MAX_DECIMAL))
|
693
689
|
expect(Money.from_amount(Rational("999999999999999999.99")).value).to eql(BigDecimal.new("999999999999999999.99", Money::Helpers::MAX_DECIMAL))
|
694
690
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shopify-money
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -118,12 +118,12 @@ files:
|
|
118
118
|
- ".document"
|
119
119
|
- ".gitignore"
|
120
120
|
- ".rspec"
|
121
|
+
- ".travis.yml"
|
121
122
|
- Gemfile
|
122
123
|
- LICENSE.txt
|
123
124
|
- README.md
|
124
125
|
- Rakefile
|
125
126
|
- bin/console
|
126
|
-
- circle.yml
|
127
127
|
- config/currency_historic.json
|
128
128
|
- config/currency_iso.json
|
129
129
|
- config/currency_non_iso.json
|