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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89e58c796f1088778eb0d65eeba3ae7cb2b262eaeefb19061502c6be554cb841
4
- data.tar.gz: 7297e8ba1271a95783dd23678d317bb2daf1c06089f21de130f99eeeb32b4bb6
3
+ metadata.gz: 5e6d4706fb1deb22fa6661dfe2c2832714c52674574ffc83b07a9fa8024b5a4d
4
+ data.tar.gz: 62dcbb7b3828814a0f1df68e22fbae1fd3f279420990e34663dc295b14bb5920
5
5
  SHA512:
6
- metadata.gz: 70ffe563aa7df2555f266b17ba41c2a32a1ba6f5e56b80d1b00cdc45c35451b82571566760265c36526e62066fdc03a614108940f16f1fae699e9948bd42aa63
7
- data.tar.gz: 1eb14c78cd92a47145981502cd7c8b89de1500bce13ee2dfffbfea75142c5f06c743598452fb769ef5558f40542d32f1e72ed9a75e500b89bb8b74f062e2c033
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
@@ -0,0 +1,11 @@
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
data/.idea/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
@@ -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
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" project-jdk-name="rbenv: 3.0.5" project-jdk-type="RUBY_SDK" />
4
+ </project>
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
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
data/CHANGELOG.md CHANGED
@@ -1,4 +1,9 @@
1
- ## [Unreleased]
1
+ ## [1.1.0] - 2024-11-13
2
+ ### Added
3
+ - Added caching support for better performance.
4
+ - Introduced error handling for missing exchange rates.
5
+ ### Fixed
6
+ - Fixed issue with conversion when rate is missing.
2
7
 
3
8
  ## [0.1.0] - 2024-11-11
4
9
 
data/Gemfile CHANGED
@@ -11,4 +11,10 @@ gem "rspec", "~> 3.0"
11
11
 
12
12
  gem "rubocop", "~> 1.21"
13
13
 
14
- gem 'stringio', '~> 3.1.2'
14
+ gem "stringio", "~> 3.1.2"
15
+
16
+ gem "activesupport", "~> 6.1"
17
+
18
+ group :test do
19
+ gem "webmock"
20
+ end
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 is a Ruby gem for converting currencies based on real-time exchange rates.
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 'currency_converter'
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 currency_converter
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
- require "currency_converter"
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
- converter = CurrencyConverter::Converter.new("your_api_key")
27
- amount_in_eur = converter.convert(100, "USD", "EUR")
28
- puts "Converted amount: #{amount_in_eur} EUR"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CurrencyConverter
4
- VERSION = "0.1.0"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -1,32 +1,11 @@
1
- # lib/currency_converter.rb
2
- require "net/http"
3
- require "json"
1
+ # frozen_string_literal: true
4
2
 
5
- module CurrencyConverter
6
- class Converter
7
- BASE_URL = "https://api.exchangerate-api.com/v4/latest/"
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
- private
19
-
20
- def fetch_exchange_rate(from_currency, to_currency)
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,9 @@
1
+ module CurrencyConverter
2
+ class Converter
3
+ @api_key: String
4
+
5
+ private
6
+
7
+ def fetch_exchange_rate: -> untyped
8
+ end
9
+ 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
@@ -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: 0.1.0
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 00:00:00.000000000 Z
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