jerry 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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