jerry 1.0.1 → 2.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7b5cd829b2996185f8ef423ab36069d638754a23
4
- data.tar.gz: 341a9a49bcc3a33149320ecd0ac003264a31e03f
3
+ metadata.gz: 0d97c357a390c069b18e9516ad1e5dac1fcdfea5
4
+ data.tar.gz: b33040e60cc83f2a481d0181c809e7b045ca96ec
5
5
  SHA512:
6
- metadata.gz: 233d001c305c8f4b57a2b2fa850b32459aacb3d86ab8a071079653b886b3691b4b048017d66e7b61a923e959ae8aed47eba4867af491246f6b2e5e54da277365
7
- data.tar.gz: 22afec9ff0c750bc7838a140aa356cc92df22a0d0cb0a8654628ffc9d7d91fe90e949974dc59674ff72707f0595acfd9448add6b05f42854d018d4309f303938
6
+ metadata.gz: eae3155f70ebb6bfd88cd878f2e4dfbe95f042a23439504d185717dd4f20583ffe3ad209e65511d922be4a5e403af7a836ef1969cdd45bff8241685e064838b0
7
+ data.tar.gz: f4cb19e8c591ae15611d0d02e1d7c6a46ea9fb1071ef37a83e454d48622565f537f59b44f7d67bc7be13ef9c13453cb8010d0a5fc66d4639000863f817f394d8
data/.gitignore CHANGED
@@ -20,7 +20,7 @@ build/
20
20
  ## Documentation cache and generated files:
21
21
  /.yardoc/
22
22
  /_yardoc/
23
- /doc/
23
+ /html-doc/
24
24
  /rdoc/
25
25
 
26
26
  ## Environment normalisation:
@@ -35,4 +35,3 @@ Gemfile.lock
35
35
 
36
36
  # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
37
37
  .rvmrc
