danconia 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f379e57876333600210d3f3669e1a45654b8016238abd82df38eaf6674084c5c
4
- data.tar.gz: b85beca8d071d199c13771c14916efc26aac999df2d0dc7bb167597fb5822f29
3
+ metadata.gz: 38d88815b5c3c9155db3c64433d6469614656a2464ab6a0c0eae51b3429b084d
4
+ data.tar.gz: d8962d50c98a68e8acfbc2a1424e81b77b1d506d069d4e87c0fa32803482d2b0
5
5
  SHA512:
6
- metadata.gz: 5712b367efd1638949888f2e1fe254a174cd63c23d7e4d14ee8f9601380f4b0cf376b572642fad40d93b4d1d7b8333de2788eff5042fe29e79c36bb9d444315a
7
- data.tar.gz: b55eab153bed5d5572789b7ed4b7a3cbb6ef8907269a5babfdebe12c1947152baac4c5bde5fc0b4bada904323c6524ceafe0d7991aa64262123362ee22209764
6
+ metadata.gz: 69fa03e8ae6c01a37eaafaf9fe358b1e57eca34bd4e1113fb795002b80d4f5dc2a3934c06f1b3102a3c64e3403e16f3d6fd97c9f301b819f9de6b742c3ea088d
7
+ data.tar.gz: 4881c55540c4b0e51e6018a23aae7923effc31ea9694295aae07a08a62fa60b32d6943adea8bd3798d5349b6f15c97ab2948f6410593c6d03ceb35c136e12c9b
@@ -0,0 +1 @@
1
+ language: ruby
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- danconia (0.2.1)
4
+ danconia (0.2.2)
5
5
  activerecord (>= 3.0.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  TODO: Write a gem description
4
4
 
5
+ [![Build Status](https://travis-ci.org/eeng/danconia.svg?branch=master)](https://travis-ci.org/eeng/danconia)
6
+
5
7
  ## Installation
6
8
 
7
9
  Add this line to your application's Gemfile:
@@ -0,0 +1,21 @@
1
+ require 'danconia'
2
+
3
+ ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
4
+
5
+ ActiveRecord::Schema.define do
6
+ create_table :exchange_rates do |t|
7
+ t.string :pair, limit: 6
8
+ t.decimal :rate, precision: 12, scale: 6
9
+ t.index :pair, unique: true
10
+ end
11
+ end
12
+
13
+ Danconia.configure do |config|
14
+ config.default_exchange = Danconia::Exchanges::CurrencyLayer.new(access_key: ENV['ACCESS_KEY'], store: Danconia::Stores::ActiveRecord.new)
15
+ end
16
+
17
+ # Periodically do this
18
+ puts 'Updating dates with CurrencyLayer API...'
19
+ Danconia.config.default_exchange.update_rates!
20
+
21
+ puts Money(1, 'USD').exchange_to('EUR').inspect # => 0.854896 EUR
@@ -5,4 +5,4 @@ Danconia.configure do |config|
5
5
  config.default_exchange = Danconia::Exchanges::FixedRates.new(rates: {'USDARS' => 27.5, 'USDEUR' => 0.86})
6
6
  end
7
7
 
8
- puts Money(10, 'ARS').exchange_to('EUR').inspect
8
+ puts Money(10, 'ARS').exchange_to('EUR').inspect # => 0.31273 EUR
@@ -4,4 +4,8 @@ require 'danconia/currency'
4
4
  require 'danconia/money'
5
5
  require 'danconia/kernel'
6
6
  require 'danconia/integrations/active_record'
7
+ require 'danconia/exchanges/exchange'
7
8
  require 'danconia/exchanges/fixed_rates'
9
+ require 'danconia/exchanges/currency_layer'
10
+ require 'danconia/stores/in_memory'
11
+ require 'danconia/stores/active_record'
@@ -1,5 +1,3 @@
1
- require 'danconia/exchange'
2
-
3
1
  module Danconia
4
2
  class << self
5
3
  def config
@@ -2,7 +2,7 @@ module Danconia
2
2
  class Currency < Struct.new(:code, :symbol, :description, keyword_init: true)
3
3
  def self.find code, exchange
4
4
  return code if code.is_a? Currency
5
- new exchange.available_currencies.find { |c| c[:code] == code } || {code: code, symbol: '$'}
5
+ new exchange.currencies.find { |c| c[:code] == code } || {code: code, symbol: '$'}
6
6
  end
7
7
  end
8
8
  end
@@ -5,15 +5,11 @@ require 'json'
5
5
  module Danconia
6
6
  module Exchanges
7
7
  class CurrencyLayer < Exchange
8
- def initialize access_key:
9
- super()
8
+ def initialize access_key:, **args
9
+ super args
10
10
  @access_key = access_key
11
11
  end
12
12
 
13
- def update_rates!
14
- @store.save_rates fetch_rates
15
- end
16
-
17
13
  def fetch_rates
18
14
  response = JSON.parse Net::HTTP.get URI "http://www.apilayer.net/api/live?access_key=#{@access_key}"
19
15
  if response['success']
@@ -0,0 +1,32 @@
1
+ module Danconia
2
+ module Exchanges
3
+ class Exchange
4
+ attr_reader :store, :currencies
5
+
6
+ def initialize store: Stores::InMemory.new, currencies: []
7
+ @store = store
8
+ @currencies = currencies
9
+ end
10
+
11
+ def rate from, to
12
+ if from == 'USD' and direct_rate = @store.direct_rate(from, to)
13
+ direct_rate
14
+ elsif to == 'USD' and inverse_rate = @store.direct_rate(to, from)
15
+ (1.0 / inverse_rate).round 6
16
+ elsif from != 'USD' and to != 'USD' and from_in_usd = rate(from, 'USD') and to_per_usd = rate('USD', to)
17
+ (from_in_usd * to_per_usd).round 6
18
+ else
19
+ raise Errors::ExchangeRateNotFound.new(from, to)
20
+ end
21
+ end
22
+
23
+ def rates
24
+ @store.rates
25
+ end
26
+
27
+ def update_rates!
28
+ @store.save_rates fetch_rates
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,9 +1,14 @@
1
1
  module Danconia
2
2
  module Exchanges
3
3
  class FixedRates < Exchange
4
- def initialize rates: {}
5
- super()
6
- @store.save_rates rates
4
+ def initialize rates: {}, **args
5
+ super args
6
+ @rates = rates
7
+ update_rates!
8
+ end
9
+
10
+ def fetch_rates
11
+ @rates
7
12
  end
8
13
  end
9
14
  end
@@ -8,17 +8,21 @@ module Danconia
8
8
 
9
9
  def initialize amount, currency_code = nil, decimals: 2, exchange: Danconia.config.default_exchange
10
10
  @decimals = decimals
11
- @amount = parse(amount)
11
+ @amount = parse amount
12
12
  @currency = Currency.find(currency_code || Danconia.config.default_currency, exchange)
13
13
  @exchange = exchange
14
14
  end
15
15
 
16
- def to_s
16
+ def format decimals: @decimals
17
17
  ActiveSupport::NumberHelper.number_to_currency amount, precision: decimals, unit: currency.symbol
18
18
  end
19
19
 
20
+ def to_s
21
+ format
22
+ end
23
+
20
24
  def inspect
21
- "#<#{self.class.name} #{amount} #{currency.code}>"
25
+ "#{amount} #{currency.code}"
22
26
  end
23
27
 
24
28
  def == other
@@ -43,11 +47,17 @@ module Danconia
43
47
  end
44
48
 
45
49
  def exchange_to other_currency
46
- other_currency = Currency.find(other_currency, exchange)
47
- rate = exchange_rate(currency, other_currency)
50
+ other_currency = other_currency.presence && Currency.find(other_currency, exchange) || currency
51
+ rate = exchange_rate_to(other_currency.code)
48
52
  clone_with amount * rate, other_currency
49
53
  end
50
54
 
55
+ def exchange_rate_to to
56
+ from = currency.code
57
+ return 1 if from == to
58
+ exchange.rate from, to
59
+ end
60
+
51
61
  %w(+ - * /).each do |op|
52
62
  class_eval <<-EOR, __FILE__, __LINE__ + 1
53
63
  def #{op} other
@@ -65,6 +75,10 @@ module Danconia
65
75
  (self * 100).round
66
76
  end
67
77
 
78
+ def default_currency?
79
+ currency.code == Danconia.config.default_currency
80
+ end
81
+
68
82
  def method_missing method, *args
69
83
  if @amount.respond_to? method
70
84
  @amount.send method, *args
@@ -80,16 +94,11 @@ module Danconia
80
94
  private
81
95
 
82
96
  def parse object
83
- BigDecimal(object.to_s) rescue BigDecimal('0')
97
+ BigDecimal(object.to_s) rescue BigDecimal(0)
84
98
  end
85
99
 
86
100
  def clone_with amount, currency = @currency
87
101
  Money.new amount, currency, decimals: decimals, exchange: exchange
88
102
  end
89
-
90
- def exchange_rate from, to
91
- return 1 if from == to
92
- exchange.rate from.code, to.code
93
- end
94
103
  end
95
104
  end
@@ -0,0 +1,24 @@
1
+ module Danconia
2
+ module Stores
3
+ class ActiveRecord
4
+ def save_rates rates
5
+ ExchangeRate.transaction do
6
+ rates.each do |pair, rate|
7
+ ExchangeRate.where(pair: pair).first_or_initialize.update rate: rate
8
+ end
9
+ end
10
+ end
11
+
12
+ def direct_rate from, to
13
+ ExchangeRate.find_by(pair: [from, to].join)&.rate
14
+ end
15
+
16
+ def rates
17
+ ExchangeRate.all
18
+ end
19
+ end
20
+
21
+ class ExchangeRate < ::ActiveRecord::Base
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module Danconia
2
+ module TestHelpers
3
+ class << self
4
+ def with_config &block
5
+ old_config = Danconia.config.dup
6
+ Danconia.configure &block
7
+ Danconia.config = old_config
8
+ end
9
+
10
+ def with_rates rates
11
+ with_config do |config|
12
+ config.default_exchange = Exchanges::FixedRates.new rates: rates, currencies: config.default_exchange.currencies
13
+ yield
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module Danconia
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.2'
3
3
  end
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'danconia/exchanges/currency_layer'
3
2
 
4
3
  module Danconia
5
4
  module Exchanges
@@ -9,11 +9,12 @@ module Danconia
9
9
  end
10
10
 
11
11
  it 'non numeric values are treated as zero' do
12
+ expect(Money(nil)).to eq Money(0)
12
13
  expect(Money('a')).to eq Money(0)
13
14
  end
14
15
 
15
16
  it 'should use the default currency if not specified' do
16
- with_config do |config|
17
+ TestHelpers.with_config do |config|
17
18
  config.default_currency = 'ARS'
18
19
  expect(Money(0).currency.code).to eq 'ARS'
19
20
 
@@ -71,9 +72,7 @@ module Danconia
71
72
  end
72
73
 
73
74
  it 'should exchange to the source currency if they differ' do
74
- with_config do |config|
75
- config.default_exchange = fake_exchange(rate: 4)
76
-
75
+ TestHelpers.with_rates 'USDARS' => 4 do |config|
77
76
  expect(Money(3, 'ARS') < Money(1, 'USD')).to be true
78
77
  expect(Money(4, 'ARS') < Money(1, 'USD')).to be false
79
78
  end
@@ -86,12 +85,18 @@ module Danconia
86
85
  end
87
86
  end
88
87
 
88
+ context 'format' do
89
+ it 'allow to override the number of decimals' do
90
+ expect(Money(3.561, decimals: 3).format(decimals: 1)).to eq '$3.6'
91
+ end
92
+ end
93
+
89
94
  context 'to_s' do
90
95
  it 'should add the currency symbol' do
91
96
  expect(Money(3.25).to_s).to eq '$3.25'
92
97
 
93
- with_config do |config|
94
- config.default_exchange = double 'exchange', available_currencies: [{code: 'EUR', symbol: '€'}, {code: 'JPY', symbol: '¥'}]
98
+ TestHelpers.with_config do |config|
99
+ config.default_exchange = Exchanges::FixedRates.new currencies: [{code: 'EUR', symbol: '€'}, {code: 'JPY', symbol: '¥'}]
95
100
 
96
101
  expect(Money(1, 'EUR').to_s).to eq '€1.00'
97
102
  expect(Money(1, 'JPY').to_s).to eq '¥1.00'
@@ -103,15 +108,11 @@ module Danconia
103
108
  expect(Money(3.256, decimals: 2).to_s).to eq '$3.26'
104
109
  expect(Money(3.2517, decimals: 3).to_s).to eq '$3.252'
105
110
  end
106
-
107
- it 'nil should be like zero' do
108
- expect(Money(nil).to_s).to eq '$0.00'
109
- end
110
111
  end
111
112
 
112
113
  context '.inspect' do
113
114
  it 'should display the object internals' do
114
- expect(Money(10.25, 'ARS', decimals: 3).inspect).to eq '#<Danconia::Money 10.25 ARS>'
115
+ expect(Money(10.25, 'ARS', decimals: 3).inspect).to eq '10.25 ARS'
115
116
  end
116
117
  end
117
118
 
@@ -121,8 +122,7 @@ module Danconia
121
122
  end
122
123
 
123
124
  it 'should use the default exchange if not set' do
124
- with_config do |config|
125
- config.default_exchange = Exchanges::FixedRates.new(rates: {'USDEUR' => 3, 'USDARS' => 4})
125
+ TestHelpers.with_rates 'USDEUR' => 3, 'USDARS' => 4 do
126
126
  expect(Money(2, 'USD').exchange_to('EUR')).to eq Money(6, 'EUR')
127
127
  expect(Money(2, 'USD').exchange_to('ARS')).to eq Money(8, 'ARS')
128
128
  end
@@ -143,6 +143,21 @@ module Danconia
143
143
  expect(m2.decimals).to eq 0
144
144
  expect(m1).to eq Money(1, 'USD')
145
145
  end
146
+
147
+ it 'if the destination currency is blank should not do the conversion' do
148
+ expect(Money(1, 'USD').exchange_to('')).to eq Money(1, 'USD')
149
+ expect(Money(1, 'ARS').exchange_to('')).to eq Money(1, 'ARS')
150
+ end
151
+ end
152
+
153
+ context 'default_currency?' do
154
+ it 'is true if the currency is the configured default' do
155
+ TestHelpers.with_config do |config|
156
+ config.default_currency = 'ARS'
157
+ expect(Money(1, 'ARS')).to be_default_currency
158
+ expect(Money(1, 'USD')).to_not be_default_currency
159
+ end
160
+ end
146
161
  end
147
162
 
148
163
  context 'delegation' do
@@ -153,7 +168,7 @@ module Danconia
153
168
  end
154
169
 
155
170
  def fake_exchange args = {}
156
- double 'exchange', args.reverse_merge(rate: nil, available_currencies: [])
171
+ double 'exchange', args.reverse_merge(rate: nil, currencies: [])
157
172
  end
158
173
  end
159
174
  end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ module Danconia
4
+ module Stores
5
+ describe ActiveRecord, active_record: true do
6
+ context 'save_rates' do
7
+ it 'should create or update the rates' do
8
+ ExchangeRate.create! pair: 'USDEUR', rate: 2
9
+ expect { subject.save_rates 'USDEUR' => 3, 'USDARS' => 4 }.to change { ExchangeRate.count }.by 1
10
+ expect(subject.rates.map { |e| [e.pair, e.rate] }).to eq({'USDEUR' => 3, 'USDARS' => 4}.to_a)
11
+ end
12
+ end
13
+
14
+ context '#direct_rate' do
15
+ it 'should find the rate for the pair' do
16
+ ExchangeRate.create! pair: 'USDEUR', rate: 2
17
+ expect(subject.direct_rate('USD', 'EUR')).to eq 2
18
+ expect(subject.direct_rate('USD', 'ARS')).to eq nil
19
+ end
20
+ end
21
+
22
+ before do
23
+ ::ActiveRecord::Schema.define do
24
+ create_table :exchange_rates do |t|
25
+ t.string :pair, limit: 6
26
+ t.decimal :rate, precision: 12, scale: 6
27
+ t.index :pair, unique: true
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,10 +1,10 @@
1
1
  require 'danconia'
2
+ require 'danconia/test_helpers'
2
3
  require 'webmock/rspec'
3
4
 
4
5
  Dir["#{__dir__}/support/*.rb"].each { |f| require f }
5
6
 
6
7
  RSpec.configure do |config|
7
8
  config.filter_run_when_matching :focus
8
- config.include Helpers
9
9
  config.include DatabaseCleaner
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: danconia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emmanuel Nicolau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-22 00:00:00.000000000 Z
11
+ date: 2018-07-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -118,6 +118,7 @@ files:
118
118
  - ".gitignore"
119
119
  - ".rspec"
120
120
  - ".ruby-version"
121
+ - ".travis.yml"
121
122
  - Gemfile
122
123
  - Gemfile.lock
123
124
  - Guardfile
@@ -125,27 +126,30 @@ files:
125
126
  - README.md
126
127
  - Rakefile
127
128
  - danconia.gemspec
128
- - examples/basic.rb
129
+ - examples/currency_layer.rb
130
+ - examples/fixed_rates.rb
129
131
  - lib/danconia.rb
130
132
  - lib/danconia/config.rb
131
133
  - lib/danconia/currency.rb
132
134
  - lib/danconia/errors/api_error.rb
133
135
  - lib/danconia/errors/exchange_rate_not_found.rb
134
- - lib/danconia/exchange.rb
135
136
  - lib/danconia/exchanges/currency_layer.rb
137
+ - lib/danconia/exchanges/exchange.rb
136
138
  - lib/danconia/exchanges/fixed_rates.rb
137
139
  - lib/danconia/integrations/active_record.rb
138
140
  - lib/danconia/kernel.rb
139
141
  - lib/danconia/money.rb
142
+ - lib/danconia/stores/active_record.rb
140
143
  - lib/danconia/stores/in_memory.rb
144
+ - lib/danconia/test_helpers.rb
141
145
  - lib/danconia/version.rb
142
146
  - spec/danconia/exchanges/currency_layer_spec.rb
143
147
  - spec/danconia/exchanges/fixed_rates_spec.rb
144
148
  - spec/danconia/integrations/active_record_spec.rb
145
149
  - spec/danconia/money_spec.rb
150
+ - spec/danconia/stores/active_record_spec.rb
146
151
  - spec/spec_helper.rb
147
152
  - spec/support/database.rb
148
- - spec/support/helpers.rb
149
153
  homepage: ''
150
154
  licenses: []
151
155
  metadata: {}
@@ -174,6 +178,6 @@ test_files:
174
178
  - spec/danconia/exchanges/fixed_rates_spec.rb
175
179
  - spec/danconia/integrations/active_record_spec.rb
176
180
  - spec/danconia/money_spec.rb
181
+ - spec/danconia/stores/active_record_spec.rb
177
182
  - spec/spec_helper.rb
178
183
  - spec/support/database.rb
179
- - spec/support/helpers.rb
@@ -1,32 +0,0 @@
1
- require 'danconia/stores/in_memory'
2
-
3
- module Danconia
4
- class Exchange
5
- attr_reader :store
6
-
7
- def initialize store: Stores::InMemory.new
8
- @store = store
9
- end
10
-
11
- def rate from, to
12
- if from == 'USD' and direct_rate = @store.direct_rate(from, to)
13
- direct_rate
14
- elsif to == 'USD' and inverse_rate = @store.direct_rate(to, from)
15
- (1.0 / inverse_rate).round 6
16
- elsif from != 'USD' and to != 'USD' and from_in_usd = rate(from, 'USD') and to_per_usd = rate('USD', to)
17
- (from_in_usd * to_per_usd).round 6
18
- else
19
- raise Errors::ExchangeRateNotFound.new(from, to)
20
- end
21
- end
22
-
23
- def rates
24
- @store.rates
25
- end
26
-
27
- # Currently is only needed for money formatting
28
- def available_currencies
29
- []
30
- end
31
- end
32
- end
@@ -1,7 +0,0 @@
1
- module Helpers
2
- def with_config &block
3
- old_config = Danconia.config.dup
4
- Danconia.configure &block
5
- Danconia.config = old_config
6
- end
7
- end