exchange 0.5.1 → 0.6.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.
Files changed (45) hide show
  1. data/Gemfile +17 -8
  2. data/Gemfile.lock +12 -6
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +15 -12
  5. data/VERSION +1 -1
  6. data/exchange.gemspec +13 -23
  7. data/lib/core_extensions/conversability.rb +5 -4
  8. data/lib/exchange.rb +6 -11
  9. data/lib/exchange/base.rb +19 -0
  10. data/lib/exchange/cache/base.rb +57 -32
  11. data/lib/exchange/cache/file.rb +41 -45
  12. data/lib/exchange/cache/memcached.rb +28 -29
  13. data/lib/exchange/cache/no_cache.rb +4 -24
  14. data/lib/exchange/cache/rails.rb +26 -26
  15. data/lib/exchange/cache/redis.rb +30 -33
  16. data/lib/exchange/configuration.rb +167 -73
  17. data/lib/exchange/currency.rb +61 -30
  18. data/lib/exchange/external_api.rb +2 -0
  19. data/lib/exchange/external_api/base.rb +11 -5
  20. data/lib/exchange/external_api/call.rb +10 -7
  21. data/lib/exchange/external_api/currency_bot.rb +9 -5
  22. data/lib/exchange/external_api/ecb.rb +29 -13
  23. data/lib/exchange/external_api/json.rb +22 -0
  24. data/lib/exchange/external_api/xavier_media.rb +6 -5
  25. data/lib/exchange/external_api/xml.rb +22 -0
  26. data/lib/exchange/gem_loader.rb +35 -0
  27. data/lib/exchange/helper.rb +22 -15
  28. data/lib/exchange/iso_4217.rb +55 -44
  29. data/spec/core_extensions/conversability_spec.rb +2 -2
  30. data/spec/exchange/cache/base_spec.rb +7 -7
  31. data/spec/exchange/cache/file_spec.rb +19 -18
  32. data/spec/exchange/cache/memcached_spec.rb +29 -26
  33. data/spec/exchange/cache/no_cache_spec.rb +12 -6
  34. data/spec/exchange/cache/rails_spec.rb +13 -9
  35. data/spec/exchange/cache/redis_spec.rb +24 -24
  36. data/spec/exchange/configuration_spec.rb +38 -30
  37. data/spec/exchange/currency_spec.rb +33 -21
  38. data/spec/exchange/external_api/base_spec.rb +3 -1
  39. data/spec/exchange/external_api/call_spec.rb +5 -1
  40. data/spec/exchange/external_api/currency_bot_spec.rb +5 -1
  41. data/spec/exchange/external_api/ecb_spec.rb +9 -5
  42. data/spec/exchange/external_api/xavier_media_spec.rb +5 -1
  43. data/spec/exchange/gem_loader_spec.rb +29 -0
  44. data/spec/spec_helper.rb +1 -1
  45. metadata +12 -87
@@ -11,42 +11,41 @@ module Exchange
11
11
  # c.cache_host = 'Your memcached host'
12
12
  # c.cache_port = 'Your memcached port'
13
13
  # end
14
+ #
14
15
  class Memcached < Base
15
- class << self
16
16
 