38
-
data/.reek ADDED
@@ -0,0 +1,2 @@
1
+ IrresponsibleModule:
2
+ enabled: false
data/.rubocop.yml ADDED
@@ -0,0 +1,4 @@
1
+ Documentation:
2
+ Exclude:
3
+ - lib/jerry/version.rb
4
+ - spec/fixtures/*.rb
data/.travis.yml CHANGED
@@ -1,6 +1,15 @@
1
1
  language: ruby
2
+ sudo: false
3
+ cache: bundler
2
4
  rvm:
3
- - 2.1.2
5
+ - 2.2
6
+ - 2.1
7
+ - 2.0.0
4
8
  - 1.9.3
5
9
  - jruby-19mode
6
- - rbx-2
10
+ - ruby-head
11
+ - jruby-head
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: ruby-head
15
+ - rvm: jruby-head
data/.yardopts CHANGED
@@ -1,2 +1,9 @@
1
1
  --protected
2
- --no-private
2
+ --no-private
3
+ -r README.md
4
+ lib/jerry.rb
5
+ lib/**/*.rb
6
+ -o html-doc
7
+ -
8
+ LICENSE.txt
9
+ doc/*.md
data/Gemfile CHANGED
@@ -1,5 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
-
5
- gem 'coveralls', require: false
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ guard 'rake', task: 'default' do
2
+ watch 'Rakefile'
3
+ watch '.rspec'
4
+ watch '.rubocop.yml'
5
+ watch '.reek'
6
+ watch %r{^lib/}
7
+ watch %r{^spec/}
8
+ end
9
+
10
+ guard 'rake', task: 'doc' do
11
+ watch '.yardopts'
12
+ watch 'LICENSE.txt'
13
+ watch(/.+\.(md|markdown)/)
14
+ watch %r{^lib/}
15
+ watch %r{^doc/}
16
+ end
data/README.md CHANGED
@@ -4,114 +4,129 @@ Jerry
4
4
  [![Build Status](https://travis-ci.org/beraboris/jerry.svg?branch=master)](https://travis-ci.org/beraboris/jerry)
5
5
  [![Coverage Status](https://coveralls.io/repos/beraboris/jerry/badge.png)](https://coveralls.io/r/beraboris/jerry)
6
6
 
7
- Jerry rigs your application together. It's an [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control)
8
- container for ruby. Just tell Jerry how to build your application and it will set it all up for you.
7
+ Jerry is a Ruby
8
+ [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control)
9
+ container. You tell it how your classes depend on one another and it will create
10
+ your application and wire all the dependencies correctly.
9
11
 
10
- Installation
11
- ============
12
+ Jerry aims to be simple and straight forward. It also aims to be out of your
13
+ way. Your classes don't need to know anything about jerry.
12
14
 
13
- The usual stuff. Either
15
+ Why?
16
+ ----
14
17
 
15
- ```ruby
16
- gem 'jerry'
17
- ```
18
+ [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) is a
19
+ great pattern for building loosely coupled applications. It allows you to build
20
+ isolated components that can be swapped around.
18
21
 
19
- then
22
+ The problem with this pattern is that it leaves you with a bunch of classes that
23
+ you have to build and wire together yourself. Jerry does that for you.
20
24
 
21
- $ bundle install
25
+ Getting started
26
+ ---------------
22
27
 
23
- or
24
-
25
- $ gem install jerry
28
+ ### Important note
26
29
 
27
- Usage
28
- =====
30
+ Currently jerry only supports constructor injection. If you're hoping to use
31
+ setter injection, you're out of luck. You're going to need to switch to
32
+ constructor injection.
29
33
 
30
- Let's say you have the following code:
34
+ ### Install it
31
35
 
32
- ```ruby
33
- class Window; end
34
- class Door; end
35
-
36
- class House
37
- attr_reader :window, :door
38
-
39
- def initialize(window, door)
40
- @window = window
41
- @door = door
42
- end
43
- end
44
- ```
36
+ You have 3 options:
45
37
 
46
- First, require jerry:
38
+ Options 1: Add jerry to your `Gemfile`
47
39
 
48
40
  ```ruby
49
- require 'jerry'
41
+ gem 'jerry', '~> 2.0'
50
42
  ```
51
-
52
- Then, define a config class. This class tells jerry how to construct your application.
43
+
44
+ Option 2: Add jerry to your `*.gemspec`
53
45
 
54
46
  ```ruby
55
- class MyConfig < Jerry::Config
56
- component(:window) { Window.new }
57
- component(:door) { Door.new }
58
- component(:house) { House.new rig(:window), rig(:door) }
47
+ Gem::Specification.new do |spec|
48
+ # ...
49
+ spec.add_dependency 'jerry', '~> 2.0'
59
50
  end
60
51
  ```
61
52
 
62
- The `component` method defines a component. Usually you'll want a component per class. The `rig` method tells jerry to
63
- build a component.
53
+ Option 3: Just install it
64
54
 
65
- Finally, when you want to build your application, ask jerry to do it for you.
55
+ $ gem install jerry
56
+
57
+ ### Create a configuration class
66
58
 
67
59
  ```ruby
68
- jerry = Jerry.new MyConfig.new
69
- house = jerry.rig :house
70
- ```
60
+ require 'jerry'
71
61
 
72
- Scopes
73
- ------
62
+ class MyConfig < Jerry::Config
63
+ def initialize(foo_db_url, bar_db_url)
64
+ @foo_db_url = foo_db_url
65
+ @bar_db_url = bar_db_url
66
+ end
74
67
 
75
- The `component` method lets you specify a scope. The scope can either be `:single` or `:instance` and the default is
76
- `:single`. If you set the scope to `:single` only one instance of the component will be created. If you set it to
77
- `:instance`, a new instance of the component will be created each time you call `rig`.
68
+ bind Application, [FooService, BarService]
78
69
 
79
- ```ruby
80
- class MyConfig < Jerry::Config
81
- component(:window, scope: :instance) { Window.new }
82
- component(:door, scope: :single) { Door.new }
70
+ singleton bind FooService, [BarService, :foo_db]
71
+ singleton bind BarService, [:bar_db]
72
+
73
+ named_bind :foo_db Database [proc { @foo_db_url }]
74
+ named_bind :bar_db Database [proc { @bar_db_url }]
83
75
  end
84
- jerry = Jerry.new MyConfig.new
76
+ ```
85
77
 
86
- window_a = jerry.rig :window
87
- window_b = jerry.rig :window
88
- window_a.object_id == window_b.object_id
89
- #=> false
78
+ Let's go over what's going on in this configuration class.
79
+
80
+ First, we define a constructor. It takes two database urls and stores them as
81
+ instance variables. We'll go over what these urls are used for later. You should
82
+ note that this constructor has no special meaning to jerry. It's entirely
83
+ specific to this configuration class.
84
+
85
+ Second, we use `bind` to tell jerry how to wire the `Application` class. `bind`
86
+ takes two arguments. The first is the class we're telling jerry about and the
87
+ second is an array that tells jerry what constructor arguments to pass to the
88
+ class. Here you can notice that `Application` takes an instance of `FooService`
89
+ and an instance of `BarService` in its constructor.
90
+
91
+ Third, we tell jerry how to build `FooService` and `BarService`. Note that we're
92
+ calling `singleton bind` instead of `bind` here. `singleton` is used to tell
93
+ jerry that we want it to only instantiate the class we just described only once.
94
+ When we use `singleton` jerry will always pass the same instance of the given
95
+ class as a constructor argument. This is useful when some of your classes have
96
+ a persistent state. In this case, the `BarService` instance passed to both
97
+ `Application` and `FooService` will be the exact same instance.
98
+
99
+ Finally, we tell jerry how to build two instances of the `Database` class. The
100
+ first instance is named `:foo_db` and the second is named `:bar_db`. We
101
+ reference these instances when telling jerry about `FooService` and
102
+ `BarService`. This is what `named_bind` is for. We should also note that the
103
+ last arguments of the `named_bind` calls each contain a proc. In this case, the
104
+ procs are used to inject the database urls for each database. In a more general
105
+ sense, the procs are used to pass settings to various classes.
106
+
107
+ ### Create your application
90
108
 
91
- door_a = jerry.rig :door
92
- door_b = jerry.rig :door
93
- door_a.object_id == door_b.object_id
94
- #=> true
109
+ ```ruby
110
+ require 'jerry'
111
+
112
+ jerry = Jerry.new MyConfig.new('db://localhost/foo', 'db://localhost/bar')
113
+ app = jerry[Application]
95
114
  ```
96
115
 
97
- Multiple configs
98
- ----------------
116
+ Let's look at what's going on here.
99
117
 
100
- Jerry lets you use multiple configs. This way you can organize your configs however you want. You can pass multiple
101
- configs to `Jerry.new` or you can use `jerry << SomeConfig.new` to add configs to jerry.
118
+ First, we create an instance of our configuration class. We pass the urls for
119
+ our two databases to the constructor.
102
120
 
103
- If two configs define the same component, the config that was inserted last will have priority. With `Jerry.new`, the
104
- later arguments have priority.
121
+ Second, we create an instance of `Jerry` passing in the instance of our
122
+ configuration class.
105
123
 
106
- ```ruby
107
- class ConfA < Jerry::Config
108
- component(:thing) { "from a" }
109
- end
110
- class ConfB < Jerry::Config
111
- component(:thing) { "from b" }
112
- end
113
- jerry = Jerry.new ConfA.new, ConfB.new
124
+ Finally, we use the square bracket operator (`[]`) to create an instance of our
125
+ application. Of course our application is wired properly.
114
126
 
115
- jerry.rig :thing
116
- #=> "from b"
117
- ```
127
+ Learn more
128
+ ----------
129
+
130
+ If you'd like to learn more, here's some more documentation:
131
+
132
+ - [Multiple configurations](doc/multiple-configurations.md)
data/Rakefile CHANGED
@@ -1,5 +1,24 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
+ require 'yard'
4
+ require 'rubocop/rake_task'
5
+ require 'reek/rake/task'
6
+
7
+ task default: [:spec, :lint]
3
8
 
4
9
  RSpec::Core::RakeTask.new :spec
5
- task :default => :spec
10
+ YARD::Rake::YardocTask.new :doc
11
+
12
+ task lint: [:reek, :rubocop]
13
+
14
+ desc 'Run rubocop linter on lib/**/*.rb and spec/**/*.rb'
15
+ RuboCop::RakeTask.new :rubocop do |t|
16
+ t.patterns = ['lib/**/*.rb', 'spec/**/*.rb']
17
+ t.fail_on_error = false
18
+ end
19
+
20
+ desc 'Run reek linter on lib/**/*.rb'
21
+ Reek::Rake::Task.new :reek do |t|
22
+ t.source_files = 'lib/**/*.rb'
23
+ t.fail_on_error = false
24
+ end
data/bin/console ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ cd "$(dirname "$0")/.."
3
+ bundle exec pry -Ilib -rjerry "$@"
data/bin/guard ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ cd "$(dirname "$0")/.."
3
+ bundle exec guard "$@"
data/bin/rake ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ cd "$(dirname "$0")/.."
3
+ bundle exec rake "$@"
data/bin/setup ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ cd "$(dirname "$0")/.."
3
+ bundle install "$@"
@@ -0,0 +1,51 @@
1
+ Multiple configurations
2
+ -----------------------
3
+
4
+ Jerry allows you to define and use multiple configurations. This way you can
5
+ separate the dependency configurations for different parts of your application.
6
+
7
+ Let's look at an example. In this example, we have a simple on-line store. It
8
+ has users, products and shopping carts. We can create separate configurations
9
+ for user, product, and shopping cart related classed. Users, products and
10
+ shopping carts each have a service that talks to the database and other services
11
+ and a controller that talks to the service. Here's what it might look like:
12
+
13
+ ```ruby
14
+ class DatabaseConfig < Jerry::Config
15
+ # database connector thingy
16
+ bind Database
17
+ end
18
+
19
+ class UserConfig < Jerry::Config
20
+ bind UserService, [Database]
21
+ bind UserController, [UserService]
22
+ end
23
+
24
+ class ProductConfig < Jerry::Config
25
+ bind ProductService, [Database]
26
+ bind ProductController, [ProductService]
27
+ end
28
+
29
+ class ShoppingCartConfig < Jerry::Config
30
+ bind ShoppingCartService, [Database, ProductService, UserService]
31
+ bind ShoppingCartController, [ShoppingCartService]
32
+ end
33
+
34
+ class AppConfig < Jerry::Config
35
+ bind Application, [UserController, ProductController, ShoppingCartController]
36
+ end
37
+
38
+ jerry = Jerry.new(
39
+ DatabaseConfig.new,
40
+ AppConfig.new,
41
+ UserConfig.new,
42
+ ProductConfig.new,
43
+ ShoppingCartConfig.new
44
+ )
45
+
46
+ app = jerry[Application]
47
+ ```
48
+
49
+ Note that in the example above, some the configurations reference classes that
50
+ are configured in other configurations. This is perfectly fine. When
51
+ instantiating a class, jerry will look at all the configurations.
data/jerry.gemspec CHANGED
@@ -14,12 +14,17 @@ Gem::Specification.new do |spec|
14
14
  spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
