Skip to content

Commit

Permalink
simplified documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Boris Bera committed Sep 19, 2015
1 parent a2589d4 commit 76a295d
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 212 deletions.
1 change: 1 addition & 0 deletions .yardopts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ lib/**/*.rb
-o html-doc
-
LICENSE.txt
doc/*.md
1 change: 1 addition & 0 deletions Guardfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ guard 'rake', task: 'doc' do
watch 'LICENSE.txt'
watch(/.+\.(md|markdown)/)
watch %r{^lib/}
watch %r{^doc/}
end
293 changes: 81 additions & 212 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,260 +4,129 @@ Jerry
[![Build Status](https://travis-ci.org/beraboris/jerry.svg?branch=master)](https://travis-ci.org/beraboris/jerry)
[![Coverage Status](https://coveralls.io/repos/beraboris/jerry/badge.png)](https://coveralls.io/r/beraboris/jerry)


Jerry is a Ruby
[Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control)
container. You tell it how your classes depend on one another and it will create
your application and wire all the dependencies correctly.

Installation
------------

Add it to your `Gemfile`
Jerry aims to be simple and straight forward. It also aims to be out of your
way. Your classes don't need to know anything about jerry.

```ruby
gem 'jerry', '~> 2.0'
```
Why?
----

then run
[Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) is a
great pattern for building loosely coupled applications. It allows you to build
isolated components that can be swapped around.

$ bundle install
The problem with this pattern is that it leaves you with a bunch of classes that
you have to build and wire together yourself. Jerry does that for you.

Usage
-----
Getting started
---------------

Jerry expect your classes to take their dependencies in their constructors. If
you're not familiar with this pattern, it's called
[Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection).
It helps you decouple your code and allows you to build reusable components.
### Important note

Let's say you have the following class structure:
Currently jerry only supports constructor injection. If you're hoping to use
setter injection, you're out of luck. You're going to need to switch to
constructor injection.

```ruby
class Door; end
class Window; end
### Install it

class House
def initialize(door, window)
# ...
end
end
```
You have 3 options:

To create an instance of `House`, you need to call `new` passing an both an
instance of `Door` and an instance of `Window`. Jerry can do this for you. All
you have to do is create a configuration and tell jerry to use it.
Options 1: Add jerry to your `Gemfile`

```ruby
require 'jerry'

class HouseConfig < Jerry::Config
bind Door
bind Window
bind House, [Door, Window]
end

jerry = Jerry.new HouseConfig.new
jerry[House]
# => #<House:0x00000002a54978
# @door=#<Door:0x00000002a54b08>,
# @window=#<Window:0x00000002a549a0>>
```

Let's break the above example down a little bit.

First, we create the `HouseConfig` class.

`HouseConfig` is used to tell jerry how to build your classes. This is a
class you have to create. It has to inherit from `Jerry::Config`. Within this
class you can use `bind` to tell jerry how to instantiate your classes.

`bind` takes two arguments. The first is the class you're telling jerry about.
The second is a specification for the constructor arguments. If the second
argument is missing, no arguments will be passed to the constructor.

When we're calling `bind Door`, we're telling jerry that the `Door` class should
be instantiated by calling the constructor with no arguments. We're doing the
same thing for `Window` class.

When we're calling `bind House, [Door, Window]`, we're telling jerry that the
`House` class should be instantiated calling the constructor and passing an
instance of `Door` as the first argument and an instance of `Window` as the
second argument. Since we've told jerry about the `Door` and `Window` classes,
it'll figure out how to instantiate those all by itself.

Second, we create an instance of the `Jerry` class passing in an instance of our
configuration class. What's cool is that you can have multiple configurations.
We'll look into this later on.

Finally, we ask jerry to create an instance of the `House` class by using the
`[]` operator. As you can see from the output, jerry passed in an instance of
`Door` and `Window`.

### Dealing with settings

Sometimes, you want to wire settings into your classes. This is stuff like URIs,
host names, port numbers, credentials and API keys. With jerry, you can just
wire those into the constructor like you would do with regular classes. Let's
look at an example.

For this example, we're trying to wire together an application that talks to a
database. The database has a URI that it connects to. Here's what it looks like.

```ruby
class Database
def initialize(uri)
@uri = uri
end
end

class Application
def initialize(db)
@db = db
end
end
gem 'jerry', '~> 2.0'
```

We can write a simple configuration for these classes. Here's what it looks
like.
Option 2: Add jerry to your `*.gemspec`

```ruby
class AppConfig < Jerry::Config
def initialize(database_uri)
@database_uri = database_uri
end

bind Database, [proc { @database_uri }]
bind Application, [Database]
Gem::Specification.new do |spec|
# ...
spec.add_dependency 'jerry', '~> 2.0'
end

jerry = Jerry.new AppConfig.new('foo://localhost:1234')
jerry[Application]
#=> #<Application:0x0000000178c110
# @db=#<Database:0x0000000178c138 @uri="foo://localhost:1234">>
```

There are two key things to notice here. First, `AppConfig` takes a single
constructor argument. It's the database's URI. It takes this argument and stores
it as an instance variable. Second, it passes a proc in the argument
specification of the `bind` call. This is how the database uri is passed to the
constructor. The proc is executed in the configuration's context. This means
that you can access all the private methods and instance variables of the
configuration.
Option 3: Just install it

### Wire one class in multiple ways
$ gem install jerry

Sometimes, you want to wire a single class in more than one way. One example of
this would be an application that connects to two databases. Here's what the
classes would look like:
### Create a configuration class

```ruby
class Database
attr_reader :uri
require 'jerry'

def initialize(uri)
@uri = uri
class MyConfig < Jerry::Config
def initialize(foo_db_url, bar_db_url)
@foo_db_url = foo_db_url
@bar_db_url = bar_db_url
end
end

class Application
attr_reader :foo_db, :bar_db
bind Application, [FooService, BarService]

def initialize(foo_db, bar_db)
@foo_db = foo_db
@bar_db = bar_db
end
singleton bind FooService, [BarService, :foo_db]
singleton bind BarService, [:bar_db]

named_bind :foo_db Database [proc { @foo_db_url }]
named_bind :bar_db Database [proc { @bar_db_url }]
end
```

What we need here is two instances of the `Database` class. Each instance has
a different URI. This can be done by using `named_bind`. Here's what it looks
like:
Let's go over what's going on in this configuration class.

First, we define a constructor. It takes two database urls and stores them as
instance variables. We'll go over what these urls are used for later. You should
note that this constructor has no special meaning to jerry. It's entirely
specific to this configuration class.

Second, we use `bind` to tell jerry how to wire the `Application` class. `bind`
takes two arguments. The first is the class we're telling jerry about and the
second is an array that tells jerry what constructor arguments to pass to the
class. Here you can notice that `Application` takes an instance of `FooService`
and an instance of `BarService` in its constructor.

Third, we tell jerry how to build `FooService` and `BarService`. Note that we're
calling `singleton bind` instead of `bind` here. `singleton` is used to tell
jerry that we want it to only instantiate the class we just described only once.
When we use `singleton` jerry will always pass the same instance of the given
class as a constructor argument. This is useful when some of your classes have
a persistent state. In this case, the `BarService` instance passed to both
`Application` and `FooService` will be the exact same instance.

Finally, we tell jerry how to build two instances of the `Database` class. The
first instance is named `:foo_db` and the second is named `:bar_db`. We
reference these instances when telling jerry about `FooService` and
`BarService`. This is what `named_bind` is for. We should also note that the
last arguments of the `named_bind` calls each contain a proc. In this case, the
procs are used to inject the database urls for each database. In a more general
sense, the procs are used to pass settings to various classes.

### Create your application

```ruby
class MultiDbAppConfig < Jerry::Config
def initialize(foo_uri, bar_uri)
@foo_uri = foo_uri
@bar_uri = bar_uri
end

named_bind :foo_db, Database, [proc { @foo_uri }]
named_bind :bar_db, Database, [proc { @bar_uri }]
bind Application, [:foo_db, :bar_db]
end

jerry = Jerry.new MultiDbAppConfig.new(
'somedb://foo.db.net',
'somedb://bar.db.net'
)
require 'jerry'

jerry[Application]
#=> #<Application:0x00000001b56340
# @bar_db=#<Database:0x00000001b56430 @uri="somedb://bar.db.net">,
# @foo_db=#<Database:0x00000001b565e8 @uri="somedb://foo.db.net">>
jerry = Jerry.new MyConfig.new('db://localhost/foo', 'db://localhost/bar')
app = jerry[Application]
```

In the above example, we define a config that takes two arguments in its
constructor. These are the URIs for the two databases (here called `foo` and
`bar`).
Let's look at what's going on here.

In this config, we use `named_bind` to tell jerry how to wire an instance of
`Database` called `:foo_db` constructed by passing in `@foo_uri` as its first
constructor argument. We do the same thing to tell jerry how to wire another
instance of `Database` called `:bar_db` constructed by passing in `@bar_uri` as
its first constructor argument.
First, we create an instance of our configuration class. We pass the urls for
our two databases to the constructor.

Finally, we use `bind` to tell jerry how to wire instances of `Application`.
When specifying the constructor arguments, we use their names (`:foo_db` and
`:bar_db`) to identify them.
Second, we create an instance of `Jerry` passing in the instance of our
configuration class.

### Multiples configurations
Finally, we use the square bracket operator (`[]`) to create an instance of our
application. Of course our application is wired properly.

Jerry allows you to define and use multiple configurations. This way you can
separate the dependency configurations for different parts of your application.
Learn more
----------

Let's look at an example. In this example, we have a simple on-line store. It
has users, products and shopping carts. We can create separate configurations
for user, product, and shopping cart related classed. Users, products and
shopping carts each have a service that talks to the database and other services
and a controller that talks to the service. Here's what it might look like:

```ruby
class DatabaseConfig < Jerry::Config
# database connector thingy
bind Database
end

class UserConfig < Jerry::Config
bind UserService, [Database]
bind UserController, [UserService]
end

class ProductConfig < Jerry::Config
bind ProductService, [Database]
bind ProductController, [ProductService]
end

class ShoppingCartConfig < Jerry::Config
bind ShoppingCartService, [Database, ProductService, UserService]
bind ShoppingCartController, [ShoppingCartService]
end

class AppConfig < Jerry::Config
bind Application, [UserController, ProductController, ShoppingCartController]
end

jerry = Jerry.new(
DatabaseConfig.new,
AppConfig.new,
UserConfig.new,
ProductConfig.new,
ShoppingCartConfig.new
)

app = jerry[Application]
```
If you'd like to learn more, here's some more documentation:

Note that in the example above, some the configurations reference classes that
are configured in other configurations. This is perfectly fine. When
instantiating a class, jerry will look at all the configurations.
- [Multiple configurations](doc/multiple-configurations)
Loading

0 comments on commit 76a295d

Please sign in to comment.