Add docsite for version 0.8

This commit is contained in:
Piotr Solnica 2019-10-04 10:58:29 +02:00
parent f908d26621
commit 763744e593
No known key found for this signature in database
GPG Key ID: 66BF2FDA7BA0F29C
3 changed files with 247 additions and 0 deletions

View File

@ -0,0 +1,131 @@
---
title: Introduction & Usage
description: Simple, thread-safe container
layout: gem-single
order: 3
type: gem
name: dry-container
sections:
- registry-and-resolver
- testing
---
### Introduction
`dry-container` is a simple, thread-safe container, intended to be one half of a dependency injection system, possibly in combination with [dry-auto_inject](/gems/dry-auto_inject/).
At its most basic, dependency injection is a simple technique that makes it possible to implement patterns or principles of code design that rely on object composition, such as the [SOLID principles](https://en.wikipedia.org/wiki/SOLID). By being passed its dependencies instead of instantiating them itself, your code can be written to depend on abstractions, with implementations that can vary independently, potentially at runtime or for specific use-cases, such as injecting a double instead of an expensive web service call when running tests. A container offers two main improvements to basic dependency injection: it takes the work out of manually instantiating and composing trees of dependencies, and it makes it trivial to swap out one implementation of a dependency for another.
Note that dependency *injection*, dependency *inversion*, and *inversion of control* are related, but distinct, concepts that are often confused or conflated. [**Inversion of control**](https://en.wikipedia.org/wiki/Inversion_of_control) is an architectural pattern by which a low-level *system* passes control to higher-level application code, as opposed to the classical pattern, where higher-level code calls directly into a lower-level dependency. **Dependency inversion** is a principle that encourages thoughtfully designing the interfaces that your classes depend on, instead of tightly coupling to an *external* dependency's interface. This shouldn't imply that the external dependency itself changes in any way; instead it encourages the use of bridge, facade, or adapter classes to implement the interface that you designed using the third party dependency's public interface. **Dependency injection**, finally, is the practical technique of providing an object with its dependencies, instead of hard-coding them.
`dry-container` makes it much easier than with so-called "idiomatic" Ruby to make use of any one or all three of these, as desired.
### Brief Example
```ruby
container = Dry::Container.new
container.register(:parrot) { |a| puts a }
parrot = container.resolve(:parrot)
parrot.call("Hello World")
# Hello World
# => nil
```
### Detailed Example
```ruby
User = Struct.new(:name, :email)
data_store = ThreadSafe::Cache.new.tap do |ds|
ds[:users] = ThreadSafe::Array.new
end
# Initialize container
container = Dry::Container.new
# Register an item with the container to be resolved later
container.register(:data_store, data_store)
container.register(:user_repository, -> { container.resolve(:data_store)[:users] })
# Resolve an item from the container
container.resolve(:user_repository) << User.new('Jack', 'jack@dry-container.com')
# You can also resolve with []
container[:user_repository] << User.new('Jill', 'jill@dry-container.com')
# => [
# #<struct User name="Jack", email="jack@dry-container.com">,
# #<struct User name="Jill", email="jill@dry-container.com">
# ]
# If you wish to register an item that responds to call but don't want it to be
# called when resolved, you can use the options hash
container.register(:proc, -> { :result }, call: false)
container.resolve(:proc)
# => #<Proc:0x007fa75e652c98@(irb):25 (lambda)>
# You can also register using a block
container.register(:item) do
:result
end
container.resolve(:item)
# => :result
container.register(:block, call: false) do
:result
end
container.resolve(:block)
# => #<Proc:0x007fa75e6830f0@(irb):36>
# You can also register items under namespaces using the #namespace method
container.namespace('repositories') do
namespace('checkout') do
register('orders') { ThreadSafe::Array.new }
end
end
container.resolve('repositories.checkout.orders')
# => []
# Or import a namespace
ns = Dry::Container::Namespace.new('repositories') do
namespace('authentication') do
register('users') { ThreadSafe::Array.new }
end
end
container.import(ns)
container.resolve('repositories.authentication.users')
# => []
# Also, you can import namespaces in container class
Repositories = Dry::Container::Namespace.new('repositories') do
namespace('authentication') do
register('users') { ThreadSafe::Array.new }
end
end
class Container
extend Dry::Container::Mixin
import Repositories
end
Container.resolve('repositories.authentication.users')
# => []
```
You can also get container behaviour at both the class and instance level via the mixin:
```ruby
class Container
extend Dry::Container::Mixin
end
Container.register(:item, :my_item)
Container.resolve(:item)
# => :my_item
class ContainerObject
include Dry::Container::Mixin
end
container = ContainerObject.new
container.register(:item, :my_item)
container.resolve(:item)
# => :my_item
```

View File

@ -0,0 +1,77 @@
---
title: Registry &amp; Resolver
layout: gem-single
name: dry-container
---
### Register options
#### `call`
This boolean option determines whether or not the registered item should be invoked when resolved, i.e.
```ruby
container = Dry::Container.new
container.register(:key_1, call: false) { "Integer: #{rand(1000)}" }
container.register(:key_2, call: true) { "Integer: #{rand(1000)}" }
container.resolve(:key_1) # => <Proc:0x007f98c90454c0@dry_c.rb:23>
container.resolve(:key_1) # => <Proc:0x007f98c90454c0@dry_c.rb:23>
container.resolve(:key_2) # => "Integer: 157"
container.resolve(:key_2) # => "Integer: 713"
```
#### `memoize`
This boolean option determines whether or not the registered item should be memoized on the first invocation, i.e.
```ruby
container = Dry::Container.new
container.register(:key_1, memoize: true) { "Integer: #{rand(1000)}" }
container.register(:key_2, memoize: false) { "Integer: #{rand(1000)}" }
container.resolve(:key_1) # => "Integer: 734"
container.resolve(:key_1) # => "Integer: 734"
container.resolve(:key_2) # => "Integer: 855"
container.resolve(:key_2) # => "Integer: 282"
```
### Customization
You can configure how items are registered and resolved from the container. Currently, registry can be as simple as a proc
but custom resolver should subclass the default one or have the same public interface.
```ruby
class CustomResolver < Dry::Container::Registry
RENAMED_KEYS = { 'old' => 'new' }
def call(container, key)
container.fetch(key.to_s) {
fallback_key = RENAMED_KEYS.fetch(key.to_s) {
raise Error, "Missing #{ key }"
}
container.fetch(fallback_key) {
raise Error, "Missing #{ key } and #{ fallback_key }"
}
}.call
end
end
class Container
extend Dry::Container::Mixin
config.registry = ->(container, key, item, options) { container[key] = item }
config.resolver = CustomResolver
end
class ContainerObject
include Dry::Container::Mixin
config.registry = ->(container, key, item, options) { container[key] = item }
config.resolver = CustomResolver
end
```
This allows you to customise the behaviour of Dry::Container, for example, the default registry (Dry::Container::Registry) will raise a Dry::Container::Error exception if you try to register under a key that is already used, you may want to just overwrite the existing value in that scenario, configuration allows you to do so.

View File

@ -0,0 +1,39 @@
---
title: Testing
layout: gem-single
name: dry-container
---
### Stub
To stub your containers call `#stub` method:
```ruby
container = Dry::Container.new
container.register(:redis) { "Redis instance" }
container[:redis] # => "Redis instance"
require 'dry/container/stub'
# before stub you need to enable stubs for specific container
container.enable_stubs!
container.stub(:redis, "Stubbed redis instance")
container[:redis] # => "Stubbed redis instance"
```
Also, you can unstub container:
```ruby
container = Dry::Container.new
container.register(:redis) { "Redis instance" }
container[:redis] # => "Redis instance"
require 'dry/container/stub'
container.enable_stubs!
container.stub(:redis, "Stubbed redis instance")
container[:redis] # => "Stubbed redis instance"
container.unstub(:redis) # => "Redis instance"
```