currency-converter 0.1.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +11 -0
- data/.idea/.gitignore +8 -0
- data/.idea/currency_converter.iml +76 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/CHANGELOG.md +6 -1
- data/Gemfile +7 -1
- data/Gemfile.lock +90 -0
- data/README.md +100 -7
- data/currency-converter.gemspec +34 -0
- data/lib/currency_converter/api_client.rb +34 -0
- data/lib/currency_converter/cache.rb +22 -0
- data/lib/currency_converter/config.rb +31 -0
- data/lib/currency_converter/converter.rb +60 -0
- data/lib/currency_converter/errors.rb +9 -0
- data/lib/currency_converter/version.rb +1 -1
- data/lib/currency_converter.rb +8 -29
- data/sig/currency_converter/converter.rbs +9 -0
- data/spec/currency_converter/api_client_spec.rb +24 -0
- data/spec/currency_converter/cache_spec.rb +27 -0
- data/spec/currency_converter/converter_spec.rb +28 -0
- data/spec/spec_helper.rb +16 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e6d4706fb1deb22fa6661dfe2c2832714c52674574ffc83b07a9fa8024b5a4d
|
4
|
+
data.tar.gz: 62dcbb7b3828814a0f1df68e22fbae1fd3f279420990e34663dc295b14bb5920
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c52d11935b1926394ee4601638d3899f2a96d545405c97950e961b1d64d80f9cc5810276f97f7fe19d725c4b6406d24d43a8a9ba66a1dd0dca064ac0b4396ddd
|
7
|
+
data.tar.gz: 8428242fe34991ac477a360af6b3f6b5533de0c96bd4e183888ce8ff9079e781835dc1ceb33892165163db9507a470661eef3cd6e81584f89821ac286200a3a1
|
@@ -0,0 +1,27 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
|
8
|
+
pull_request:
|
9
|
+
|
10
|
+
jobs:
|
11
|
+
build:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
name: Ruby ${{ matrix.ruby }}
|
14
|
+
strategy:
|
15
|
+
matrix:
|
16
|
+
ruby:
|
17
|
+
- '3.0.4'
|
18
|
+
|
19
|
+
steps:
|
20
|
+
- uses: actions/checkout@v2
|
21
|
+
- name: Set up Ruby
|
22
|
+
uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
25
|
+
bundler-cache: true
|
26
|
+
- name: Run the default task
|
27
|
+
run: bundle exec rake
|
data/.gitignore
ADDED
data/.idea/.gitignore
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<module type="RUBY_MODULE" version="4">
|
3
|
+
<component name="ModuleRunConfigurationManager">
|
4
|
+
<shared />
|
5
|
+
</component>
|
6
|
+
<component name="NewModuleRootManager">
|
7
|
+
<content url="file://$MODULE_DIR$">
|
8
|
+
<sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
|
9
|
+
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
10
|
+
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
11
|
+
</content>
|
12
|
+
<orderEntry type="inheritedJdk" />
|
13
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
14
|
+
<orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, rbenv: 3.0.5) [gem]" level="application" />
|
15
|
+
<orderEntry type="library" scope="PROVIDED" name="bundler (v2.2.33, rbenv: 3.0.5) [gem]" level="application" />
|
16
|
+
<orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.1, rbenv: 3.0.5) [gem]" level="application" />
|
17
|
+
<orderEntry type="library" scope="PROVIDED" name="json (v2.5.1, rbenv: 3.0.5) [gem]" level="application" />
|
18
|
+
<orderEntry type="library" scope="PROVIDED" name="language_server-protocol (v3.17.0.3, rbenv: 3.0.5) [gem]" level="application" />
|
19
|
+
<orderEntry type="library" scope="PROVIDED" name="parallel (v1.26.3, rbenv: 3.0.5) [gem]" level="application" />
|
20
|
+
<orderEntry type="library" scope="PROVIDED" name="parser (v3.3.6.0, rbenv: 3.0.5) [gem]" level="application" />
|
21
|
+
<orderEntry type="library" scope="PROVIDED" name="racc (v1.5.2, rbenv: 3.0.5) [gem]" level="application" />
|
22
|
+
<orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, rbenv: 3.0.5) [gem]" level="application" />
|
23
|
+
<orderEntry type="library" scope="PROVIDED" name="rake (v13.0.3, rbenv: 3.0.5) [gem]" level="application" />
|
24
|
+
<orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.9.2, rbenv: 3.0.5) [gem]" level="application" />
|
25
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec (v3.13.0, rbenv: 3.0.5) [gem]" level="application" />
|
26
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.13.2, rbenv: 3.0.5) [gem]" level="application" />
|
27
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.13.3, rbenv: 3.0.5) [gem]" level="application" />
|
28
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.13.2, rbenv: 3.0.5) [gem]" level="application" />
|
29
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.13.1, rbenv: 3.0.5) [gem]" level="application" />
|
30
|
+
<orderEntry type="library" scope="PROVIDED" name="rubocop (v1.68.0, rbenv: 3.0.5) [gem]" level="application" />
|
31
|
+
<orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v1.34.1, rbenv: 3.0.5) [gem]" level="application" />
|
32
|
+
<orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.13.0, rbenv: 3.0.5) [gem]" level="application" />
|
33
|
+
<orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.6.0, rbenv: 3.0.5) [gem]" level="application" />
|
34
|
+
</component>
|
35
|
+
<component name="RakeTasksCache-v2">
|
36
|
+
<option name="myRootTask">
|
37
|
+
<RakeTaskImpl id="rake">
|
38
|
+
<subtasks>
|
39
|
+
<RakeTaskImpl description="Build currency_converter-0.1.0.gem into the pkg directory" fullCommand="build" id="build" />
|
40
|
+
<RakeTaskImpl id="build">
|
41
|
+
<subtasks>
|
42
|
+
<RakeTaskImpl description="Generate SHA512 checksum if currency_converter-0.1.0.gem into the checksums directory" fullCommand="build:checksum" id="checksum" />
|
43
|
+
</subtasks>
|
44
|
+
</RakeTaskImpl>
|
45
|
+
<RakeTaskImpl description="Remove any temporary products" fullCommand="clean" id="clean" />
|
46
|
+
<RakeTaskImpl description="Remove any generated files" fullCommand="clobber" id="clobber" />
|
47
|
+
<RakeTaskImpl description="Build and install currency_converter-0.1.0.gem into system gems" fullCommand="install" id="install" />
|
48
|
+
<RakeTaskImpl id="install">
|
49
|
+
<subtasks>
|
50
|
+
<RakeTaskImpl description="Build and install currency_converter-0.1.0.gem into system gems without network access" fullCommand="install:local" id="local" />
|
51
|
+
</subtasks>
|
52
|
+
</RakeTaskImpl>
|
53
|
+
<RakeTaskImpl description="Create tag v0.1.0 and build and push currency_converter-0.1.0.gem to TODO: Set to your gem server 'https://example.com'" fullCommand="release[remote]" id="release[remote]" />
|
54
|
+
<RakeTaskImpl description="Run RuboCop" fullCommand="rubocop" id="rubocop" />
|
55
|
+
<RakeTaskImpl id="rubocop">
|
56
|
+
<subtasks>
|
57
|
+
<RakeTaskImpl description="Autocorrect RuboCop offenses (only when it's safe)" fullCommand="rubocop:autocorrect" id="autocorrect" />
|
58
|
+
<RakeTaskImpl description="Autocorrect RuboCop offenses (safe and unsafe)" fullCommand="rubocop:autocorrect_all" id="autocorrect_all" />
|
59
|
+
<RakeTaskImpl description="" fullCommand="rubocop:auto_correct" id="auto_correct" />
|
60
|
+
</subtasks>
|
61
|
+
</RakeTaskImpl>
|
62
|
+
<RakeTaskImpl description="Run RSpec code examples" fullCommand="spec" id="spec" />
|
63
|
+
<RakeTaskImpl description="" fullCommand="default" id="default" />
|
64
|
+
<RakeTaskImpl description="" fullCommand="release" id="release" />
|
65
|
+
<RakeTaskImpl id="release">
|
66
|
+
<subtasks>
|
67
|
+
<RakeTaskImpl description="" fullCommand="release:guard_clean" id="guard_clean" />
|
68
|
+
<RakeTaskImpl description="" fullCommand="release:rubygem_push" id="rubygem_push" />
|
69
|
+
<RakeTaskImpl description="" fullCommand="release:source_control_push" id="source_control_push" />
|
70
|
+
</subtasks>
|
71
|
+
</RakeTaskImpl>
|
72
|
+
</subtasks>
|
73
|
+
</RakeTaskImpl>
|
74
|
+
</option>
|
75
|
+
</component>
|
76
|
+
</module>
|
data/.idea/misc.xml
ADDED
data/.idea/modules.xml
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<project version="4">
|
3
|
+
<component name="ProjectModuleManager">
|
4
|
+
<modules>
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/currency_converter.iml" filepath="$PROJECT_DIR$/.idea/currency_converter.iml" />
|
6
|
+
</modules>
|
7
|
+
</component>
|
8
|
+
</project>
|
data/.idea/vcs.xml
ADDED
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
currency-converter (1.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activesupport (6.1.7.10)
|
10
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
11
|
+
i18n (>= 1.6, < 2)
|
12
|
+
minitest (>= 5.1)
|
13
|
+
tzinfo (~> 2.0)
|
14
|
+
zeitwerk (~> 2.3)
|
15
|
+
addressable (2.8.7)
|
16
|
+
public_suffix (>= 2.0.2, < 7.0)
|
17
|
+
ast (2.4.2)
|
18
|
+
bigdecimal (3.1.8)
|
19
|
+
concurrent-ruby (1.3.4)
|
20
|
+
crack (1.0.0)
|
21
|
+
bigdecimal
|
22
|
+
rexml
|
23
|
+
diff-lcs (1.5.1)
|
24
|
+
hashdiff (1.1.2)
|
25
|
+
i18n (1.14.6)
|
26
|
+
concurrent-ruby (~> 1.0)
|
27
|
+
json (2.8.1)
|
28
|
+
language_server-protocol (3.17.0.3)
|
29
|
+
minitest (5.25.1)
|
30
|
+
parallel (1.26.3)
|
31
|
+
parser (3.3.6.0)
|
32
|
+
ast (~> 2.4.1)
|
33
|
+
racc
|
34
|
+
public_suffix (6.0.1)
|
35
|
+
racc (1.8.1)
|
36
|
+
rainbow (3.1.1)
|
37
|
+
rake (13.2.1)
|
38
|
+
regexp_parser (2.9.2)
|
39
|
+
rexml (3.3.9)
|
40
|
+
rspec (3.13.0)
|
41
|
+
rspec-core (~> 3.13.0)
|
42
|
+
rspec-expectations (~> 3.13.0)
|
43
|
+
rspec-mocks (~> 3.13.0)
|
44
|
+
rspec-core (3.13.2)
|
45
|
+
rspec-support (~> 3.13.0)
|
46
|
+
rspec-expectations (3.13.3)
|
47
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
+
rspec-support (~> 3.13.0)
|
49
|
+
rspec-mocks (3.13.2)
|
50
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
51
|
+
rspec-support (~> 3.13.0)
|
52
|
+
rspec-support (3.13.1)
|
53
|
+
rubocop (1.68.0)
|
54
|
+
json (~> 2.3)
|
55
|
+
language_server-protocol (>= 3.17.0)
|
56
|
+
parallel (~> 1.10)
|
57
|
+
parser (>= 3.3.0.2)
|
58
|
+
rainbow (>= 2.2.2, < 4.0)
|
59
|
+
regexp_parser (>= 2.4, < 3.0)
|
60
|
+
rubocop-ast (>= 1.32.2, < 2.0)
|
61
|
+
ruby-progressbar (~> 1.7)
|
62
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
63
|
+
rubocop-ast (1.35.0)
|
64
|
+
parser (>= 3.3.1.0)
|
65
|
+
ruby-progressbar (1.13.0)
|
66
|
+
stringio (3.1.2)
|
67
|
+
tzinfo (2.0.6)
|
68
|
+
concurrent-ruby (~> 1.0)
|
69
|
+
unicode-display_width (2.6.0)
|
70
|
+
webmock (3.24.0)
|
71
|
+
addressable (>= 2.8.0)
|
72
|
+
crack (>= 0.3.2)
|
73
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
74
|
+
zeitwerk (2.6.18)
|
75
|
+
|
76
|
+
PLATFORMS
|
77
|
+
x86_64-darwin-23
|
78
|
+
x86_64-linux
|
79
|
+
|
80
|
+
DEPENDENCIES
|
81
|
+
activesupport (~> 6.1)
|
82
|
+
currency-converter!
|
83
|
+
rake (~> 13.0)
|
84
|
+
rspec (~> 3.0)
|
85
|
+
rubocop (~> 1.21)
|
86
|
+
stringio (~> 3.1.2)
|
87
|
+
webmock
|
88
|
+
|
89
|
+
BUNDLED WITH
|
90
|
+
2.2.33
|
data/README.md
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
# CurrencyConverter
|
2
2
|
|
3
|
-
CurrencyConverter
|
3
|
+
The CurrencyConverter gem provides an easy way to perform currency conversions. It allows you to convert amounts between different currencies using real-time exchange rates from an external API, while offering caching for performance improvements and error handling for robustness.
|
4
|
+
|
5
|
+
Author
|
6
|
+
Developed by [Shobhit Jain](https://github.com/shobhits7).
|
4
7
|
|
5
8
|
## Installation
|
6
9
|
|
7
10
|
Add this line to your application's Gemfile:
|
8
11
|
|
9
12
|
```ruby
|
10
|
-
gem '
|
13
|
+
gem 'currency-converter', '~> 0.1.0'
|
11
14
|
```
|
12
15
|
|
13
16
|
And then execute:
|
@@ -16,18 +19,106 @@ And then execute:
|
|
16
19
|
|
17
20
|
Or install it yourself as:
|
18
21
|
|
19
|
-
$ gem install
|
22
|
+
$ gem install currency-converter
|
23
|
+
|
24
|
+
## Configuration
|
25
|
+
|
26
|
+
Before using the gem, you need to configure it by setting up the necessary API key and cache duration. The gem uses an external API for exchange rates, so you'll need an API key for that.
|
27
|
+
|
28
|
+
Setting up Configuration
|
29
|
+
To get your api key, you can sign up for free at [Exchange Rate API](https://www.exchangerate-api.com/)
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
CurrencyConverter.configure do |config|
|
33
|
+
config.api_key = 'your_api_key_here' # Your API key for the external API
|
34
|
+
config.cache_duration = 60 # Cache duration in seconds
|
35
|
+
config.logger = Logger.new(STDOUT) # Optional: Configure logging
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
> api_key: Your API key for accessing exchange rates from the external provider.
|
40
|
+
|
41
|
+
> cache_duration: The duration for which exchange rates should be cached (in seconds).
|
42
|
+
|
43
|
+
> logger: Optional logging configuration for debugging purposes.
|
44
|
+
|
20
45
|
|
21
46
|
## Usage
|
47
|
+
Creating an Instance of the Converter
|
48
|
+
You can create an instance of the `CurrencyConverter::Converter` class to perform currency conversions.
|
22
49
|
|
23
50
|
```ruby
|
24
|
-
|
51
|
+
# Initialize the converter with the configured API key
|
52
|
+
converter = CurrencyConverter::Converter.new
|
53
|
+
```
|
54
|
+
|
55
|
+
### Converting Currency
|
56
|
+
|
57
|
+
Use the convert method to convert an amount from one currency to another.
|
25
58
|
|
26
|
-
|
27
|
-
|
28
|
-
|
59
|
+
```ruby
|
60
|
+
# Convert 100 USD to EUR
|
61
|
+
amount_in_eur = converter.convert(100, 'USD', 'EUR')
|
62
|
+
puts amount_in_eur
|
29
63
|
```
|
30
64
|
|
65
|
+
> amount: The amount you want to convert.
|
66
|
+
|
67
|
+
> from_currency: The source currency code (e.g., "USD").
|
68
|
+
|
69
|
+
> to_currency: The target currency code (e.g., "EUR").
|
70
|
+
|
71
|
+
This method will return the converted amount as a Float and will raise an error if the conversion fails (e.g., if the exchange rate for the requested currency pair is unavailable).
|
72
|
+
|
73
|
+
## Handling Errors
|
74
|
+
The convert method raises errors in the following situations:
|
75
|
+
|
76
|
+
> Missing Rate: If the exchange rate for the requested currency pair is not available, a CurrencyConverter::APIError is raised.
|
77
|
+
|
78
|
+
> General Conversion Failure: If any other issues occur (e.g., network failure), a StandardError is raised.
|
79
|
+
|
80
|
+
## Example of Handling Errors:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
begin
|
84
|
+
amount_in_eur = converter.convert(100, 'USD', 'EUR')
|
85
|
+
puts "Converted amount: #{amount_in_eur} EUR"
|
86
|
+
rescue CurrencyConverter::APIError => e
|
87
|
+
puts "Conversion failed: #{e.message}"
|
88
|
+
rescue StandardError => e
|
89
|
+
puts "An unexpected error occurred: #{e.message}"
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
## Testing
|
94
|
+
|
95
|
+
The gem is tested with RSpec, and several tests are included for verifying the correctness of functionality such as caching, conversion, and error handling.
|
96
|
+
|
97
|
+
Example Test:
|
98
|
+
```ruby
|
99
|
+
RSpec.describe CurrencyConverter::Converter do
|
100
|
+
let(:converter) { CurrencyConverter::Converter.new }
|
101
|
+
|
102
|
+
it 'performs currency conversion successfully' do
|
103
|
+
result = converter.convert(100, 'USD', 'EUR')
|
104
|
+
expect(result).to be_a(Float)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'raises an error if the conversion fails' do
|
108
|
+
expect { converter.convert(100, 'USD', 'XYZ') }.to raise_error(CurrencyConverter::APIError)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
## Test Setup:
|
114
|
+
Ensure you have the required API key and any necessary configuration before running the tests. Mocking external API calls in tests is recommended for consistent results.
|
115
|
+
|
116
|
+
## Changelog
|
117
|
+
> v1.0.0
|
118
|
+
|
119
|
+
Initial release with currency conversion and caching support.
|
120
|
+
Error handling for missing rates and failed conversions.
|
121
|
+
|
31
122
|
## Development
|
32
123
|
|
33
124
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -36,6 +127,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
36
127
|
|
37
128
|
## Contributing
|
38
129
|
|
130
|
+
If you'd like to contribute to the development of this gem, feel free to fork the repository and create a pull request with your changes. Ensure that you write tests for any new features or bug fixes, and follow the style guide for Ruby code and RSpec testing.
|
131
|
+
|
39
132
|
Bug reports and pull requests are welcome on GitHub at https://github.com/shobhits7/currency_converter. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/shobhits7/currency_converter/blob/main/CODE_OF_CONDUCT.md).
|
40
133
|
|
41
134
|
## License
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/currency_converter/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "currency-converter"
|
7
|
+
spec.version = CurrencyConverter::VERSION
|
8
|
+
spec.authors = ["Shobhit Jain"]
|
9
|
+
spec.email = ["shobjain09@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "A simple gem for currency conversion."
|
12
|
+
spec.description = "Fetches real-time currency exchange rates and allows currency conversion."
|
13
|
+
spec.homepage = "https://github.com/shobhits7/currency_converter"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/shobhits7/currency_converter"
|
19
|
+
# spec.metadata["changelog_uri"] = "https://github.com/shobhits7/currency_converter"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match?(/\.gem$/) }
|
24
|
+
|
25
|
+
# spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
+
# `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
# (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
require "json"
|
5
|
+
require_relative "errors"
|
6
|
+
|
7
|
+
module CurrencyConverter
|
8
|
+
# The APIClient class handles API requests to the external exchange rate provider.
|
9
|
+
# Responsible for retrieving exchange rates based on the provided currencies.
|
10
|
+
class APIClient
|
11
|
+
BASE_URL = "https://api.exchangerate-api.com/v4/latest/"
|
12
|
+
|
13
|
+
def initialize(api_key)
|
14
|
+
@api_key = api_key
|
15
|
+
end
|
16
|
+
|
17
|
+
# Gets the exchange rate for a currency pair.
|
18
|
+
# @param from_currency [String] the source currency code
|
19
|
+
# @param to_currency [String] the target currency code
|
20
|
+
# @return [Float] the exchange rate
|
21
|
+
# @raise [RateNotFoundError, APIError] for invalid responses or network issues
|
22
|
+
def get_rate(from_currency, to_currency)
|
23
|
+
url = "#{BASE_URL}#{from_currency}"
|
24
|
+
response = Net::HTTP.get(URI(url))
|
25
|
+
data = JSON.parse(response)
|
26
|
+
|
27
|
+
data.dig("rates", to_currency) || raise(RateNotFoundError, "#{to_currency} rate not available")
|
28
|
+
rescue JSON::ParserError
|
29
|
+
raise APIError, "Invalid API response format"
|
30
|
+
rescue Net::HTTPError, SocketError => e
|
31
|
+
raise APIError, "Network error: #{e.message}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/cache"
|
4
|
+
require "active_support/notifications"
|
5
|
+
|
6
|
+
module CurrencyConverter
|
7
|
+
# The Cache class is responsible for caching the exchange rates to improve performance.
|
8
|
+
# It uses ActiveSupport's MemoryStore for caching.
|
9
|
+
class Cache
|
10
|
+
def initialize
|
11
|
+
@cache = ActiveSupport::Cache::MemoryStore.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# Fetches the value from the cache, using the block to provide a default value.
|
15
|
+
# @param key [String] the cache key
|
16
|
+
# @param block [Proc] the block to execute if the key is not found in cache
|
17
|
+
# @return [Object] the cached value
|
18
|
+
def fetch(key, &block)
|
19
|
+
@cache.fetch(key, &block) # ActiveSupport's cache expects only the key and a block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Configuration module for the CurrencyConverter gem.
|
4
|
+
# Holds settings for API key, cache duration, and logger.
|
5
|
+
# The Configuration class allows users to set these globally for the gem.
|
6
|
+
module CurrencyConverter
|
7
|
+
# The Configuration class defines settings that can be adjusted
|
8
|
+
# for the CurrencyConverter gem, such as:
|
9
|
+
# - `api_key`: The API key required for accessing the currency exchange service.
|
10
|
+
# - `cache_duration`: Duration for which the exchange rates should be cached.
|
11
|
+
# - `logger`: Configurable logger for logging any errors or information.
|
12
|
+
class Configuration
|
13
|
+
attr_accessor :api_key, :cache_duration, :logger
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@api_key = ENV["CURRENCY_CONVERTER_API_KEY"]
|
17
|
+
@cache_duration = 1.hour # Default cache duration is 1 hour
|
18
|
+
@logger = Logger.new($stdout)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
attr_accessor :configuration
|
24
|
+
|
25
|
+
# Yields a configuration block to set global settings for the gem.
|
26
|
+
def configure
|
27
|
+
self.configuration ||= Configuration.new
|
28
|
+
yield(configuration)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Main module for the CurrencyConverter gem.
|
4
|
+
# Provides methods for currency conversion with caching and error handling.
|
5
|
+
require "net/http"
|
6
|
+
require "json"
|
7
|
+
require "logger"
|
8
|
+
require "active_support/cache"
|
9
|
+
require_relative "config"
|
10
|
+
require_relative "api_client"
|
11
|
+
require_relative "cache"
|
12
|
+
|
13
|
+
module CurrencyConverter
|
14
|
+
# The Converter class performs currency conversions using exchange rates.
|
15
|
+
# It retrieves rates from an external API and caches them for performance.
|
16
|
+
class Converter
|
17
|
+
def initialize(api_key: CurrencyConverter.configuration.api_key)
|
18
|
+
# Initializes the API client with the provided or configured API key.
|
19
|
+
# Initializes a caching object for exchange rates.
|
20
|
+
@api_client = APIClient.new(api_key)
|
21
|
+
@cache = Cache.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Converts a given amount from one currency to another.
|
25
|
+
# @param amount [Float] the amount of currency to convert
|
26
|
+
# @param from_currency [String] the source currency code (e.g., "USD")
|
27
|
+
# @param to_currency [String] the target currency code (e.g., "EUR")
|
28
|
+
# @return [Float] the converted amount
|
29
|
+
# @raise [CurrencyConverter::APIError] if conversion fails due to API or network errors
|
30
|
+
def convert(amount, from_currency, to_currency)
|
31
|
+
rate = fetch_exchange_rate(from_currency, to_currency)
|
32
|
+
(amount * rate).round(2)
|
33
|
+
rescue CurrencyConverter::RateNotFoundError => e
|
34
|
+
log_error("Conversion failed: #{e.message}")
|
35
|
+
raise CurrencyConverter::APIError, "Conversion failed: #{e.message}"
|
36
|
+
rescue StandardError => e
|
37
|
+
log_error("Conversion failed: #{e.message}")
|
38
|
+
raise
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Fetches the exchange rate, using the cache if available.
|
44
|
+
# If the rate is not in the cache, retrieves it from the API.
|
45
|
+
# @param from_currency [String] the source currency code
|
46
|
+
# @param to_currency [String] the target currency code
|
47
|
+
# @return [Float] the exchange rate
|
48
|
+
def fetch_exchange_rate(from_currency, to_currency)
|
49
|
+
@cache.fetch("#{from_currency}_#{to_currency}") do
|
50
|
+
@api_client.get_rate(from_currency, to_currency)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Logs errors to the configured logger for easier debugging.
|
55
|
+
# @param message [String] the error message to log
|
56
|
+
def log_error(message)
|
57
|
+
CurrencyConverter.configuration.logger.error(message)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CurrencyConverter
|
4
|
+
# Custom error class for API-related errors.
|
5
|
+
class APIError < StandardError; end
|
6
|
+
|
7
|
+
# Custom error class for cases where a specific exchange rate is not found.
|
8
|
+
class RateNotFoundError < StandardError; end
|
9
|
+
end
|
data/lib/currency_converter.rb
CHANGED
@@ -1,32 +1,11 @@
|
|
1
|
-
#
|
2
|
-
require "net/http"
|
3
|
-
require "json"
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(api_key)
|
10
|
-
@api_key = api_key
|
11
|
-
end
|
12
|
-
|
13
|
-
def convert(amount, from_currency, to_currency)
|
14
|
-
rate = fetch_exchange_rate(from_currency, to_currency)
|
15
|
-
(amount * rate).round(2)
|
16
|
-
end
|
3
|
+
require "currency_converter/config"
|
4
|
+
require "currency_converter/api_client"
|
5
|
+
require "currency_converter/cache"
|
6
|
+
require "currency_converter/converter"
|
17
7
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
url = "#{BASE_URL}#{from_currency}"
|
22
|
-
response = Net::HTTP.get(URI(url))
|
23
|
-
data = JSON.parse(response)
|
24
|
-
|
25
|
-
if data && data["rates"] && data["rates"][to_currency]
|
26
|
-
data["rates"][to_currency]
|
27
|
-
else
|
28
|
-
raise "Currency not supported or API error"
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
8
|
+
# CurrencyConverter module provides functionality to convert currencies using exchange rates.
|
9
|
+
# It includes features for caching and error handling during conversions.
|
10
|
+
module CurrencyConverter
|
32
11
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "currency_converter"
|
4
|
+
require "webmock/rspec"
|
5
|
+
|
6
|
+
RSpec.describe CurrencyConverter::APIClient do
|
7
|
+
let(:client) { described_class.new("test_key") }
|
8
|
+
|
9
|
+
describe "#get_rate" do
|
10
|
+
it "returns the exchange rate for a valid currency pair" do
|
11
|
+
stub_request(:get, "https://api.exchangerate-api.com/v4/latest/USD")
|
12
|
+
.to_return(body: { rates: { "EUR" => 0.85 } }.to_json)
|
13
|
+
|
14
|
+
expect(client.get_rate("USD", "EUR")).to eq(0.85)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "raises a RateNotFoundError if the rate is missing" do
|
18
|
+
stub_request(:get, "https://api.exchangerate-api.com/v4/latest/USD")
|
19
|
+
.to_return(body: { rates: {} }.to_json)
|
20
|
+
|
21
|
+
expect { client.get_rate("USD", "EUR") }.to raise_error(CurrencyConverter::RateNotFoundError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require "currency_converter"
|
6
|
+
|
7
|
+
RSpec.describe CurrencyConverter::Cache do
|
8
|
+
before do
|
9
|
+
CurrencyConverter.configure do |config|
|
10
|
+
config.cache_duration = 60 # Set cache duration for testing
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:cache) { described_class.new }
|
15
|
+
|
16
|
+
it "caches values for the specified duration" do
|
17
|
+
# Cache a value for the key "test_key"
|
18
|
+
# rubocop:disable Style/RedundantFetchBlock
|
19
|
+
result = cache.fetch("test_key") { 100 }
|
20
|
+
expect(result).to eq(100)
|
21
|
+
|
22
|
+
# Try fetching the value again, it should return the cached value
|
23
|
+
cached_result = cache.fetch("test_key") { 200 }
|
24
|
+
expect(cached_result).to eq(100) # Cached value should be returned
|
25
|
+
# rubocop:enable Style/RedundantFetchBlock
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "currency_converter"
|
4
|
+
|
5
|
+
RSpec.describe CurrencyConverter::Converter do
|
6
|
+
let(:converter) { described_class.new }
|
7
|
+
|
8
|
+
# Simulate a missing exchange rate to trigger the error handling
|
9
|
+
before do
|
10
|
+
allow_any_instance_of(CurrencyConverter::APIClient)
|
11
|
+
.to receive(:get_rate)
|
12
|
+
.and_raise(CurrencyConverter::RateNotFoundError, "EUR rate not available")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises an APIError if the conversion fails due to missing rate" do
|
16
|
+
expect { converter.convert(100, "USD", "EUR") }.to raise_error(CurrencyConverter::APIError, /Conversion failed/)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "performs the conversion successfully when rate is available" do
|
20
|
+
# Simulate a successful exchange rate retrieval
|
21
|
+
allow_any_instance_of(CurrencyConverter::APIClient)
|
22
|
+
.to receive(:get_rate)
|
23
|
+
.and_return(0.85)
|
24
|
+
|
25
|
+
result = converter.convert(100, "USD", "EUR")
|
26
|
+
expect(result).to eq(85.0)
|
27
|
+
end
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "currency_converter"
|
4
|
+
require "webmock/rspec"
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
# Enable flags like --only-failures and --next-failure
|
8
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
9
|
+
|
10
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
11
|
+
config.disable_monkey_patching!
|
12
|
+
|
13
|
+
config.expect_with :rspec do |c|
|
14
|
+
c.syntax = :expect
|
15
|
+
end
|
16
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: currency-converter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shobhit Jain
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Fetches real-time currency exchange rates and allows currency conversion.
|
14
14
|
email:
|
@@ -17,19 +17,38 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- ".github/workflows/main.yml"
|
21
|
+
- ".gitignore"
|
22
|
+
- ".idea/.gitignore"
|
23
|
+
- ".idea/currency_converter.iml"
|
24
|
+
- ".idea/misc.xml"
|
25
|
+
- ".idea/modules.xml"
|
26
|
+
- ".idea/vcs.xml"
|
20
27
|
- ".rspec"
|
21
28
|
- ".rubocop.yml"
|
22
29
|
- CHANGELOG.md
|
23
30
|
- CODE_OF_CONDUCT.md
|
24
31
|
- Gemfile
|
32
|
+
- Gemfile.lock
|
25
33
|
- LICENSE.txt
|
26
34
|
- README.md
|
27
35
|
- Rakefile
|
28
36
|
- bin/console
|
29
37
|
- bin/setup
|
38
|
+
- currency-converter.gemspec
|
30
39
|
- lib/currency_converter.rb
|
40
|
+
- lib/currency_converter/api_client.rb
|
41
|
+
- lib/currency_converter/cache.rb
|
42
|
+
- lib/currency_converter/config.rb
|
43
|
+
- lib/currency_converter/converter.rb
|
44
|
+
- lib/currency_converter/errors.rb
|
31
45
|
- lib/currency_converter/version.rb
|
32
46
|
- sig/currency_converter.rbs
|
47
|
+
- sig/currency_converter/converter.rbs
|
48
|
+
- spec/currency_converter/api_client_spec.rb
|
49
|
+
- spec/currency_converter/cache_spec.rb
|
50
|
+
- spec/currency_converter/converter_spec.rb
|
51
|
+
- spec/spec_helper.rb
|
33
52
|
homepage: https://github.com/shobhits7/currency_converter
|
34
53
|
licenses:
|
35
54
|
- MIT
|