17
- # instantiates a memcached client and memoizes it in a class variable.
18
- # Use this client to access memcached data. For further explanation of use visit the memcached gem documentation
19
- # @example
20
- # Exchange::Cache::Memcached.client.set('FOO', 'BAR')
21
- # @return [::Memcached] an instance of the memcached client gem class
22
-
23
- def client
24
- @@client ||= ::Memcached.new("#{Configuration.cache_host}:#{Configuration.cache_port}")
25
- end
17
+ # instantiates a memcached client and memoizes it in a class variable.
18
+ # Use this client to access memcached data. For further explanation of use visit the memcached gem documentation
19
+ # @example
20
+ # Exchange::Cache::Memcached.client.set('FOO', 'BAR')
21
+ # @return [Dalli::Client] an instance of the dalli gem client class
22
+ #
23
+ def client
24
+ Exchange::GemLoader.new('dalli').try_load unless defined?(::Dalli)
25
+ @client ||= Dalli::Client.new("#{Exchange.configuration.cache.host}:#{Exchange.configuration.cache.port}")
26
+ end
27
+
28
+ # returns either cached data from the memcached client or calls the block and caches it in memcached.
29
+ # This method has to be the same in all the cache classes in order for the configuration binding to work
30
+ # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
31
+ # @param [Hash] opts the options to cache with
32
+ # @option opts [Time] :at the historic time of the exchange rates to be cached
33
+ # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
34
+ # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
35
+
36
+ def cached api, opts={}, &block
37
+ result = opts[:plain] ? client.get(key(api, opts)).to_s.gsub(/["\s+]/, '') : JSON.load(client.get(key(api, opts)))
26
38
 
27
- # returns either cached data from the memcached client or calls the block and caches it in memcached.
28
- # This method has to be the same in all the cache classes in order for the configuration binding to work
29
- # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
30
- # @param [Hash] opts the options to cache with
31
- # @option opts [Time] :at the historic time of the exchange rates to be cached
32
- # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
33
- # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
34
-
35
- def cached api, opts={}, &block
36
- raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
37
- begin
38
- result = opts[:plain] ? client.get(key(api, opts)).gsub(/["\s+]/, '') : JSON.load(client.get(key(api, opts)))
39
- rescue ::Memcached::NotFound
40
- result = block.call
41
- if result && !result.to_s.empty?
42
- client.set key(api, opts), result.to_json, Configuration.update == :daily ? 86400 : 3600
43
- end
39
+ unless result
40
+ result = super
41
+ if result && !result.to_s.empty?
42
+ client.set key(api, opts), result.to_json, Exchange.configuration.cache.expire == :daily ? 86400 : 3600
44
43
  end
45
-
46
- result
47
44
  end
48
45
 
46
+ result
49
47
  end
48
+
50
49
  end
51
50
  end
52
51
  end
@@ -2,32 +2,12 @@ module Exchange
2
2
  module Cache
3
3
 
4
4
  # @author Beat Richartz
5
- # A class that allows to store api call results in files. THIS NOT A RECOMMENDED CACHING OPTION!
6
- # It just may be necessary to cache large files somewhere, this class allows you to do that
5
+ # A class to set caching to false
7
6
  #
8
- # @version 0.3
7
+ # @version 0.6
9
8
  # @since 0.3
9
+ #
10
+ NoCache = Class.new Base
10
11
 
11
- class NoCache < Base
12
- class << self
13
-
14
- # returns either cached data from a stored file or stores a file.
15
- # This method has to be the same in all the cache classes in order for the configuration binding to work
16
- # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
17
- # @param [Hash] opts the options to cache with
18
- # @option opts [Time] :at IS IGNORED FOR FILECACHE
19
- # @option opts [Symbol] :cache_period The period to cache the file for
20
- # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
21
- # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
22
-
23
- def cached api, opts={}, &block
24
- raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
25
-
26
- block.call
27
- end
28
-
29
-
30
- end
31
- end
32
12
  end
33
13
  end
@@ -9,37 +9,37 @@ module Exchange
9
9
  # Exchange::Configuration.define do |c|
10
10
  # c.cache = :rails
11
11
  # end
12
+ #
12
13
  class Rails < Base
13
- class << self
14
14
 
15
- # returns a Rails cache client. This has not to be stored since rails already memoizes it.
16
- # Use this client to access rails cache data. For further explanation of use visit the rails documentation
17
- # @example
18
- # Exchange::Cache::Rails.client.set('FOO', 'BAR')
19
- # @return [ActiveSupport::Cache::Subclass] an instance of the rails cache class (presumably a subclass of ActiveSupport::Cache)
20
-
21
- def client
22
- ::Rails.cache if defined?(::Rails)
23
- end
24
-
25
- # returns either cached data from the memcached client or calls the block and caches it in rails cache.
26
- # This method has to be the same in all the cache classes in order for the configuration binding to work
27
- # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
28
- # @param [Hash] opts the options to cache with
29
- # @option opts [Time] :at the historic time of the exchange rates to be cached
30
- # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
31
- # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
15
+ # returns a Rails cache client. This has not to be stored since rails already memoizes it.
16
+ # Use this client to access rails cache data. For further explanation of use visit the rails documentation
17
+ # @example
18
+ # Exchange::Cache::Rails.client.set('FOO', 'BAR')
19
+ # @return [ActiveSupport::Cache::Subclass] an instance of the rails cache class (presumably a subclass of ActiveSupport::Cache)
20
+ #
21
+ def client
22
+ Exchange::GemLoader.new('rails').try_load unless defined?(::Rails)
23
+ ::Rails.cache
24
+ end
25
+
26
+ # returns either cached data from the memcached client or calls the block and caches it in rails cache.
27
+ # This method has to be the same in all the cache classes in order for the configuration binding to work
28
+ # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
29
+ # @param [Hash] opts the options to cache with
30
+ # @option opts [Time] :at the historic time of the exchange rates to be cached
31
+ # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
32
+ # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
33
+ #
34
+ def cached api, opts={}, &block
35
+ raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
32
36
 
33
- def cached api, opts={}, &block
34
- raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
35
-
36
- result = client.fetch key(api, opts), :expires_in => Configuration.update == :daily ? 86400 : 3600, &block
37
- client.delete(key(api, opts)) unless result && !result.to_s.empty?
38
-
39
- result
40
- end
37
+ result = client.fetch key(api, opts), :expires_in => Exchange.configuration.cache.expire == :daily ? 86400 : 3600, &block
38
+ client.delete(key(api, opts)) unless result && !result.to_s.empty?
41
39
 
40
+ result
42
41
  end
42
+
43
43
  end
44
44
  end
45
45
  end
@@ -12,43 +12,40 @@ module Exchange
12
12
  # c.cache_port = 'Your redis port (an Integer)'
13
13
  # end
14
14
  class Redis < Base
15
- class << self
16
-
17
- # instantiates a redis client and memoizes it in a class variable.
18
- # Use this client to access redis data. For further explanation of use visit the redis gem documentation
19
- # @example
20
- # Exchange::Cache::Redis.client.set('FOO', 'BAR')
21
- # @return [::Redis] an instance of the redis client gem class
22
-
23
- def client
24
- @@client ||= ::Redis.new(:host => Configuration.cache_host, :port => Configuration.cache_port)
25
- end
26
-
27
- # returns either cached data from the redis client or calls the block and caches it in redis.
28
- # This method has to be the same in all the cache classes in order for the configuration binding to work
29
- # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
30
- # @param [Hash] opts the options to cache with
31
- # @option opts [Time] :at the historic time of the exchange rates to be cached
32
- # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
33
- # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
34
-
35
- def cached api, opts={}, &block
36
- raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
37
-
38
- if result = client.get(key(api, opts))
39
- result = opts[:plain] ? result.gsub(/["\s+]/, '') : JSON.load(result)
40
- else
41
- result = block.call
42
- if result && !result.to_s.empty?
43
- client.set key(api, opts), result.to_json
44
- client.expire key(api, opts), Configuration.update == :daily ? 86400 : 3600
45
- end
15
+
16
+ # instantiates a redis client and memoizes it in a class variable.
17
+ # Use this client to access redis data. For further explanation of use visit the redis gem documentation
18
+ # @example
19
+ # Exchange::Cache::Redis.client.set('FOO', 'BAR')
20
+ # @return [::Redis] an instance of the redis client gem class
21
+ #
22
+ def client
23
+ Exchange::GemLoader.new('redis').try_load unless defined?(::Redis)
24
+ @client ||= ::Redis.new(:host => Exchange.configuration.cache.host, :port => Exchange.configuration.cache.port)
25
+ end
26
+
27
+ # returns either cached data from the redis client or calls the block and caches it in redis.
28
+ # This method has to be the same in all the cache classes in order for the configuration binding to work
29
+ # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
30
+ # @param [Hash] opts the options to cache with
31
+ # @option opts [Time] :at the historic time of the exchange rates to be cached
32
+ # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
33
+ # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
34
+ #
35
+ def cached api, opts={}, &block
36
+ if result = client.get(key(api, opts))
37
+ result = opts[:plain] ? result.gsub(/["\s+]/, '') : JSON.load(result)
38
+ else
39
+ result = super
40
+ if result && !result.to_s.empty?
41
+ client.set key(api, opts), result.to_json
42
+ client.expire key(api, opts), Exchange.configuration.cache.expire == :daily ? 86400 : 3600
46
43
  end
47
-
48
- result
49
44
  end
50
45
 
46
+ result
51
47
  end
48
+
52
49
  end
53
50
  end
54
51
  end
@@ -1,88 +1,182 @@
1
1
  module Exchange
2
+
3
+ class << self
4
+
5
+ # A configuration setter for the Exchange gem. This comes in handy if you want to have backup or test setting
6
+ # @param [Exchange::Configuration] configuration the configuration to install
7
+ # @raise [ArgumentError] if fed with anything else than an Exchange::Configuration
8
+ # @return [Exchange::Configuration] The configuration installed
9
+ #
10
+ def configuration= configuration
11
+ if configuration.is_a? Exchange::Configuration
12
+ @configuration = configuration
13
+ else
14
+ raise ArgumentError.new("The configuration needs to be an instance of Exchange::Configuration")
15
+ end
16
+ end
17
+
18
+ # A getter for the configuration, returns the currently installed configuration or a default configuration
19
+ # if no configuration is installed
20
+ # @return [Exchange::Configuration] The currently installed / the default configuration
21
+ #
22
+ def configuration
23
+ @configuration ||= Configuration.new
24
+ end
25
+ end
26
+
2
27
  # @author Beat Richartz
3
28
  # A configuration class that stores the configuration of the gem. It allows to set the api from which the data gets retrieved,
4
29
  # the cache in which the data gets cached, the regularity of updates for the currency rates, how many times the api calls should be
5
30
  # retried on failure, and wether operations mixing currencies should raise errors or not
6
- # @version 0.1
31
+ # @version 0.6
7
32
  # @since 0.1
8
- class Configuration
33
+ #
34
+ class Configuration
35
+
9
36
  class << self
10
- @@config ||= {:api => :xavier_media, :retries => 5, :filestore_path => File.expand_path('exchange_filestore'), :allow_mixed_operations => true, :cache => :memcached, :cache_host => 'localhost', :cache_port => 11211, :update => :daily}
11
-
12
- # A configuration method that stores the configuration of the gem. It allows to set the api from which the data gets retrieved,
13
- # the cache in which the data gets cached, the regularity of updates for the currency rates, how many times the api calls should be
14
- # retried on failure, and wether operations mixing currencies should raise errors or not
15
- # @version 0.1
16
- # @since 0.1
17
- # @example Set a configuration by passing a block to the configuration
18
- # Exchange::Configuration.define do |c|
19
- # c.cache = :redis
20
- # c.cache_host = '127.0.0.1'
21
- # c.cache_port = 6547
22
- # c.api = :currency_bot
23
- # c.retries = 1
24
- # c.allow_mixed_operations = false
25
- # c.update = :hourly
26
- # end
27
- # @yield [Exchange::Configuration] yields a the configuration class
28
- # @yieldparam [optional, Symbol] cache The cache type to use. Possible Values: :redis, :memcached or :rails or false to disable caching. Defaults to :memcached
29
- # @yieldparam [optional, String] cache_host A string with the hostname or IP to set the cache host to. Does not have to be set for Rails cache
30
- # @yieldparam [optional, Integer] cache_port An integer for the cache port. Does not have to be set for Rails cache
31
- # @yieldparam [optional, Symbol] api The API to use. Possible Values: :currency_bot (Open Source currency bot API) or :xavier_media (Xavier Media API). Defaults to :currency_bot
32
- # @yieldparam [optional, Integer] retries The number of times the gem should retry to connect to the api host. Defaults to 5.
33
- # @yieldparam [optional, Boolean] If set to false, Operations with with different currencies raise errors. Defaults to true.
34
- # @yieldparam [optional, Symbol] The regularity of updates for the API. Possible values: :daily, :hourly. Defaults to :daily.
35
- # @yieldparam [optional, String] The path where files can be stored for the gem (used for large files from ECB). Make sure ruby has write access.
36
- # @example Set configuration values directly to the class
37
- # Exchange::Configuration.cache = :redis
38
- # Exchange::Configuration.api = :xavier_media
39
37
 
40
- def define &blk
41
- self.instance_eval(&blk)
42
- end
43
-
44
- [:api, :api_app_id, :retries, :cache, :cache_host, :cache_port, :filestore_path, :update, :allow_mixed_operations].each do |m|
45
- define_method m do
46
- @@config[m]
47
- end
48
- define_method :"#{m}=" do |data|
49
- @@config.merge! m => data
38
+ private
39
+
40
+ # @private
41
+ # @macro [setter] install_setter
42
+ #
43
+ def install_setter key
44
+ define_method :"#{key}=" do |data|
45
+ @config[key] = DEFAULTS[key].merge(data)
46
+ end
50
47
  end
51
- end
52
-
53
- # The instantiated api class according to the configuration
54
- # @example
55
- # Exchange::Configuration.api = :currency_bot
56
- # Exchange::Configuration.api_class #=> Exchange::ExternalAPI::CurrencyBot
57
- # @param [Hash] options A hash of Options
58
- # @option options [Class] :api A api to return instead of the api class (use for fallback)
59
- # @return [Exchange::ExternalAPI::Subclass] A subclass of Exchange::ExternalAPI
60
-
61
- def api_class(options={})
62
- @api_class ||= {}
63
- return @api_class[options[:api] || self.api] if @api_class[options[:api] || self.api]
64
48
 
65
- @api_class[options[:api] || self.api] = ExternalAPI.const_get((options[:api] || self.api).to_s.gsub(/(?:^|_)(.)/) { $1.upcase })
66
- end
67
-
68
- # The instantiated cache class according to the configuration
69
- # @example
70
- # Exchange::Configuration.cache = :redis
71
- # Exchange::Configuration.cache_class #=> Exchange::ExternalAPI::Redis
72
- # @param [Hash] options A hash of Options
73
- # @option options [Class] :api A api to return instead of the api class (use for fallback)
74
- # @return [Exchange::Cache::Subclass] A subclass of Exchange::Cache (or nil if caching has been set to false)
75
-
76
- def cache_class(options={})
77
- @cache_class ||= {}
78
- return @cache_class[options[:cache] || self.cache] if @cache_class[options[:cache] || self.cache]
79
49
 
80
- @cache_class[options[:cache] || self.cache] = if self.cache
81
- Cache.const_get((options[:cache] || self.cache).to_s.gsub(/(?:^|_)(.)/) { $1.upcase })
82
- else
83
- Cache::NoCache
50
+ # @private
51
+ # @macro [getter] install_getter
52
+ #
53
+ def install_getter key, parent_module
54
+ define_method key do
55
+ @config[key] = OpenStruct.new(@config[key]) unless @config[key].is_a?(OpenStruct)
56
+ @config[key].subclass = parent_module.const_get camelize(@config[key].subclass) unless @config[key].subclass.is_a?(Class)
57
+ @config[key]
58
+ end
84
59
  end
85
- end
60
+
61
+ end
62
+
63
+ # The configuration defaults
64
+ # @version 0.6
65
+ # @since 0.6
66
+ #
67
+ DEFAULTS = {
68
+ :api => {
69
+ :subclass => ExternalAPI::XavierMedia,
70
+ :retries => 5
71
+ },
72
+ :cache => {
73
+ :subclass => Cache::Memcached,
74
+ :host => 'localhost',
75
+ :port => 11211,
76
+ :expire => :daily
77
+ },
78
+ :allow_mixed_operations => true
79
+ }
80
+
81
+ # Initialize a new configuration. Takes a hash and/or a block. Lets you easily set the configuration the way you want it to be
82
+ # @version 0.6
83
+ # @since 0.6
84
+ # @param [Hash] configuration The configuration as a hash
85
+ # @param [Proc] block A block to yield the configuration with
86
+ # @example Define the configuration with a hash
87
+ # Exchange::Configuration.new(:allow_mixed_operations => false, :api => {:subclass => :currency_bot, :retries => 2})
88
+ # @example Define the configuration with a block
89
+ # Exchange::Configuration.new do |c|
90
+ # c.allow_mixed_operations = false
91
+ # c.cache = {
92
+ # :subclass => Cache::Redis,
93
+ # :expire => :hourly
94
+ # }
95
+ #
96
+ # @yield [Exchange::Configuration] yields an instance of the configuration class
97
+ # @yieldparam [optional, Symbol] cache The cache type to use. Possible Values: :redis, :memcached or :rails or false to disable caching. Defaults to :memcached
98
+ # @yieldparam [optional, String] cache_host A string with the hostname or IP to set the cache host to. Does not have to be set for Rails cache
99
+ # @yieldparam [optional, Integer] cache_port An integer for the cache port. Does not have to be set for Rails cache
100
+ # @yieldparam [optional, Symbol] api The API to use. Possible Values: :currency_bot (Open Source currency bot API) or :xavier_media (Xavier Media API). Defaults to :currency_bot
101
+ # @yieldparam [optional, Integer] retries The number of times the gem should retry to connect to the api host. Defaults to 5.
102
+ # @yieldparam [optional, Boolean] If set to false, Operations with with different currencies raise errors. Defaults to true.
103
+ # @yieldparam [optional, Symbol] The regularity of updates for the API. Possible values: :daily, :hourly. Defaults to :daily.
104
+ # @yieldparam [optional, String] The path where files can be stored for the gem (used for large files from ECB). Make sure ruby has write access.
105
+ #
106
+ def initialize configuration={}, &block
107
+ @config = DEFAULTS.merge(configuration)
108
+ self.instance_eval(&block) if block_given?
109
+ super()
86
110
  end
111
+
112
+ # Getter for the mixed operations configuration. If set to true, operations with mixed currencies will not raise errors
113
+ # If set to false, mixed operations will raise errors
114
+ # @since 0.6
115
+ # @version 0.6
116
+ # @return [Boolean] True if mixed operations are allowed, false if not
117
+ #
118
+ def allow_mixed_operations
119
+ @config[:allow_mixed_operations]
120
+ end
121
+
122
+ # Setter for the mixed operations configuration. If set to true, operations with mixed currencies will not raise errors
123
+ # If set to false, mixed operations will raise errors
124
+ # @since 0.6
125
+ # @version 0.6
126
+ # @param [Boolean] data The configuration to set
127
+ # @return [Boolean] The configuration set
128
+ #
129
+ def allow_mixed_operations= data
130
+ @config[:allow_mixed_operations] = data
131
+ end
132
+
133
+ # Setter for the api configuration.
134
+ # @since 0.6
135
+ # @version 0.6
136
+ # @param [Hash] The hash to set the configuration to
137
+ # @option [Symbol] :subclass The API subclass to use as a underscored symbol (will be camelized and constantized)
138
+ # @option [Integer] :retries The amount of retries on connection failure
139
+ # @example set the api to be ecb with a maximum of 8 retries on connection failure
140
+ # configuration.api = { :subclass => :ecb, :retries => 8 }
141
+ #
142
+ install_setter :api
143
+
144
+ # Setter for the cache configuration.
145
+ # @since 0.6
146
+ # @version 0.6
147
+ # @param [Hash] The hash to set the configuration to
148
+ # @option [Symbol] :subclass The Cache subclass to use as a underscored symbol (will be camelized and constantized)
149
+ # @option [String] :host The cache connection host
150
+ # @option [Integer] :port The cache connection port
151
+ # @option [Symbol] :expire The expiration period for the cache, can be :daily or :hourly, defaults to daily
152
+ # @example set the cache to be redis on localhost:6578 with hourly expiration
153
+ # configuration.cache = { :subclass => :redis, :host => 'localhost', :port => 6578, :expire => :hourly }
154
+ #
155
+ install_setter :cache
156
+
157
+ # Getter for the api configuration. Instantiates the configuration as an open struct, if called for the first time.
158
+ # Also camelizes and constantizes the api subclass, if used for the first time.
159
+ # @return [OpenStruct] an openstruct with the complete api configuration
160
+ #
161
+ install_getter :api, ExternalAPI
162
+
163
+ # Getter for the cache configuration. Instantiates the configuration as an open struct, if called for the first time.
164
+ # Also camelizes and constantizes the cache subclass, if used for the first time.
165
+ # @return [OpenStruct] an openstruct with the complete cache configuration
166
+ #
167
+ install_getter :cache, Cache
168
+
169
+ private
170
+
171
+ # Camelize a string or a symbol
172
+ # @param [String, Symbol] s The string to camelize
173
+ # @return [String] a camelized string
174
+ # @example Camelize an underscored symbol
175
+ # camelize(:some_thing) #=> "SomeThing"
176
+ #
177
+ def camelize s
178
+ s = s.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }
179
+ end
180
+
87
181
  end
88
182
  end