18
  spec.require_paths = ['lib']
20
19
 
21
20
  spec.add_development_dependency 'bundler', '~> 1.6'
22
21
  spec.add_development_dependency 'rake'
23
- spec.add_development_dependency 'rspec', '~> 3.0.0'
24
- spec.add_development_dependency 'yard', '~> 0.8.7.4'
22
+ spec.add_development_dependency 'guard'
23
+ spec.add_development_dependency 'guard-rake'
24
+ spec.add_development_dependency 'rspec', '~> 3.3'
25
+ spec.add_development_dependency 'coveralls', '~> 0.8'
26
+ spec.add_development_dependency 'yard', '~> 0.8.7'
27
+ spec.add_development_dependency 'pry', '~> 0.10'
28
+ spec.add_development_dependency 'rubocop', '~> 0.32'
29
+ spec.add_development_dependency 'reek', '~> 2.2'
25
30
  end
@@ -0,0 +1,30 @@
1
+ class Jerry
2
+ # A provider that instanciates a given class by collecting constructor
3
+ # arguments through a given jerry instance.
4
+ class ClassProvider
5
+ # @param klass [Class] class to be instanciated
6
+ # @param args_spec [Array<Clsss, Symbol, Proc>] specification for the
7
+ # constructor arguments. Classes and Symbols are used to collect the
8
+ # arguments from the jerry instance. Procs are called to generate
9
+ # arguments.
10
+ def initialize(klass, args_spec)
11
+ @klass = klass
12
+ @args_spec = args_spec
13
+ end
14
+
15
+ # @param jerry [Jerry] a jerry instance
16
+ # @param config [Jerry::Config] the config holding the provider
17
+ # @return An instance of the class given in the constructor
18
+ def call(jerry, config)
19
+ args = @args_spec.map do |spec|
20
+ if spec.respond_to? :call
21
+ config.instance_exec(jerry, config, &spec)
22
+ else
23
+ jerry[spec]
24
+ end
25
+ end
26
+
27
+ @klass.new(*args)
28
+ end
29
+ end
30
+ end
data/lib/jerry/config.rb CHANGED
@@ -1,85 +1,88 @@
1
- class Jerry
2
- # Indicated that an error occurred when defining a component
3
- class ComponentError < StandardError; end
1
+ require 'jerry/class_provider'
2
+ require 'jerry/errors'
4
3
 
