currency-converter 0.1.0 → 1.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/.github/workflows/main.yml +27 -0
- data/.gitignore +17 -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/.rubocop.yml +18 -0
- data/CHANGELOG.md +54 -1
- data/Gemfile +8 -1
- data/Gemfile.lock +98 -0
- data/README.md +210 -7
- data/currency-converter.gemspec +34 -0
- data/lib/currency_converter/api_client.rb +114 -0
- data/lib/currency_converter/cache.rb +23 -0
- data/lib/currency_converter/config.rb +32 -0
- data/lib/currency_converter/converter.rb +100 -0
- data/lib/currency_converter/errors.rb +18 -0
- data/lib/currency_converter/version.rb +1 -1
- data/lib/currency_converter.rb +8 -29
- data/manual_integration_test.rb +157 -0
- data/sig/currency_converter/converter.rbs +9 -0
- data/spec/currency_converter/api_client_spec.rb +175 -0
- data/spec/currency_converter/cache_spec.rb +65 -0
- data/spec/currency_converter/converter_spec.rb +141 -0
- data/spec/currency_converter/version_spec.rb +15 -0
- data/spec/spec_helper.rb +23 -0
- metadata +23 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 41b3778d46388a2a405491288e53299f646d91033c38905f1ff9b8b2a6022ecc
|
|
4
|
+
data.tar.gz: ea8d42edb7f824133e28abb514022a5aa217e1e8df877193bd310864baa175f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7eb0dbca550cb87b40e2dfad8c071371cdd29a82ba4f306fd8b063494f3d53e00f0023787d3525cd0d09195394bc2844fb8ff45028c32419997129dcb729747
|
|
7
|
+
data.tar.gz: 8332235cd209a3121077cd751b383a8505f46658d7e2ff2413238fedd9ae374a277ae423ddec37488ea83e60ecb9f6c9beed3c7cd9e1650ed0c00e477bfb9f28
|
|
@@ -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
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/.bundle/
|
|
2
|
+
/.yardoc
|
|
3
|
+
/_yardoc/
|
|
4
|
+
/coverage/
|
|
5
|
+
/doc/
|
|
6
|
+
/pkg/
|
|
7
|
+
/spec/reports/
|
|
8
|
+
/tmp/
|
|
9
|
+
|
|
10
|
+
# rspec failure tracking
|
|
11
|
+
.rspec_status
|
|
12
|
+
|
|
13
|
+
# Claude Code documentation (local only)
|
|
14
|
+
CLAUDE.md
|
|
15
|
+
ACTION_PLAN.md
|
|
16
|
+
TESTING_PLAN.md
|
|
17
|
+
TESTING_SUMMARY.md
|
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/.rubocop.yml
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
AllCops:
|
|
2
2
|
TargetRubyVersion: 2.6
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
3
5
|
|
|
4
6
|
Style/StringLiterals:
|
|
5
7
|
Enabled: true
|
|
@@ -11,3 +13,19 @@ Style/StringLiteralsInInterpolation:
|
|
|
11
13
|
|
|
12
14
|
Layout/LineLength:
|
|
13
15
|
Max: 120
|
|
16
|
+
|
|
17
|
+
# Allow longer methods for complex logic
|
|
18
|
+
Metrics/MethodLength:
|
|
19
|
+
Max: 15
|
|
20
|
+
Exclude:
|
|
21
|
+
- 'lib/currency_converter/api_client.rb'
|
|
22
|
+
- 'lib/currency_converter/converter.rb'
|
|
23
|
+
|
|
24
|
+
# Allow longer blocks in tests
|
|
25
|
+
Metrics/BlockLength:
|
|
26
|
+
Exclude:
|
|
27
|
+
- 'spec/**/*_spec.rb'
|
|
28
|
+
|
|
29
|
+
# Disable for generated or gem metadata files
|
|
30
|
+
Gemspec/RequireMFA:
|
|
31
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,57 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [1.2.0] - 2026-01-10
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- **v6 API Support**: Migrated from deprecated v4 to v6 ExchangeRate-API
|
|
5
|
+
- Dual-mode operation: authenticated (with API key) and open access (without API key)
|
|
6
|
+
- API key is now optional - gem works perfectly without it
|
|
7
|
+
- Improved error handling for v6-specific errors (invalid-key, quota-reached, inactive-account, unsupported-code)
|
|
8
|
+
- Helpful logging to inform users which API mode they're using
|
|
9
|
+
- **Input Validation**: Comprehensive validation for all conversion inputs
|
|
10
|
+
- Validates amounts (rejects nil, negative, and non-numeric values)
|
|
11
|
+
- Validates currency codes (enforces 3-letter uppercase ISO 4217 format)
|
|
12
|
+
- New error classes: `InvalidAmountError` and `InvalidCurrencyError`
|
|
13
|
+
- Clear, actionable error messages for debugging
|
|
14
|
+
- **HTTP Timeout Configuration**: Configurable timeout to prevent hanging requests
|
|
15
|
+
- Default timeout: 10 seconds
|
|
16
|
+
- Prevents indefinite hangs on slow/dead connections
|
|
17
|
+
- New `TimeoutError` exception for timeout scenarios
|
|
18
|
+
- Configurable via `CurrencyConverter.configure { |c| c.timeout = 15 }`
|
|
19
|
+
- **Test Infrastructure**: Achieved 100% code coverage
|
|
20
|
+
- SimpleCov integration for coverage tracking
|
|
21
|
+
- 43 comprehensive test examples covering all code paths
|
|
22
|
+
- Manual integration test script for real API verification
|
|
23
|
+
- Zero RuboCop offenses
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **Cache Duration Bug**: Cache expiration now works correctly
|
|
27
|
+
- Previously configured `cache_duration` was ignored
|
|
28
|
+
- Cache now properly expires after the configured duration
|
|
29
|
+
- Fixed by passing `expires_in` parameter to ActiveSupport::Cache
|
|
30
|
+
- **API Key Usage**: API key is now properly used in authenticated requests
|
|
31
|
+
- v1.1.0 configured but never used the API key
|
|
32
|
+
- v6 authenticated mode now includes key in URL path
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- **Breaking Change**: Migrated from v4 to v6 API
|
|
36
|
+
- v4 endpoint: `https://api.exchangerate-api.com/v4/latest/{currency}`
|
|
37
|
+
- v6 authenticated: `https://v6.exchangerate-api.com/v6/{API_KEY}/latest/{currency}`
|
|
38
|
+
- v6 open access: `https://open.er-api.com/v6/latest/{currency}`
|
|
39
|
+
- No breaking changes for existing users - works with or without API key
|
|
40
|
+
- Code quality improvements: All RuboCop offenses fixed
|
|
41
|
+
|
|
42
|
+
### Migration Guide from v1.1.0 to v1.2.0
|
|
43
|
+
- **No code changes required** - fully backward compatible
|
|
44
|
+
- API key is now optional (but recommended for better rate limits)
|
|
45
|
+
- Invalid inputs (nil, negative amounts, invalid currency codes) will now raise validation errors
|
|
46
|
+
- Cache will now properly expire after configured duration (was broken in v1.1.0)
|
|
47
|
+
- Set timeout if needed: `CurrencyConverter.configure { |c| c.timeout = 15 }`
|
|
48
|
+
|
|
49
|
+
## [1.1.0] - 2024-11-13
|
|
50
|
+
### Added
|
|
51
|
+
- Added caching support for better performance.
|
|
52
|
+
- Introduced error handling for missing exchange rates.
|
|
53
|
+
### Fixed
|
|
54
|
+
- Fixed issue with conversion when rate is missing.
|
|
2
55
|
|
|
3
56
|
## [0.1.0] - 2024-11-11
|
|
4
57
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
currency-converter (1.2.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
|
+
docile (1.4.1)
|
|
25
|
+
hashdiff (1.1.2)
|
|
26
|
+
i18n (1.14.6)
|
|
27
|
+
concurrent-ruby (~> 1.0)
|
|
28
|
+
json (2.8.1)
|
|
29
|
+
language_server-protocol (3.17.0.3)
|
|
30
|
+
minitest (5.25.1)
|
|
31
|
+
parallel (1.26.3)
|
|
32
|
+
parser (3.3.6.0)
|
|
33
|
+
ast (~> 2.4.1)
|
|
34
|
+
racc
|
|
35
|
+
public_suffix (6.0.1)
|
|
36
|
+
racc (1.8.1)
|
|
37
|
+
rainbow (3.1.1)
|
|
38
|
+
rake (13.2.1)
|
|
39
|
+
regexp_parser (2.9.2)
|
|
40
|
+
rexml (3.3.9)
|
|
41
|
+
rspec (3.13.0)
|
|
42
|
+
rspec-core (~> 3.13.0)
|
|
43
|
+
rspec-expectations (~> 3.13.0)
|
|
44
|
+
rspec-mocks (~> 3.13.0)
|
|
45
|
+
rspec-core (3.13.2)
|
|
46
|
+
rspec-support (~> 3.13.0)
|
|
47
|
+
rspec-expectations (3.13.3)
|
|
48
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
49
|
+
rspec-support (~> 3.13.0)
|
|
50
|
+
rspec-mocks (3.13.2)
|
|
51
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
52
|
+
rspec-support (~> 3.13.0)
|
|
53
|
+
rspec-support (3.13.1)
|
|
54
|
+
rubocop (1.68.0)
|
|
55
|
+
json (~> 2.3)
|
|
56
|
+
language_server-protocol (>= 3.17.0)
|
|
57
|
+
parallel (~> 1.10)
|
|
58
|
+
parser (>= 3.3.0.2)
|
|
59
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
60
|
+
regexp_parser (>= 2.4, < 3.0)
|
|
61
|
+
rubocop-ast (>= 1.32.2, < 2.0)
|
|
62
|
+
ruby-progressbar (~> 1.7)
|
|
63
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
64
|
+
rubocop-ast (1.35.0)
|
|
65
|
+
parser (>= 3.3.1.0)
|
|
66
|
+
ruby-progressbar (1.13.0)
|
|
67
|
+
simplecov (0.22.0)
|
|
68
|
+
docile (~> 1.1)
|
|
69
|
+
simplecov-html (~> 0.11)
|
|
70
|
+
simplecov_json_formatter (~> 0.1)
|
|
71
|
+
simplecov-html (0.13.2)
|
|
72
|
+
simplecov_json_formatter (0.1.4)
|
|
73
|
+
stringio (3.1.2)
|
|
74
|
+
tzinfo (2.0.6)
|
|
75
|
+
concurrent-ruby (~> 1.0)
|
|
76
|
+
unicode-display_width (2.6.0)
|
|
77
|
+
webmock (3.24.0)
|
|
78
|
+
addressable (>= 2.8.0)
|
|
79
|
+
crack (>= 0.3.2)
|
|
80
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
81
|
+
zeitwerk (2.6.18)
|
|
82
|
+
|
|
83
|
+
PLATFORMS
|
|
84
|
+
x86_64-darwin-23
|
|
85
|
+
x86_64-linux
|
|
86
|
+
|
|
87
|
+
DEPENDENCIES
|
|
88
|
+
activesupport (~> 6.1)
|
|
89
|
+
currency-converter!
|
|
90
|
+
rake (~> 13.0)
|
|
91
|
+
rspec (~> 3.0)
|
|
92
|
+
rubocop (~> 1.21)
|
|
93
|
+
simplecov
|
|
94
|
+
stringio (~> 3.1.2)
|
|
95
|
+
webmock
|
|
96
|
+
|
|
97
|
+
BUNDLED WITH
|
|
98
|
+
2.2.33
|
data/README.md
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
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 ExchangeRate-API, while offering caching for performance improvements and comprehensive error handling for robustness.
|
|
4
|
+
|
|
5
|
+
**Version 1.2.0** brings significant improvements including v6 API support, input validation, HTTP timeout protection, and 100% test coverage.
|
|
6
|
+
|
|
7
|
+
Author: Developed by [Shobhit Jain](https://github.com/shobhits7).
|
|
4
8
|
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
Add this line to your application's Gemfile:
|
|
8
12
|
|
|
9
13
|
```ruby
|
|
10
|
-
gem '
|
|
14
|
+
gem 'currency-converter', '~> 1.2.0'
|
|
11
15
|
```
|
|
12
16
|
|
|
13
17
|
And then execute:
|
|
@@ -16,18 +20,215 @@ And then execute:
|
|
|
16
20
|
|
|
17
21
|
Or install it yourself as:
|
|
18
22
|
|
|
19
|
-
$ gem install
|
|
23
|
+
$ gem install currency-converter
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
The gem can be used with or without configuration. For better rate limits and performance, it's recommended to configure it with an API key.
|
|
28
|
+
|
|
29
|
+
### Basic Usage (No Configuration Required)
|
|
30
|
+
|
|
31
|
+
The gem works out of the box using the v6 open access API:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
converter = CurrencyConverter::Converter.new
|
|
35
|
+
amount_in_eur = converter.convert(100, 'USD', 'EUR')
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Advanced Configuration (Recommended)
|
|
39
|
+
|
|
40
|
+
To get your free API key, sign up at [Exchange Rate API](https://www.exchangerate-api.com/)
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
CurrencyConverter.configure do |config|
|
|
44
|
+
config.api_key = 'your_api_key_here' # Optional: For better rate limits (v6 authenticated API)
|
|
45
|
+
config.cache_duration = 3600 # Optional: Cache duration in seconds (default: 1 hour)
|
|
46
|
+
config.timeout = 10 # Optional: HTTP timeout in seconds (default: 10)
|
|
47
|
+
config.logger = Logger.new(STDOUT) # Optional: Configure logging
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Configuration Options:**
|
|
52
|
+
|
|
53
|
+
- **api_key** (Optional): Your API key for v6 authenticated access with higher rate limits. Without it, the gem uses the v6 open access API (free tier).
|
|
54
|
+
- **cache_duration** (Optional): Duration in seconds for caching exchange rates. Default: 3600 seconds (1 hour).
|
|
55
|
+
- **timeout** (Optional): HTTP request timeout in seconds. Prevents hanging on slow connections. Default: 10 seconds.
|
|
56
|
+
- **logger** (Optional): Logger instance for debugging and monitoring. Default: Logger writing to STDOUT.
|
|
57
|
+
|
|
20
58
|
|
|
21
59
|
## Usage
|
|
22
60
|
|
|
61
|
+
### Creating an Instance of the Converter
|
|
62
|
+
|
|
63
|
+
Create an instance of the `CurrencyConverter::Converter` class to perform currency conversions:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
converter = CurrencyConverter::Converter.new
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Converting Currency
|
|
70
|
+
|
|
71
|
+
Use the `convert` method to convert an amount from one currency to another:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
# Convert 100 USD to EUR
|
|
75
|
+
amount_in_eur = converter.convert(100, 'USD', 'EUR')
|
|
76
|
+
puts amount_in_eur # => 85.0 (example rate)
|
|
77
|
+
|
|
78
|
+
# Convert with decimal amounts
|
|
79
|
+
amount_in_gbp = converter.convert(99.99, 'USD', 'GBP')
|
|
80
|
+
puts amount_in_gbp # => 75.99 (example rate)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Parameters:**
|
|
84
|
+
|
|
85
|
+
- **amount** (Numeric): The amount to convert. Must be a positive number (Integer or Float).
|
|
86
|
+
- **from_currency** (String): Source currency code in ISO 4217 format (3 uppercase letters, e.g., "USD").
|
|
87
|
+
- **to_currency** (String): Target currency code in ISO 4217 format (3 uppercase letters, e.g., "EUR").
|
|
88
|
+
|
|
89
|
+
**Returns:** The converted amount as a Float, rounded to 2 decimal places.
|
|
90
|
+
|
|
91
|
+
### Input Validation
|
|
92
|
+
|
|
93
|
+
Version 1.2.0 includes comprehensive input validation:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
# Valid inputs
|
|
97
|
+
converter.convert(100, 'USD', 'EUR') # ✓ Valid
|
|
98
|
+
converter.convert(0, 'USD', 'EUR') # ✓ Valid (zero is allowed)
|
|
99
|
+
converter.convert(99.99, 'USD', 'GBP') # ✓ Valid (decimals allowed)
|
|
100
|
+
|
|
101
|
+
# Invalid inputs that will raise errors
|
|
102
|
+
converter.convert(nil, 'USD', 'EUR') # ✗ Raises InvalidAmountError
|
|
103
|
+
converter.convert(-100, 'USD', 'EUR') # ✗ Raises InvalidAmountError
|
|
104
|
+
converter.convert('100', 'USD', 'EUR') # ✗ Raises InvalidAmountError
|
|
105
|
+
converter.convert(100, 'usd', 'EUR') # ✗ Raises InvalidCurrencyError (must be uppercase)
|
|
106
|
+
converter.convert(100, 'US', 'EUR') # ✗ Raises InvalidCurrencyError (must be 3 letters)
|
|
107
|
+
converter.convert(100, nil, 'EUR') # ✗ Raises InvalidCurrencyError
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Error Handling
|
|
111
|
+
|
|
112
|
+
Version 1.2.0 provides comprehensive error handling with specific exception types:
|
|
113
|
+
|
|
114
|
+
### Exception Types
|
|
115
|
+
|
|
116
|
+
- **`InvalidAmountError`**: Raised when the amount is invalid (nil, negative, or non-numeric)
|
|
117
|
+
- **`InvalidCurrencyError`**: Raised when currency codes are invalid (nil, wrong format, not ISO 4217)
|
|
118
|
+
- **`TimeoutError`**: Raised when the API request times out
|
|
119
|
+
- **`APIError`**: Raised for API-related failures (network errors, missing rates, API quota reached)
|
|
120
|
+
- **`RateNotFoundError`**: Raised when a specific currency rate is not available from the API
|
|
121
|
+
|
|
122
|
+
### Error Handling Examples
|
|
123
|
+
|
|
23
124
|
```ruby
|
|
24
|
-
|
|
125
|
+
begin
|
|
126
|
+
amount_in_eur = converter.convert(100, 'USD', 'EUR')
|
|
127
|
+
puts "Converted amount: #{amount_in_eur} EUR"
|
|
128
|
+
rescue CurrencyConverter::InvalidAmountError => e
|
|
129
|
+
puts "Invalid amount: #{e.message}"
|
|
130
|
+
rescue CurrencyConverter::InvalidCurrencyError => e
|
|
131
|
+
puts "Invalid currency: #{e.message}"
|
|
132
|
+
rescue CurrencyConverter::TimeoutError => e
|
|
133
|
+
puts "Request timed out: #{e.message}"
|
|
134
|
+
rescue CurrencyConverter::APIError => e
|
|
135
|
+
puts "API error: #{e.message}"
|
|
136
|
+
rescue StandardError => e
|
|
137
|
+
puts "Unexpected error: #{e.message}"
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Common Error Messages
|
|
25
142
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
143
|
+
```ruby
|
|
144
|
+
# Invalid amount errors
|
|
145
|
+
"Amount cannot be nil"
|
|
146
|
+
"Amount must be a number"
|
|
147
|
+
"Amount cannot be negative"
|
|
148
|
+
|
|
149
|
+
# Invalid currency errors
|
|
150
|
+
"from_currency cannot be nil"
|
|
151
|
+
"from_currency must be a 3-letter uppercase code (e.g., 'USD')"
|
|
152
|
+
"to_currency must be a 3-letter uppercase code (e.g., 'EUR')"
|
|
153
|
+
|
|
154
|
+
# API errors
|
|
155
|
+
"Request timed out after 10 seconds"
|
|
156
|
+
"Invalid API key provided"
|
|
157
|
+
"API rate limit quota reached"
|
|
158
|
+
"Unsupported currency code: XYZ"
|
|
159
|
+
"EUR rate not available"
|
|
29
160
|
```
|
|
30
161
|
|
|
162
|
+
## Testing
|
|
163
|
+
|
|
164
|
+
The gem has **100% test coverage** with comprehensive test suites covering all functionality.
|
|
165
|
+
|
|
166
|
+
### Running Tests
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Run all tests
|
|
170
|
+
bundle exec rspec
|
|
171
|
+
|
|
172
|
+
# Run with coverage report
|
|
173
|
+
bundle exec rspec
|
|
174
|
+
# View coverage report: open coverage/index.html
|
|
175
|
+
|
|
176
|
+
# Run code quality checks
|
|
177
|
+
bundle exec rubocop
|
|
178
|
+
|
|
179
|
+
# Run manual integration tests (with real API)
|
|
180
|
+
ruby manual_integration_test.rb
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Test Coverage
|
|
184
|
+
|
|
185
|
+
**Version 1.2.0 Test Statistics:**
|
|
186
|
+
- **43 test examples**, 0 failures
|
|
187
|
+
- **100% code coverage** (125/125 lines)
|
|
188
|
+
- **0 RuboCop offenses**
|
|
189
|
+
- All tests use WebMock to stub external API calls for consistency
|
|
190
|
+
- Manual integration test script included for real API verification
|
|
191
|
+
|
|
192
|
+
### Example Test
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
RSpec.describe CurrencyConverter::Converter do
|
|
196
|
+
let(:converter) { CurrencyConverter::Converter.new }
|
|
197
|
+
|
|
198
|
+
it 'performs currency conversion successfully' do
|
|
199
|
+
# WebMock stubs the API call
|
|
200
|
+
allow_any_instance_of(CurrencyConverter::APIClient)
|
|
201
|
+
.to receive(:get_rate)
|
|
202
|
+
.and_return(0.85)
|
|
203
|
+
|
|
204
|
+
result = converter.convert(100, 'USD', 'EUR')
|
|
205
|
+
expect(result).to eq(85.0)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it 'raises InvalidAmountError for negative amounts' do
|
|
209
|
+
expect { converter.convert(-100, 'USD', 'EUR') }
|
|
210
|
+
.to raise_error(CurrencyConverter::InvalidAmountError)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'raises InvalidCurrencyError for invalid currency codes' do
|
|
214
|
+
expect { converter.convert(100, 'usd', 'EUR') }
|
|
215
|
+
.to raise_error(CurrencyConverter::InvalidCurrencyError)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Changelog
|
|
221
|
+
|
|
222
|
+
See [CHANGELOG.md](CHANGELOG.md) for detailed version history and release notes.
|
|
223
|
+
|
|
224
|
+
**Latest Release (v1.2.0 - 2026-01-10):**
|
|
225
|
+
- Migrated to ExchangeRate-API v6 with dual-mode support
|
|
226
|
+
- Added comprehensive input validation
|
|
227
|
+
- Fixed cache duration implementation
|
|
228
|
+
- Added HTTP timeout configuration
|
|
229
|
+
- Achieved 100% test coverage
|
|
230
|
+
- Zero code quality issues
|
|
231
|
+
|
|
31
232
|
## Development
|
|
32
233
|
|
|
33
234
|
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 +237,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
36
237
|
|
|
37
238
|
## Contributing
|
|
38
239
|
|
|
240
|
+
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.
|
|
241
|
+
|
|
39
242
|
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
243
|
|
|
41
244
|
## 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").grep_v(/\.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
|