amountable 0.1.0 → 0.2.0
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/README.md +0 -1
- data/lib/amountable.rb +39 -33
- data/lib/amountable/amount.rb +12 -38
- data/lib/amountable/nil_amount.rb +9 -0
- data/lib/amountable/operations.rb +27 -0
- data/lib/amountable/version.rb +1 -1
- data/spec/amountable/amount_spec.rb +12 -12
- data/spec/amountable/amountable_spec.rb +9 -9
- data/spec/amountable/nil_amount_spec.rb +10 -10
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 70fd4bd67ff8f0f3c0a67ea5701fb035934c3a8f
|
|
4
|
+
data.tar.gz: 3a7f3ce3ff221dcbe13378f3ebe18958385e787d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b785e2e1a02a280a3e6125c4e3731b1281eb02760a17405f669e1d1669d089131a3c9e01cc084ffa2bc6673ca3721dcc5060c974e892aaad973d2deb2d3097a8
|
|
7
|
+
data.tar.gz: 3063523fcfa424dc576bdb2ad3cf7a2e2eca9378098cde60a06fbb150d40c8a6313ab7e4cd77f6a6c51e04ed9ecc8b1c0b3d2a38e9a849fc80fb51a3c96b32f3
|
data/README.md
CHANGED
data/lib/amountable.rb
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Amountable
|
|
4
4
|
extend ActiveSupport::Autoload
|
|
5
|
+
autoload :Operations
|
|
5
6
|
autoload :Amount
|
|
7
|
+
autoload :NilAmount
|
|
6
8
|
autoload :VERSION
|
|
7
9
|
autoload :TableMethods
|
|
8
10
|
autoload :JsonbMethods
|
|
@@ -13,46 +15,38 @@ module Amountable
|
|
|
13
15
|
ALLOWED_STORAGE = %i(table json).freeze
|
|
14
16
|
|
|
15
17
|
def self.included(base)
|
|
16
|
-
|
|
17
18
|
base.extend Amountable::ClassMethods
|
|
19
|
+
end
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
validate :validate_amount_names
|
|
21
|
-
class_attribute :amount_names
|
|
22
|
-
class_attribute :amount_sets
|
|
23
|
-
class_attribute :amounts_column_name
|
|
24
|
-
self.amount_sets = Hash.new { |h, k| h[k] = Set.new }
|
|
25
|
-
self.amount_names = Set.new
|
|
26
|
-
self.amounts_column_name = 'amounts'
|
|
21
|
+
module InstanceMethods
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
def all_amounts
|
|
24
|
+
@all_amounts ||= amounts.to_set
|
|
25
|
+
end
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
def find_amount(name)
|
|
28
|
+
(@amounts_by_name ||= {})[name.to_sym] ||= amounts.to_set.find { |am| am.name == name.to_s }
|
|
29
|
+
end
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
def find_amounts(names)
|
|
32
|
+
amounts.to_set.select { |am| names.include?(am.name.to_sym) }
|
|
33
|
+
end
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
end
|
|
35
|
+
def validate_amount_names
|
|
36
|
+
amounts.each do |amount|
|
|
37
|
+
errors.add(:amounts, "#{amount.name} is not an allowed amount name.") unless self.class.allowed_amount_name?(amount.name)
|
|
44
38
|
end
|
|
39
|
+
end
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
end
|
|
54
|
-
base.merge!(amounts_json)
|
|
41
|
+
def serializable_hash(opts = nil)
|
|
42
|
+
opts ||= {}
|
|
43
|
+
super(opts).tap do |base|
|
|
44
|
+
unless opts[:except].to_a.include?(:amounts)
|
|
45
|
+
amounts_json = (self.class.amount_names + self.class.amount_sets.keys).inject({}) do |mem, name|
|
|
46
|
+
mem.merge!(name.to_s => send(name).to_f) unless opts[:except].to_a.include?(name.to_sym)
|
|
47
|
+
mem
|
|
55
48
|
end
|
|
49
|
+
base.merge!(amounts_json)
|
|
56
50
|
end
|
|
57
51
|
end
|
|
58
52
|
end
|
|
@@ -62,9 +56,15 @@ module Amountable
|
|
|
62
56
|
|
|
63
57
|
# Possible storage values: [:table, :jsonb]
|
|
64
58
|
def act_as_amountable(options = {})
|
|
59
|
+
class_attribute :amount_names
|
|
60
|
+
class_attribute :amount_sets
|
|
61
|
+
class_attribute :amounts_column_name
|
|
62
|
+
self.amount_sets = Hash.new { |h, k| h[k] = Set.new }
|
|
63
|
+
self.amount_names = Set.new
|
|
64
|
+
self.amounts_column_name = 'amounts'
|
|
65
65
|
case (options[:storage] || :table).to_sym
|
|
66
66
|
when :table
|
|
67
|
-
has_many :amounts, as: :amountable, dependent: :destroy, autosave: false
|
|
67
|
+
has_many :amounts, class_name: 'Amountable::Amount', as: :amountable, dependent: :destroy, autosave: false
|
|
68
68
|
include Amountable::TableMethods
|
|
69
69
|
when :jsonb
|
|
70
70
|
self.amounts_column_name = options[:column].to_s if options[:column]
|
|
@@ -73,6 +73,8 @@ module Amountable
|
|
|
73
73
|
else
|
|
74
74
|
raise ArgumentError.new("Please specify a storage: #{ALLOWED_STORAGE}")
|
|
75
75
|
end
|
|
76
|
+
validate :validate_amount_names
|
|
77
|
+
include Amountable::InstanceMethods
|
|
76
78
|
end
|
|
77
79
|
|
|
78
80
|
def amount_set(set_name, component)
|
|
@@ -87,7 +89,7 @@ module Amountable
|
|
|
87
89
|
(self.amount_names ||= Set.new) << name
|
|
88
90
|
|
|
89
91
|
define_method name do
|
|
90
|
-
(find_amount(name) || ::NilAmount.new).value
|
|
92
|
+
(find_amount(name) || Amountable::NilAmount.new).value
|
|
91
93
|
end
|
|
92
94
|
|
|
93
95
|
define_method "#{name}=" do |value|
|
|
@@ -105,3 +107,7 @@ module Amountable
|
|
|
105
107
|
|
|
106
108
|
end
|
|
107
109
|
end
|
|
110
|
+
|
|
111
|
+
ActiveSupport.on_load(:active_record) do
|
|
112
|
+
include Amountable
|
|
113
|
+
end
|
data/lib/amountable/amount.rb
CHANGED
|
@@ -1,49 +1,23 @@
|
|
|
1
1
|
# Copyright 2015-2016, Instacart
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
module Amountable
|
|
4
|
+
class Amount < ActiveRecord::Base
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
include Amountable::Operations
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
belongs_to :amountable, polymorphic: true
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
validates :name, uniqueness: {scope: [:amountable_id, :amountable_type]}
|
|
10
|
+
monetize :value_cents
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
validates :name, presence: true
|
|
13
|
+
validates :name, uniqueness: {scope: [:amountable_id, :amountable_type]}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
raise StandardError.new("Can't persist amount to database") if persistable == false
|
|
16
|
-
super
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
module Operations
|
|
20
|
-
|
|
21
|
-
def +(other_value)
|
|
22
|
-
value + other_value.to_money
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def -(other_value)
|
|
26
|
-
value - other_value.to_money
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def *(multiplier)
|
|
30
|
-
value * multiplier
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def /(divisor)
|
|
34
|
-
value / divisor
|
|
35
|
-
end
|
|
15
|
+
attr_accessor :persistable
|
|
36
16
|
|
|
37
|
-
def
|
|
38
|
-
|
|
17
|
+
def save
|
|
18
|
+
raise StandardError.new("Can't persist amount to database") if persistable == false
|
|
19
|
+
super
|
|
39
20
|
end
|
|
40
21
|
|
|
41
22
|
end
|
|
42
|
-
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
class NilAmount
|
|
46
|
-
include Amount::Operations
|
|
47
|
-
def value; Money.zero; end
|
|
48
|
-
def amountable; nil; end
|
|
49
|
-
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Copyright 2015-2016, Instacart
|
|
2
|
+
|
|
3
|
+
module Amountable
|
|
4
|
+
module Operations
|
|
5
|
+
|
|
6
|
+
def +(other_value)
|
|
7
|
+
value + other_value.to_money
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def -(other_value)
|
|
11
|
+
value - other_value.to_money
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def *(multiplier)
|
|
15
|
+
value * multiplier
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def /(divisor)
|
|
19
|
+
value / divisor
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_money
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/amountable/version.rb
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
|
-
describe Amount do
|
|
5
|
+
describe Amountable::Amount do
|
|
6
6
|
|
|
7
7
|
it 'should validate name presence' do
|
|
8
|
-
|
|
8
|
+
subject.tap do |amount|
|
|
9
9
|
expect(amount.valid?).to be false
|
|
10
10
|
expect(amount.errors[:name]).not_to be nil
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
it 'should validate name uniqueness' do
|
|
15
|
-
Amount.new(name: 'test', amountable_id: 1, amountable_type: 'Amountable').tap do |amount|
|
|
15
|
+
Amountable::Amount.new(name: 'test', amountable_id: 1, amountable_type: 'Amountable').tap do |amount|
|
|
16
16
|
expect(amount.valid?).to be true
|
|
17
17
|
amount.save
|
|
18
|
-
Amount.new(name: 'test', amountable_id: 2, amountable_type: 'Amountable').tap do |other_amount|
|
|
18
|
+
Amountable::Amount.new(name: 'test', amountable_id: 2, amountable_type: 'Amountable').tap do |other_amount|
|
|
19
19
|
expect(other_amount.valid?).to be true
|
|
20
20
|
other_amount.amountable_id = amount.amountable_id
|
|
21
21
|
expect(other_amount.valid?).to be false
|
|
@@ -25,17 +25,17 @@ describe Amount do
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
it 'should have operations' do
|
|
28
|
-
expect(Amount.new(value: Money.new(1)) + Amount.new(value: Money.new(2))).to eq(Money.new(3))
|
|
29
|
-
expect(Amount.new(value: Money.new(1)) + 0.02).to eq(Money.new(3))
|
|
30
|
-
expect(Amount.new(value: Money.new(2)) - Amount.new(value: Money.new(1))).to eq(Money.new(1))
|
|
31
|
-
expect(Amount.new(value: Money.new(2)) - 0.01).to eq(Money.new(1))
|
|
32
|
-
expect(Amount.new(value: Money.new(2)) * 3).to eq(Money.new(6))
|
|
33
|
-
expect(Amount.new(value: Money.new(6)) / 3).to eq(Money.new(2))
|
|
34
|
-
expect(Amount.new(value: Money.new(2)).to_money).to eq(Money.new(2))
|
|
28
|
+
expect(Amountable::Amount.new(value: Money.new(1)) + Amountable::Amount.new(value: Money.new(2))).to eq(Money.new(3))
|
|
29
|
+
expect(Amountable::Amount.new(value: Money.new(1)) + 0.02).to eq(Money.new(3))
|
|
30
|
+
expect(Amountable::Amount.new(value: Money.new(2)) - Amountable::Amount.new(value: Money.new(1))).to eq(Money.new(1))
|
|
31
|
+
expect(Amountable::Amount.new(value: Money.new(2)) - 0.01).to eq(Money.new(1))
|
|
32
|
+
expect(Amountable::Amount.new(value: Money.new(2)) * 3).to eq(Money.new(6))
|
|
33
|
+
expect(Amountable::Amount.new(value: Money.new(6)) / 3).to eq(Money.new(2))
|
|
34
|
+
expect(Amountable::Amount.new(value: Money.new(2)).to_money).to eq(Money.new(2))
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
it 'should not save if not persistable' do
|
|
38
|
-
expect {
|
|
38
|
+
expect { subject.new(persistable: false).save }.to raise_exception(StandardError)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
end
|
|
@@ -7,7 +7,7 @@ describe Amountable do
|
|
|
7
7
|
context 'storage == :table' do
|
|
8
8
|
it 'should' do
|
|
9
9
|
order = Order.new
|
|
10
|
-
expect { order.save }.not_to change { Amount.count }
|
|
10
|
+
expect { order.save }.not_to change { Amountable::Amount.count }
|
|
11
11
|
%i(sub_total taxes total).each do |name|
|
|
12
12
|
expect(order.send(name)).to eq(Money.zero)
|
|
13
13
|
end
|
|
@@ -19,12 +19,12 @@ describe Amountable do
|
|
|
19
19
|
expect(amount.name).to eq('sub_total')
|
|
20
20
|
expect(amount.value).to eq(Money.new(100))
|
|
21
21
|
expect(amount.new_record?).to be true
|
|
22
|
-
expect { order.save }.to change { Amount.count }.by(1)
|
|
22
|
+
expect { order.save }.to change { Amountable::Amount.count }.by(1)
|
|
23
23
|
expect(amount.persisted?).to be true
|
|
24
24
|
end
|
|
25
25
|
expect do
|
|
26
26
|
expect(order.update_attributes(sub_total: Money.new(200)))
|
|
27
|
-
end.not_to change { Amount.count }
|
|
27
|
+
end.not_to change { Amountable::Amount.count }
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
describe 'name=' do
|
|
@@ -32,20 +32,20 @@ describe Amountable do
|
|
|
32
32
|
|
|
33
33
|
it 'should not persist Money.zero' do
|
|
34
34
|
expect(order.sub_total = Money.zero).to eq(Money.zero)
|
|
35
|
-
expect { order.save }.not_to change { Amount.count }
|
|
35
|
+
expect { order.save }.not_to change { Amountable::Amount.count }
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
it 'should not persist Money.zero if using ActiveRecord persistence' do
|
|
39
|
-
expect { order.update(sub_total: Money.zero) }.not_to change { Amount.count }
|
|
39
|
+
expect { order.update(sub_total: Money.zero) }.not_to change { Amountable::Amount.count }
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
it 'should work with ActiveRecord#update' do
|
|
43
|
-
expect { order.update(sub_total: Money.new(1)) }.to change { Amount.count }.by(1)
|
|
43
|
+
expect { order.update(sub_total: Money.new(1)) }.to change { Amountable::Amount.count }.by(1)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
it 'should destroy Amount if exist and assigning Money.zero' do
|
|
47
47
|
order.update(sub_total: Money.new(1))
|
|
48
|
-
expect { order.sub_total = Money.zero }.to change { Amount.count }.by(-1)
|
|
48
|
+
expect { order.sub_total = Money.zero }.to change { Amountable::Amount.count }.by(-1)
|
|
49
49
|
expect(order.amounts.empty?).to be true
|
|
50
50
|
end
|
|
51
51
|
end
|
|
@@ -61,7 +61,7 @@ describe Amountable do
|
|
|
61
61
|
context 'storage == :jsonb' do
|
|
62
62
|
it 'should' do
|
|
63
63
|
subscription = Subscription.new
|
|
64
|
-
expect { subscription.save }.not_to change { Amount.count }
|
|
64
|
+
expect { subscription.save }.not_to change { Amountable::Amount.count }
|
|
65
65
|
expect(subscription.amounts).to eq(Set.new)
|
|
66
66
|
expect(subscription.attributes['amounts']).to be_nil
|
|
67
67
|
%i(sub_total taxes total).each do |name|
|
|
@@ -76,7 +76,7 @@ describe Amountable do
|
|
|
76
76
|
expect(amount.name).to eq('sub_total')
|
|
77
77
|
expect(amount.value).to eq(Money.new(100))
|
|
78
78
|
expect(amount.new_record?).to be true
|
|
79
|
-
expect { subscription.save }.not_to change { Amount.count }
|
|
79
|
+
expect { subscription.save }.not_to change { Amountable::Amount.count }
|
|
80
80
|
expect(amount.persisted?).to be false
|
|
81
81
|
end
|
|
82
82
|
subscription.update_attributes(sub_total: Money.new(200))
|
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
|
-
describe NilAmount do
|
|
5
|
+
describe Amountable::NilAmount do
|
|
6
6
|
|
|
7
7
|
it 'should return 0' do
|
|
8
|
-
expect(
|
|
8
|
+
expect(subject.value).to eq(Money.zero)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
it 'should have nil amountable' do
|
|
12
|
-
expect(
|
|
12
|
+
expect(subject.amountable).to be nil
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
it 'should have operations' do
|
|
16
|
-
expect(
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
19
|
-
expect(
|
|
20
|
-
expect(
|
|
21
|
-
expect(
|
|
22
|
-
expect(
|
|
16
|
+
expect(subject + Amountable::Amount.new(value: Money.new(2))).to eq(Money.new(2))
|
|
17
|
+
expect(subject + 0.02).to eq(Money.new(2))
|
|
18
|
+
expect(subject - Amountable::Amount.new(value: Money.new(1))).to eq(Money.new(-1))
|
|
19
|
+
expect(subject - 0.01).to eq(Money.new(-1))
|
|
20
|
+
expect(subject * 3).to eq(Money.zero)
|
|
21
|
+
expect(subject / 3).to eq(Money.zero)
|
|
22
|
+
expect(subject.to_money).to eq(Money.zero)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: amountable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emmanuel Turlay
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2016-10-
|
|
11
|
+
date: 2016-10-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -210,6 +210,8 @@ files:
|
|
|
210
210
|
- lib/amountable.rb
|
|
211
211
|
- lib/amountable/amount.rb
|
|
212
212
|
- lib/amountable/jsonb_methods.rb
|
|
213
|
+
- lib/amountable/nil_amount.rb
|
|
214
|
+
- lib/amountable/operations.rb
|
|
213
215
|
- lib/amountable/table_methods.rb
|
|
214
216
|
- lib/amountable/version.rb
|
|
215
217
|
- spec/amountable/amount_spec.rb
|