5
- # Base class for all jerry configs.
4
+ class Jerry
5
+ # A configuration specifies how to wire parts of an application
6
+ #
7
+ # @abstract Subclass this class in order to create a configuration
8
+ # @example Basic usage
9
+ # class Door; end
10
+ # class Window; end
6
11
  #
7
- # A config is a class that tells jerry about a set of available
8
- # components and how those should be created
12
+ # class House
13
+ # def initialize(door, window)
14
+ # # ...
15
+ # end
16
+ # end
9
17
  #
10
- # @abstract Subclass to define a config
11
- # @example
12
18
  # class MyConfig < Jerry::Config
13
- # component(:service) { MyService.new }
14
- # component(:app) { MyApp.new rig(:service) }
19
+ # bind House, [Door, Window]
20
+ # bind Door
21
+ # bind Window
15
22
  # end
16
23
  class Config
17
24
  class << self
18
- # @return [Array<Symbol>] list of the components defined by the config
19
- def components
20
- @components ||= []
25
+ # Specify how to wire the dependencies of a given class
26
+ #
27
+ # @param klass [Class] The class to wire the dependencies for
28
+ # @param ctor_args [Array<Class, Symbol, Proc>] specifies the arguments to
29
+ # be given to the constructor
30
+ # @return [Class] the class that will be instanciated
31
+ def bind(klass, ctor_args = [])
32
+ named_bind klass, klass, ctor_args
21
33
  end
