foreign_currency_exchange 0.1.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f712145f1831ac7938c72551ea7573f15cc58098
4
+ data.tar.gz: 981ba95ba055ce1523bdf4bda7a5fa4c48f75950
5
+ SHA512:
6
+ metadata.gz: 5d3b01d2b81041af0db49cb6f65ab90e20d990e9776b4b365c562f8aa2e0bbcd2ce9de09fd89c1fed3fd55ae259896f80cb3de5619e21419f285e6631895eedb
7
+ data.tar.gz: 9ac7c85f292e5335929dd2046a655efe7da2915cb1f64dfc9ed480bf8d1bac5e1c8c26e6f7463743dc7ffd0e5d838a67974bddd0617d660974ad8f91476d55b2
@@ -0,0 +1,3 @@
1
+ # Created by .ignore support plugin (hsz.mobi)
2
+ .idea
3
+ .ruby-*
@@ -0,0 +1,19 @@
1
+ # See full list of defaults here: https://github.com/bbatsov/rubocop/blob/master/config/default.yml
2
+ # To see all cops used see here: https://github.com/bbatsov/rubocop/blob/master/config/enabled.yml
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 2.3
6
+ DisplayCopNames: true
7
+
8
+ LineLength:
9
+ Max: 120
10
+
11
+ Style/OpMethod:
12
+ Enabled: false
13
+
14
+ Style/FrozenStringLiteralComment:
15
+ Enabled: false
16
+
17
+ Metrics/BlockLength:
18
+ Exclude:
19
+ - '**/*_spec.rb'
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in foreign_currency_exchange.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rspec', require: false
8
+ end
9
+
10
+ group :development do
11
+ gem 'rubocop'
12
+ end
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ foreign_currency_exchange (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.3.0)
10
+ diff-lcs (1.3)
11
+ parser (2.4.0.0)
12
+ ast (~> 2.2)
13
+ powerpack (0.1.1)
14
+ rainbow (2.2.2)
15
+ rake
16
+ rake (12.0.0)
17
+ rspec (3.5.0)
18
+ rspec-core (~> 3.5.0)
19
+ rspec-expectations (~> 3.5.0)
20
+ rspec-mocks (~> 3.5.0)
21
+ rspec-core (3.5.4)
22
+ rspec-support (~> 3.5.0)
23
+ rspec-expectations (3.5.0)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.5.0)
26
+ rspec-mocks (3.5.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.5.0)
29
+ rspec-support (3.5.0)
30
+ rubocop (0.48.1)
31
+ parser (>= 2.3.3.1, < 3.0)
32
+ powerpack (~> 0.1)
33
+ rainbow (>= 1.99.1, < 3.0)
34
+ ruby-progressbar (~> 1.7)
35
+ unicode-display_width (~> 1.0, >= 1.0.1)
36
+ ruby-progressbar (1.8.1)
37
+ unicode-display_width (1.2.1)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ foreign_currency_exchange!
44
+ rspec
45
+ rubocop
46
+
47
+ BUNDLED WITH
48
+ 1.13.6
@@ -0,0 +1,56 @@
1
+ ### Installation
2
+
3
+ ```bash
4
+ gem install foreign_currency_exchange
5
+ ```
6
+
7
+ ### Usage
8
+
9
+ ```ruby
10
+ require 'foreign_currency_exchange'
11
+
12
+ # Configure the currency rates with respect to a base currency (here EUR):
13
+
14
+ ForeignCurrencyExchange::Money.conversion_rates(
15
+ 'EUR',
16
+ 'USD' => 1.11,
17
+ 'Bitcoin' => 0.0047
18
+ )
19
+
20
+ # Instantiate money objects:
21
+
22
+ fifty_eur = ForeignCurrencyExchange::Money.new(50, 'EUR')
23
+
24
+ # Get amount and currency:
25
+
26
+ fifty_eur.amount # => 50
27
+ fifty_eur.currency # => "EUR"
28
+ fifty_eur.inspect # => "50.00 EUR"
29
+
30
+ # Convert to a different currency (should return a Money
31
+ # instance, not a String):
32
+
33
+ fifty_eur.convert_to('USD') # => 55.50 USD
34
+
35
+ # Perform operations in different currencies:
36
+
37
+ twenty_dollars = ForeignCurrencyExchange::Money.new(20, 'USD')
38
+
39
+ # Arithmetics:
40
+
41
+ fifty_eur + twenty_dollars # => 68.02 EUR
42
+ fifty_eur - twenty_dollars # => 31.98 EUR
43
+ fifty_eur / 2 # => 25 EUR
44
+ twenty_dollars * 3 # => 60 USD
45
+
46
+ # Comparisons (also in different currencies):
47
+
48
+ twenty_dollars == ForeignCurrencyExchange::Money.new(20, 'USD') # => true
49
+ twenty_dollars == ForeignCurrencyExchange::Money.new(30, 'USD') # => false
50
+
51
+ fifty_eur_in_usd = fifty_eur.convert_to('USD')
52
+ fifty_eur_in_usd == fifty_eur # => true
53
+
54
+ twenty_dollars > ForeignCurrencyExchange::Money.new(5, 'USD') # => true
55
+ twenty_dollars < fifty_eur # => true
56
+ ```
@@ -0,0 +1,16 @@
1
+ require File.expand_path('../lib/foreign_currency_exchange/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'foreign_currency_exchange'
5
+ gem.version = ForeignCurrencyExchange::VERSION
6
+ gem.date = '2017-04-24'
7
+ gem.summary = 'Foreign currency exchange'
8
+ gem.description = 'Ruby gem to perform currency conversion and arithmetic with different currencies'
9
+ gem.authors = ['Konstantin Lynda']
10
+ gem.email = 'knlynda@gmail.com'
11
+ gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
12
+ gem.test_files = gem.files.grep(%r{^spec/})
13
+ gem.require_paths = ['lib']
14
+ gem.homepage = 'http://rubygems.org/gems/foreign_currency_exchange'
15
+ gem.license = 'MIT'
16
+ end
@@ -0,0 +1,7 @@
1
+ require 'forwardable'
2
+
3
+ # This is main namespace for the library
4
+ module ForeignCurrencyExchange
5
+ end
6
+
7
+ require 'foreign_currency_exchange/money'
@@ -0,0 +1,94 @@
1
+ require 'foreign_currency_exchange/money/exceptions'
2
+ require 'foreign_currency_exchange/money/configuration'
3
+
4
+ module ForeignCurrencyExchange
5
+ # This class represents a money object.
6
+ class Money
7
+ AMOUNT_FORMAT = '%.2f'.freeze
8
+ ROUND_LEVEL = 2
9
+
10
+ extend Configuration
11
+ extend Forwardable
12
+ include Comparable
13
+
14
+ attr_reader :amount, :currency
15
+ def_delegators :'self.class', :base_currency, :rates, :new, :check_configuration
16
+
17
+ def initialize(amount, currency)
18
+ check_configuration # Configuration should be valid.
19
+ check_currency(currency) # Currency should be configured before.
20
+ @amount = amount.is_a?(Integer) ? amount : amount.to_f.round(ROUND_LEVEL)
21
+ @currency = currency
22
+ end
23
+
24
+ # Returns current object amount if currency equals to base_currency
25
+ # or converted current object amount to base_currency otherwise.
26
+ def base_amount
27
+ return amount if currency == base_currency
28
+ (amount / rates[currency].to_f).round(ROUND_LEVEL)
29
+ end
30
+
31
+ def <=>(other)
32
+ base_amount <=> other.base_amount
33
+ end
34
+
35
+ def +(other)
36
+ new_amount = amount + other.convert_to(currency).amount
37
+ new(new_amount, currency)
38
+ end
39
+
40
+ def -(other)
41
+ new_amount = amount - other.convert_to(currency).amount
42
+ new(new_amount, currency)
43
+ end
44
+
45
+ # Returns new money object with multiplied current object amount,
46
+ # parameter multiplier should be kind of Numeric object.
47
+ def *(multiplier)
48
+ new(amount * multiplier, currency)
49
+ end
50
+
51
+ # Returns new money object with divided current object amount,
52
+ # parameter divider should be kind of Numeric object.
53
+ def /(divider)
54
+ raise ZeroDivisionError if divider.zero?
55
+ new(amount / divider.to_f, currency)
56
+ end
57
+
58
+ def to_s
59
+ "#{formatted_amount} #{currency}"
60
+ end
61
+
62
+ def inspect
63
+ to_s
64
+ end
65
+
66
+ # Returns new money object converted to given currency,
67
+ # raises UnknownCurrencyError in case when given currency is unknown.
68
+ def convert_to(other_currency)
69
+ new_amount = calculate_amount(other_currency)
70
+ new(new_amount, other_currency)
71
+ end
72
+
73
+ private
74
+
75
+ def formatted_amount
76
+ amount.is_a?(Integer) ? amount.to_s : format(AMOUNT_FORMAT, amount)
77
+ end
78
+
79
+ # Returns amount based on given currency,
80
+ # raises UnknownCurrencyError in case when given other_currency is unknown.
81
+ def calculate_amount(other_currency)
82
+ check_currency(other_currency)
83
+ return amount if other_currency == currency
84
+ return base_amount if other_currency == base_currency
85
+ base_amount * rates[other_currency]
86
+ end
87
+
88
+ # Checks that currency is known otherwise raises UnknownCurrencyError.
89
+ def check_currency(currency)
90
+ return if base_currency == currency || rates.keys.include?(currency)
91
+ raise UnknownCurrencyError
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,49 @@
1
+ module ForeignCurrencyExchange
2
+ class Money
3
+ # This module contains configuration methods for Money class
4
+ module Configuration
5
+ BASE_CURRENCY_RATE = 1.0
6
+
7
+ attr_reader :base_currency, :rates
8
+
9
+ def conversion_rates(base_currency, rates)
10
+ @rates = rates
11
+ @base_currency = base_currency
12
+ check_configuration
13
+ rescue => exception
14
+ nullify_configuration
15
+ raise exception
16
+ end
17
+
18
+ def check_configuration
19
+ check_rates
20
+ check_base_currency_rate
21
+ end
22
+
23
+ private
24
+
25
+ def nullify_configuration
26
+ @rates = nil
27
+ @base_currency = nil
28
+ end
29
+
30
+ def check_rates
31
+ raise InvalidRatesError unless valid_rates?
32
+ end
33
+
34
+ def check_base_currency_rate
35
+ raise InvalidBaseCurrencyRateError unless valid_base_currency_rate?
36
+ end
37
+
38
+ def valid_rates?
39
+ rates.is_a?(Hash) && !rates.empty? &&
40
+ rates.values.all? { |v| v.is_a?(Numeric) && v.positive? }
41
+ end
42
+
43
+ def valid_base_currency_rate?
44
+ !rates.key?(base_currency) ||
45
+ rates[base_currency] == BASE_CURRENCY_RATE
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ module ForeignCurrencyExchange
2
+ class Money
3
+ UnknownCurrencyError = Class.new(StandardError)
4
+ InvalidRatesError = Class.new(StandardError)
5
+ InvalidBaseCurrencyRateError = Class.new(StandardError)
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module ForeignCurrencyExchange
2
+ VERSION = '0.1.1'.freeze
3
+ end
File without changes
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ForeignCurrencyExchange::Money do
4
+ before do
5
+ described_class.conversion_rates(
6
+ 'EUR',
7
+ 'USD' => 1.11,
8
+ 'Bitcoin' => 0.0047
9
+ )
10
+ end
11
+
12
+ describe '.new' do
13
+ subject { described_class.new(amount, currency) }
14
+
15
+ context 'when known currency' do
16
+ let(:currency) { 'USD' }
17
+ let(:amount) { 50 }
18
+
19
+ it { is_expected.to be_an_instance_of described_class }
20
+ it { expect(subject.currency).to eql currency }
21
+ it { expect(subject.amount).to eql amount }
22
+ end
23
+
24
+ context 'when currency is base currency' do
25
+ let(:currency) { 'EUR' }
26
+ let(:amount) { 100 }
27
+
28
+ it { is_expected.to be_an_instance_of described_class }
29
+ it { expect(subject.currency).to eql currency }
30
+ it { expect(subject.amount).to eql amount }
31
+ end
32
+
33
+ context 'when unknown currency' do
34
+ let(:currency) { 'UAH' }
35
+ let(:amount) { 30 }
36
+
37
+ it 'raises UnknownCurrencyError' do
38
+ expect { subject }.to raise_error described_class::UnknownCurrencyError
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '#base_amount' do
44
+ subject { money_object.base_amount }
45
+ let(:money_object) { described_class.new(10, currency) }
46
+
47
+ context 'when object currency is base currency' do
48
+ let(:currency) { 'EUR' }
49
+ it { is_expected.to eql 10 }
50
+ end
51
+
52
+ context 'when object currency is known currency' do
53
+ let(:currency) { 'USD' }
54
+ it { is_expected.to eql 9.01 }
55
+ end
56
+
57
+ context 'when object currency is known currency' do
58
+ let(:currency) { 'Bitcoin' }
59
+ it { is_expected.to eql 2127.66 }
60
+ end
61
+ end
62
+
63
+ describe '#<=>' do
64
+ subject { money_object <=> other_money_object }
65
+
66
+ context 'when same amount and currency' do
67
+ let(:money_object) { described_class.new(5.01, 'EUR') }
68
+ let(:other_money_object) { described_class.new(5.01, 'EUR') }
69
+
70
+ it { is_expected.to be_zero }
71
+ end
72
+
73
+ context 'when amount greater then other amount (0.001)' do
74
+ let(:money_object) { described_class.new(5.012, 'EUR') }
75
+ let(:other_money_object) { described_class.new(5.011, 'EUR') }
76
+
77
+ it { is_expected.to be_zero }
78
+ end
79
+
80
+ context 'when amount greater then other amount (0.01)' do
81
+ let(:money_object) { described_class.new(5.02, 'EUR') }
82
+ let(:other_money_object) { described_class.new(5.01, 'EUR') }
83
+
84
+ it { is_expected.to eql 1 }
85
+ end
86
+
87
+ context 'when amount less then other amount (0.01)' do
88
+ let(:money_object) { described_class.new(5.01, 'EUR') }
89
+ let(:other_money_object) { described_class.new(5.02, 'EUR') }
90
+
91
+ it { is_expected.to eql(-1) }
92
+ end
93
+ end
94
+
95
+ describe '#+' do
96
+ subject { money_object + other_money_object }
97
+
98
+ let(:money_object) { described_class.new(5, 'EUR') }
99
+ let(:other_money_object) { described_class.new(10, 'EUR') }
100
+
101
+ it { expect(subject.amount).to eql 15 }
102
+ it { expect(subject.currency).to eql 'EUR' }
103
+
104
+ context 'when different currencies' do
105
+ let(:money_object) { described_class.new(5, 'USD') }
106
+ let(:other_money_object) { described_class.new(10, 'EUR') }
107
+
108
+ it { expect(subject.amount).to eql 16.1 }
109
+ it { expect(subject.currency).to eql 'USD' }
110
+ end
111
+ end
112
+
113
+ describe '#-' do
114
+ subject { money_object - other_money_object }
115
+
116
+ let(:money_object) { described_class.new(10, 'EUR') }
117
+ let(:other_money_object) { described_class.new(5, 'EUR') }
118
+
119
+ it { expect(subject.amount).to eql 5 }
120
+ it { expect(subject.currency).to eql 'EUR' }
121
+
122
+ context 'when different currencies' do
123
+ let(:money_object) { described_class.new(10, 'USD') }
124
+ let(:other_money_object) { described_class.new(5, 'EUR') }
125
+
126
+ it { expect(subject.amount).to eql 4.45 }
127
+ it { expect(subject.currency).to eql 'USD' }
128
+ end
129
+ end
130
+
131
+ describe '#*' do
132
+ subject { described_class.new(10, 'EUR') * 10.00001 }
133
+
134
+ it { expect(subject.amount).to eql 100.0 }
135
+ it { expect(subject.currency).to eql 'EUR' }
136
+ end
137
+
138
+ describe '#/' do
139
+ subject { described_class.new(10, 'EUR') / 10.00001 }
140
+
141
+ it { expect(subject.amount).to eql 1.0 }
142
+ it { expect(subject.currency).to eql 'EUR' }
143
+
144
+ context 'when divider is zero' do
145
+ subject { described_class.new(10, 'EUR') / 0 }
146
+
147
+ it 'raises ZeroDivisionError' do
148
+ expect { subject }.to raise_error ZeroDivisionError
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '#to_s' do
154
+ context 'when amount is Fixnum' do
155
+ subject { described_class.new(10, 'EUR').to_s }
156
+ it { is_expected.to eql '10 EUR' }
157
+ end
158
+
159
+ context 'when amount is Bignum' do
160
+ subject { described_class.new(999_999_999_999_999_999_999, 'EUR').to_s }
161
+ it { is_expected.to eql '999999999999999999999 EUR' }
162
+ end
163
+
164
+ context 'when amount is Float' do
165
+ subject { described_class.new(5.5555, 'EUR').to_s }
166
+ it { is_expected.to eql '5.56 EUR' }
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'foreign_currency_exchange'
5
+
6
+ RSpec.configure(&:disable_monkey_patching!)
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foreign_currency_exchange
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Konstantin Lynda
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby gem to perform currency conversion and arithmetic with different
14
+ currencies
15
+ email: knlynda@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rubocop.yml"
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - README.md
25
+ - foreign_currency_exchange.gemspec
26
+ - lib/foreign_currency_exchange.rb
27
+ - lib/foreign_currency_exchange/money.rb
28
+ - lib/foreign_currency_exchange/money/configuration.rb
29
+ - lib/foreign_currency_exchange/money/exceptions.rb
30
+ - lib/foreign_currency_exchange/version.rb
31
+ - spec/lib/money/configuration_spec.rb
32
+ - spec/lib/money_spec.rb
33
+ - spec/spec_helper.rb
34
+ homepage: http://rubygems.org/gems/foreign_currency_exchange
35
+ licenses:
36
+ - MIT
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.5.1
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: Foreign currency exchange
58
+ test_files:
59
+ - spec/lib/money/configuration_spec.rb
60
+ - spec/lib/money_spec.rb
61
+ - spec/spec_helper.rb