README.md in config-factory-0.0.7 vs README.md in config-factory-0.0.8
- old
+ new
@@ -7,124 +7,299 @@
A gem for creating configuration classes using the
[Abstract Factory](https://web.archive.org/web/20111109224959/http://www.informit.com/articles/article.aspx?p=1398599),
pattern, with run-time configuration provided by hashes or YAML files.
-## Example
+- [Factory lookup patterns](#factory-lookup-patterns)
+ - [Looking up concrete factory classes based on a key value](#looking-up-concrete-factory-classes-based-on-a-key-value)
+ - [Finding concrete factory classes based on an argument filter](#finding-concrete-factory-classes-based-on-an-argument-filter)
+ - [Instantiating implementations directly](#instantiating-implementations-directly)
+- [Environments](#environments)
+ - [Multiple environments](#multiple-environments)
+ - [Single environment](#single-environment)
-The abstract configuration factory declares a `key`, which is used to look up the concrete
-config class for a given configuration. Concrete implementations register themselves with a
-DSL method named after the `key` value.
+## Factory lookup patterns
-In the example below, the `SourceConfig` abstract factory declares the key `:protocol`; the
-concrete classes `OAISourceConfig` and `ResyncSourceConfig` register themselves with
-`protocol: 'OAI'` and `protocol: 'Resync'`, respectively. `SourceConfig.for_environment()`
-will then look for a `protocol:` line in the configuration file to determine which
-registered concrete class to instantiate.
+### Looking up concrete factory classes based on a key value
+In the basic use case, an abstract factory class defines a configuration key:
+
```ruby
class SourceConfig
include Config::Factory
- key :protocol
+ key :protocol # <- configuration key
end
+```
-class OAISourceConfig < SourceConfig
- protocol 'OAI'
+This creates a corresponding DSL method (here `:protocol`), which implementation
+classes use to register themselves.
+```ruby
+class OAISourceConfig < SourceConfig
+ protocol 'OAI' # <- registers OAISourceConfig as implementation
+ # for the "OAI" protocol
def initialize(oai_base_url:, metadata_prefix:, set: nil, seconds_granularity: false)
+ # ...
end
end
class ResyncSourceConfig < SourceConfig
- protocol 'Resync'
-
+ protocol 'Resync' # <- registers ResyncSourceConfig as implementation
+ # for the "Resync" protocol
def initialize(capability_list_url:)
+ # ...
end
end
```
-### Single-environment example
+At run time, `SourceConfig` finds the `protocol:` key in the configuration, and
+based on the value `'OAI'`, instantiates an `OAISourceConfig`, passing the remaining
+arguments to the `OAISourceConfig` initializer.
-Configuration file:
-
```YAML
source:
- protocol: OAI
+ protocol: OAI # <- indicates we want an OAISourceConfig
+
+ # these arguments will be passed to the OAISourceConfig initializer
oai_base_url: http://oai.example.org/oai
metadata_prefix: some_prefix
set: some_set
seconds_granularity: true
```
-Loading:
-
```ruby
-environment = Environment.load_file('spec/data/single-environment.yml')
-# => #<Config::Factory::Environment:0x007fe8d3883240 @name=:production, @configs={"source"=>{"protocol"=>"OAI", "oai_base_url"=>"http://oai.example.org/oai", "metadata_prefix"=>"some_prefix", "set"=>"some_set", "seconds_granularity"=>true}}>
-source_config = SourceConfig.for_environment(environment, :source)
-# => #<OAISourceConfig:0x007fe8d38b3990 @oai_base_url="http://oai.example.org/oai", @metadata_prefix="some_prefix", @set="some_set", @seconds_granularity=true>
+config = YAML.load_file('config.yml')
+SourceConfig.build_from(config, :source)
+# => #<OAISourceConfig:0x007fc8f14a58f0>
```
-### Multiple-environment example
+### Finding concrete factory classes based on an argument filter
-Configuration file:
+In some cases (e.g., compatibility with existing configuration files), it may
+be necessary to have the implementation class examine the entire configuration
+hash to determine whether it can support a given configuration.
+```ruby
+class PersistenceConfig
+ include Config::Factory
+ # note no configuration key given
+end
+
+class DBPersistenceConfig < PersistenceConfig
+ attr_reader :connection_info
+
+ # Applies if we find 'adapter:' in the config file
+ can_build_if { |config| config.key?(:adapter) }
+
+ def initialize(connection_info)
+ @connection_info = connection_info
+ end
+end
+
+class XMLPersistenceConfig < PersistenceConfig
+ attr_reader :connection_info
+
+ # Applies if we find 'path:' in the config file
+ # and its value ends in '.xml'
+ can_build_if do |config|
+ config[:path] && config[:path].end_with?('.xml')
+ end
+
+ def initialize(connection_info)
+ @connection_info = connection_info
+ end
+end
+```
+
+This configuration will build a `DBPersistenceConfig`:
+
```YAML
-test:
- source:
- protocol: Resync
- capability_list_url: http://localhost:8888/capabilitylist.xml
+persistence:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+```
-production:
- source:
- protocol: OAI
- oai_base_url: http://oai.example.org/oai
- metadata_prefix: some_prefix
- set: some_set
- seconds_granularity: true
+```ruby
+config = YAML.load_file('config.yml')
+PersistenceConfig.build_from(config, :persistence)
+# => #<DBPersistenceConfig:0x007fc8f14c4d18>
```
-Loading:
+Whereas this configuration will build an `XMLConfig`:
+```YAML
+persistence:
+ path: config/persistence.xml
+```
+
```ruby
-environments = Environments.load_file('spec/data/multiple_environments.yml')
-# => {:test=>#<Config::Factory::Environment:0x007fe8d3863dc8 @name=:test, @configs={"source"=>{"protocol"=>"Resync", "capability_list_url"=>"http://localhost:8888/capabilitylist.xml"}}>, :production=>#<Config::Factory::Environment:0x007fe8d3863be8 @name=:production, @configs={"source"=>{"protocol"=>"OAI", "oai_base_url"=>"http://oai.example.org/oai", "metadata_prefix"=>"some_prefix", "set"=>"some_set", "seconds_granularity"=>true}}>}
-test_env = environments[:test]
-# => #<Config::Factory::Environment:0x007fe8d383a400 @name=:test, @configs={"source"=>{"protocol"=>"Resync", "capability_list_url"=>"http://localhost:8888/capabilitylist.xml"}}>
-source_config = SourceConfig.for_environment(test_env, :source)
-# => #<ResyncSourceConfig:0x007fe8d48180c0 @capability_list_url="http://localhost:8888/capabilitylist.xml">
+config = YAML.load_file('config.yml')
+PersistenceConfig.build_from(config, :persistence)
+# => #<XMLPersistenceConfig:0x007fc8f14ed420>
```
-## Config classes with only one implementation
+### Instantiating implementations directly
-`config-factory` also supports instantiating concrete configuration classes directly.
-In this case, we simply don't declare a `key` for the class, and the configuration hash
-will be passed directly to the initializer of the concrete class.
+Finally, you may have a mix of abstract and concrete factories, so that some factories
+don't need any lookup and can just be instantiated directly.
```ruby
-class DBConfig
+class SolrConfig
include Config::Factory
- def initialize(connection_info)
- @connection_info = connection_info
+ def initialize(url:, proxy: nil, open_timeout: 60, read_timeout: 120)
+ # ...
end
end
```
```YAML
+solr:
+ url: http://solr.example.org/
+ proxy: http://foo:bar@proxy.example.com/
+ open_timeout: 120
+ read_timeout: 300
+```
+
+```ruby
+config = YAML.load_file('config.yml')
+SolrConfig.build_from(config, :solr)
+# => #<SolrConfig:0x007fc8f1504f08>
+```
+
+## Environments
+
+The YAML examples above each show only the configuration for a single factory. However,
+`Config::Factory` also supports a structured configuration file with configurations for
+multiple factories, optionally organized into environments.
+
+### Multiple environments
+
+The `Environments.load_file()` method loads a multi-environment config file as a hash
+of `Environment` instances.
+
+```ruby
+envs = Environments.load_file('config.yml')
+# => {:defaults=>#<Environment:0x007f8e9a578818>,
+# :development=>#<Environment:0x007f8e9a578728>,
+# :test=>#<Environment:0x007f8e9a578660>,
+# :production=>#<Environment:0x007f8e9a578520>}
+test = envs[:test]
+# => #<Environment:0x007f8e9a578660>
+```
+
+The `AbstractFactory.for_environment()` method takes an environment instance and a
+configuration section name.
+
+```ruby
+source = SourceConfig.for_environment(test, :source)
+# => #<ResyncSourceConfig:0x007f8e9a54a878>
+index = IndexConfig.for_environment(test, :index)
+# => #<SolrConfig:0x007f8e9a5383a8>
+persistence = PersistenceConfig.for_environment(test, :persistence)
+# => #<DBConfig:0x007f8e9a5019e8>
+```
+
+The configuration file for the examples above. Note that standard YAML features such
+as references are supported.
+
+```YAML
+defaults: &defaults
+ source:
+ protocol: OAI
+ oai_base_url: http://oai.example.org/oai
+ metadata_prefix: some_prefix
+ set: some_set
+ seconds_granularity: true
+ index:
+ adapter: solr
+ url: http://solr.example.org/
+ proxy: http://foo:bar@proxy.example.com/
+ open_timeout: 120
+ read_timeout: 300
+
+development:
+ <<: *defaults
+ persistence:
+ adapter: mysql2
+ encoding: utf8
+ pool: 5
+ database: example_dev
+ host: mysql-dev.example.org
+ port: 3306
+ index:
+ adapter: solr
+ url: http://solr-dev.example.org/
+ proxy: http://foo:bar@proxy.example.com/
+ open_timeout: 120
+ read_timeout: 300
+
test:
- db:
+ persistence:
adapter: sqlite3
database: ':memory:'
pool: 5
timeout: 5000
+ source:
+ protocol: Resync
+ capability_list_url: http://localhost:8888/capabilitylist.xml
+ index:
+ adapter: solr
+ url: http://localhost:8000/solr/
production:
- db:
+ <<: *defaults
+ persistence:
adapter: mysql2
- host: mydb.example.org
- database: myapp
- username: myuser
- password: blank
- port: 3306
encoding: utf8
+ pool: 5
+ database: example_prod
+ host: mysql.example.org
+ port: 3306
```
+
+The `Environments` module supports arbitrary environment names, but the standard ones
+are `:defaults`, `:development`, `:test`, `:stage`, `:staging,` and `:production`. The
+`Environments.load_file` method will warn if none of these are present, as this might
+indicate loading a single-environment config file as multiple by mistake.
+
+### Single environment
+
+For a single-environment config file, use the `load_file()` method on the `Environment`
+class itself (not the `Environments` module, plural):
+
+```ruby
+env = Environment.load_file('config.yml')
+# => #<Environment:0x007f8e9a49b1c0>
+persistence = PersistenceConfig.for_environment(env, :persistence)
+# => #<DBConfig:0x007f8e9a45abe8>
+source = SourceConfig.for_environment(env, :source)
+# => #<OAISourceConfig:0x007f8e9a482fa8>
+index = IndexConfig.for_environment(env, :index)
+# => #<SolrConfig:0x007f8e9a4438a8>
+```
+
+```YAML
+persistence:
+ adapter: mysql2
+ encoding: utf8
+ pool: 5
+ database: example_prod
+ host: mysql-dev.example.org
+ port: 3306
+source:
+ protocol: OAI
+ oai_base_url: http://oai.example.org/oai
+ metadata_prefix: some_prefix
+ set: some_set
+ seconds_granularity: true
+index:
+ adapter: solr
+ url: http://solr.example.org/
+ proxy: http://foo:bar@proxy.example.com/
+ open_timeout: 120
+ read_timeout: 300
+```
+
+By default, a single environment uses the environment name `:production`.