22
34
 
23
- # Defines a component
35
+ # Specify how to wire the dependencies of a given class giving it a name
24
36
  #
25
- # @param [Symbol] name name of the component
26
- # @param [Hash] options options hash see supported options
27
- # @option options [Symbol] (:single) The scope of the component. Can be either :single or :instance.
28
- # When the scope is :single, only one instance of the component will be created and every call
29
- # to Jerry#rig will return the same instance. When the scope is :instance, every call to Jerry#rig
30
- # will return a new instance.
31
- # @yield Block used to instantiate the component. This block in only called when Jerry#rig is called.
32
- # @raise [Jerry::ComponentError] when the block is missing or the scope is invalid
33
- def component(name, options={}, &block)
34
- raise Jerry::ComponentError, "could not define component #{name}, block is missing" if block.nil?
37
+ # @param name [Symbol] The name used to identify this way of building the
38
+ # given class
39
+ # @param klass [Class] The class to wire the dependencies for
40
+ # @param ctor_args [Array<Class, Symbol, Proc>] specifies the arguments to
41
+ # be given to the constructor
42
+ # @return [Symbol] the name of the class that will be instanciated
43
+ def named_bind(name, klass, ctor_args = [])
44
+ providers[name] = ClassProvider.new klass, ctor_args
45
+ name
46
+ end
35
47
 
36
- scope = options[:scope] || :single
37
- unless [:single, :instance].include? scope
38
- raise Jerry::ComponentError, "could not define component #{name}, scope #{scope} is unknown"
39
- end
48
+ # Specifies that a class should only be instanciated once
49
+ #
50
+ # @param key [Class, Symbol] the class or the name of the class that
51
+ # should only be instanciated once
52
+ def singleton(key)
53
+ return unless providers.key? key
40
54
 
41
- define_method name do
42
- case scope
43
- when :single
44
- cache[name] ||= instance_eval(&block)
45
- when :instance
46
- instance_eval(&block)
47
- end
48
- end
55
+ provider = providers[key]
49
56
 
50
- components << name
57
+ instance = nil
58
+ providers[key] = ->(*args) { instance ||= provider.call(*args) }
51
59
  end
52
- end
53
60
 
54
- # @return [Array<Symbol>] list of components defined by the config
55
- def components
56
- self.class.components
61
+ def providers
62
+ @providers ||= {}
63
+ end
57
64
  end
58
65
 
59
- # Jerry instance the config is part of
60
- #
61
- # This gets set by Jerry when it loads a config
66
+ # The jerry instance this config is part of
62
67
  attr_writer :jerry
63
68
 
64
- protected
69
+ # @return an instance of an object wired by the config
70
+ def [](key)
71
+ provider = self.class.providers[key]
65
72
 
66
- # Creates a component
67
- #
68
- # This should be used inside the block passed to Config::component
69
- def rig(component)
70
- @jerry.rig component
71
- end
72
-
73
- # Check if given component exists
74
- #
75
- # This should be used inside the block passed to Config::component
76
- def knows?(component)
77
- @jerry.knows? component
73
+ if provider
74
+ provider.call @jerry, self
75
+ else
76
+ fail InstantiationError,
77
+ "Failed to instanciate #{key}. Can't find provider for it"
78
+ end
79
+ rescue RuntimeError
80
+ raise InstantiationError, "Provider for #{key} raised an error"
78
81
  end
79
82
 
80
- # Used internally to cache single instance components
81
- def cache
82
- @cache ||= {}
83
+ # @return true if this config can provide the given key, false otherwise
84
+ def knows?(key)
85
+ self.class.providers.key? key
83
86
  end
84
87
  end
85
- end
88
+ end
@@ -0,0 +1,16 @@
1
+ require 'English'
2
+
3
+ class Jerry
4
+ # Base error class for Jerry that allows recording causing exceptions
5
+ class Error < RuntimeError
6
+ attr_reader :cause
7
+
8
+ def initialize(message = nil)
9
+ super
10
+ @cause = $ERROR_INFO
11
+ end
12
+ end
13
+
14
+ # Failed to instanciate a class
15
+ class InstantiationError < Jerry::Error; end
16
+ end
data/lib/jerry/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Jerry
2
- VERSION = '1.0.1'
2
+ VERSION = '2.0.0'
3
3
  end