callme 0.5.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 +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +3 -0
- data/.yardops +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +22 -0
- data/README.md +147 -0
- data/Rakefile +1 -0
- data/bin/console +7 -0
- data/callme.gemspec +24 -0
- data/lib/callme/args_validator.rb +49 -0
- data/lib/callme/bean_factory.rb +115 -0
- data/lib/callme/bean_metadata.rb +70 -0
- data/lib/callme/beans_metadata_storage.rb +32 -0
- data/lib/callme/const_loaders/active_support.rb +7 -0
- data/lib/callme/const_loaders/native.rb +9 -0
- data/lib/callme/container.rb +107 -0
- data/lib/callme/errors.rb +10 -0
- data/lib/callme/inject.rb +36 -0
- data/lib/callme/scopes/prototype_scope.rb +25 -0
- data/lib/callme/scopes/request_scope.rb +33 -0
- data/lib/callme/scopes/singleton_scope.rb +29 -0
- data/lib/callme/scopes.rb +2 -0
- data/lib/callme/version.rb +3 -0
- data/lib/callme.rb +4 -0
- data/lib/ext/vendored_activesupport.rb +181 -0
- data/spec/callme/container_spec.rb +235 -0
- data/spec/callme/inject_spec.rb +55 -0
- data/spec/spec_helper.rb +7 -0
- metadata +120 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
# Extend object with the bean injection mechanism
|
2
|
+
# Example of usage:
|
3
|
+
# class Bar
|
4
|
+
# end
|
5
|
+
#
|
6
|
+
# class Foo
|
7
|
+
# inject :bar
|
8
|
+
# or:
|
9
|
+
# inject :some_bar, ref: bar
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# ioc_container[:foo].bar == ioc_container[:bar]
|
13
|
+
class Object
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
def inject(dependency_name, options = {})
|
18
|
+
unless dependency_name.is_a?(Symbol)
|
19
|
+
raise ArgumentError, "dependency name should be a symbol"
|
20
|
+
end
|
21
|
+
unless options.is_a?(Hash)
|
22
|
+
raise ArgumentError, "second argument for inject method should be a Hash"
|
23
|
+
end
|
24
|
+
unless respond_to?(:_callme_injectable_attrs)
|
25
|
+
class_attribute :_callme_injectable_attrs
|
26
|
+
self._callme_injectable_attrs = { dependency_name => options.dup }
|
27
|
+
else
|
28
|
+
self._callme_injectable_attrs =
|
29
|
+
self._callme_injectable_attrs.merge(dependency_name => options.dup)
|
30
|
+
end
|
31
|
+
attr_accessor dependency_name
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Prototype scope instantiates new bean instance
|
2
|
+
# on each +get_bean+ call
|
3
|
+
class Callme::Scopes::PrototypeScope
|
4
|
+
|
5
|
+
# Constructon
|
6
|
+
# @param bean_factory bean factory
|
7
|
+
def initialize(bean_factory)
|
8
|
+
@bean_factory = bean_factory
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get new bean instance
|
12
|
+
# @param bean_metadata [BeanMetadata] bean metadata
|
13
|
+
# @returns bean instance
|
14
|
+
def get_bean(bean_metadata)
|
15
|
+
@bean_factory.create_bean_and_save(bean_metadata, {})
|
16
|
+
end
|
17
|
+
|
18
|
+
# Delete bean from scope,
|
19
|
+
# because Prototype scope doesn't store bean
|
20
|
+
# then do nothing here
|
21
|
+
#
|
22
|
+
# @param bean_metadata [BeanMetadata] bean metadata
|
23
|
+
def delete_bean(bean_metadata)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'request_store'
|
2
|
+
|
3
|
+
# Request scope instantiates new bean instance
|
4
|
+
# on each new HTTP request
|
5
|
+
class Callme::Scopes::RequestScope
|
6
|
+
|
7
|
+
# Constructon
|
8
|
+
# @param bean_factory bean factory
|
9
|
+
def initialize(bean_factory)
|
10
|
+
@bean_factory = bean_factory
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns a bean from the +RequestStore+
|
14
|
+
# RequestStore is a wrapper for Thread.current
|
15
|
+
# which clears it on each new HTTP request
|
16
|
+
#
|
17
|
+
# @param bean_metadata [BeanMetadata] bean metadata
|
18
|
+
# @returns bean instance
|
19
|
+
def get_bean(bean_metadata)
|
20
|
+
RequestStore.store[:_callme_beans] ||= {}
|
21
|
+
if bean = RequestStore.store[:_callme_beans][bean_metadata.name]
|
22
|
+
bean
|
23
|
+
else
|
24
|
+
@bean_factory.create_bean_and_save(bean_metadata, RequestStore.store[:_callme_beans])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Delete bean from scope
|
29
|
+
# @param bean_metadata [BeanMetadata] bean metadata
|
30
|
+
def delete_bean(bean_metadata)
|
31
|
+
RequestStore.store[:_callme_beans].delete(bean_metadata.name)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Singleton scope returns the same bean instance
|
2
|
+
# on each call
|
3
|
+
class Callme::Scopes::SingletonScope
|
4
|
+
|
5
|
+
# Constructon
|
6
|
+
# @param bean_factory bean factory
|
7
|
+
def initialize(bean_factory)
|
8
|
+
@beans = {}
|
9
|
+
@bean_factory = bean_factory
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns the same bean instance
|
13
|
+
# on each call
|
14
|
+
# @param bean_metadata [BeanMetadata] bean metadata
|
15
|
+
# @returns bean instance
|
16
|
+
def get_bean(bean_metadata)
|
17
|
+
if bean = @beans[bean_metadata.name]
|
18
|
+
bean
|
19
|
+
else
|
20
|
+
@bean_factory.create_bean_and_save(bean_metadata, @beans)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Delete bean from scope
|
25
|
+
# @param bean_metadata [BeanMetadata] bean metadata
|
26
|
+
def delete_bean(bean_metadata)
|
27
|
+
@beans.delete(bean_metadata.name)
|
28
|
+
end
|
29
|
+
end
|
data/lib/callme.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
##### vendored code from active_support (5.0.0) for Class.class_attribute ####
|
2
|
+
# require 'active_support/core_ext/class/attribute'
|
3
|
+
# require 'active_support/core_ext/kernel/singleton_class'
|
4
|
+
# require 'active_support/core_ext/module/remove_method'
|
5
|
+
# require 'active_support/core_ext/array/extract_options'
|
6
|
+
|
7
|
+
unless defined?(ActiveSupport)
|
8
|
+
class Module
|
9
|
+
def remove_possible_method(method)
|
10
|
+
if method_defined?(method) || private_method_defined?(method)
|
11
|
+
undef_method(method)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def redefine_method(method, &block)
|
16
|
+
remove_possible_method(method)
|
17
|
+
define_method(method, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Kernel
|
22
|
+
# class_eval on an object acts like singleton_class.class_eval.
|
23
|
+
def class_eval(*args, &block)
|
24
|
+
singleton_class.class_eval(*args, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Hash
|
29
|
+
# By default, only instances of Hash itself are extractable.
|
30
|
+
# Subclasses of Hash may implement this method and return
|
31
|
+
# true to declare themselves as extractable. If a Hash
|
32
|
+
# is extractable, Array#extract_options! pops it from
|
33
|
+
# the Array when it is the last element of the Array.
|
34
|
+
def extractable_options?
|
35
|
+
instance_of?(Hash)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Array
|
40
|
+
# Extracts options from a set of arguments. Removes and returns the last
|
41
|
+
# element in the array if it's a hash, otherwise returns a blank hash.
|
42
|
+
#
|
43
|
+
# def options(*args)
|
44
|
+
# args.extract_options!
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# options(1, 2) # => {}
|
48
|
+
# options(1, 2, a: :b) # => {:a=>:b}
|
49
|
+
def extract_options!
|
50
|
+
if last.is_a?(Hash) && last.extractable_options?
|
51
|
+
pop
|
52
|
+
else
|
53
|
+
{}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Class
|
59
|
+
# Declare a class-level attribute whose value is inheritable by subclasses.
|
60
|
+
# Subclasses can change their own value and it will not impact parent class.
|
61
|
+
#
|
62
|
+
# class Base
|
63
|
+
# class_attribute :setting
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# class Subclass < Base
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# Base.setting = true
|
70
|
+
# Subclass.setting # => true
|
71
|
+
# Subclass.setting = false
|
72
|
+
# Subclass.setting # => false
|
73
|
+
# Base.setting # => true
|
74
|
+
#
|
75
|
+
# In the above case as long as Subclass does not assign a value to setting
|
76
|
+
# by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
|
77
|
+
# would read value assigned to parent class. Once Subclass assigns a value then
|
78
|
+
# the value assigned by Subclass would be returned.
|
79
|
+
#
|
80
|
+
# This matches normal Ruby method inheritance: think of writing an attribute
|
81
|
+
# on a subclass as overriding the reader method. However, you need to be aware
|
82
|
+
# when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
|
83
|
+
# In such cases, you don't want to do changes in places but use setters:
|
84
|
+
#
|
85
|
+
# Base.setting = []
|
86
|
+
# Base.setting # => []
|
87
|
+
# Subclass.setting # => []
|
88
|
+
#
|
89
|
+
# # Appending in child changes both parent and child because it is the same object:
|
90
|
+
# Subclass.setting << :foo
|
91
|
+
# Base.setting # => [:foo]
|
92
|
+
# Subclass.setting # => [:foo]
|
93
|
+
#
|
94
|
+
# # Use setters to not propagate changes:
|
95
|
+
# Base.setting = []
|
96
|
+
# Subclass.setting += [:foo]
|
97
|
+
# Base.setting # => []
|
98
|
+
# Subclass.setting # => [:foo]
|
99
|
+
#
|
100
|
+
# For convenience, an instance predicate method is defined as well.
|
101
|
+
# To skip it, pass <tt>instance_predicate: false</tt>.
|
102
|
+
#
|
103
|
+
# Subclass.setting? # => false
|
104
|
+
#
|
105
|
+
# Instances may overwrite the class value in the same way:
|
106
|
+
#
|
107
|
+
# Base.setting = true
|
108
|
+
# object = Base.new
|
109
|
+
# object.setting # => true
|
110
|
+
# object.setting = false
|
111
|
+
# object.setting # => false
|
112
|
+
# Base.setting # => true
|
113
|
+
#
|
114
|
+
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
|
115
|
+
#
|
116
|
+
# object.setting # => NoMethodError
|
117
|
+
# object.setting? # => NoMethodError
|
118
|
+
#
|
119
|
+
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
|
120
|
+
#
|
121
|
+
# object.setting = false # => NoMethodError
|
122
|
+
#
|
123
|
+
# To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
|
124
|
+
def class_attribute(*attrs)
|
125
|
+
options = attrs.extract_options!
|
126
|
+
instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
|
127
|
+
instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
|
128
|
+
instance_predicate = options.fetch(:instance_predicate, true)
|
129
|
+
|
130
|
+
attrs.each do |name|
|
131
|
+
define_singleton_method(name) { nil }
|
132
|
+
define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
|
133
|
+
|
134
|
+
ivar = "@#{name}"
|
135
|
+
|
136
|
+
define_singleton_method("#{name}=") do |val|
|
137
|
+
singleton_class.class_eval do
|
138
|
+
remove_possible_method(name)
|
139
|
+
define_method(name) { val }
|
140
|
+
end
|
141
|
+
|
142
|
+
if singleton_class?
|
143
|
+
class_eval do
|
144
|
+
remove_possible_method(name)
|
145
|
+
define_method(name) do
|
146
|
+
if instance_variable_defined? ivar
|
147
|
+
instance_variable_get ivar
|
148
|
+
else
|
149
|
+
singleton_class.send name
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
val
|
155
|
+
end
|
156
|
+
|
157
|
+
if instance_reader
|
158
|
+
remove_possible_method name
|
159
|
+
define_method(name) do
|
160
|
+
if instance_variable_defined?(ivar)
|
161
|
+
instance_variable_get ivar
|
162
|
+
else
|
163
|
+
self.class.public_send name
|
164
|
+
end
|
165
|
+
end
|
166
|
+
define_method("#{name}?") { !!public_send(name) } if instance_predicate
|
167
|
+
end
|
168
|
+
|
169
|
+
attr_writer name if instance_writer
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
unless respond_to?(:singleton_class?)
|
176
|
+
def singleton_class?
|
177
|
+
ancestors.first != self
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'callme'
|
3
|
+
|
4
|
+
describe Callme::Container do
|
5
|
+
|
6
|
+
class Logger
|
7
|
+
attr_accessor :appender
|
8
|
+
end
|
9
|
+
class Appender
|
10
|
+
end
|
11
|
+
class Printer
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "bean definitions" do
|
15
|
+
let(:container) do
|
16
|
+
container = Callme::Container.new
|
17
|
+
container.bean(:appender, class: Appender)
|
18
|
+
container.bean(:logger, class: Logger) do
|
19
|
+
attr :appender, ref: :appender
|
20
|
+
end
|
21
|
+
container.bean(:printer, class: Printer, instance: false)
|
22
|
+
container
|
23
|
+
end
|
24
|
+
it "should instanciate bean and it's dependencies" do
|
25
|
+
container[:logger].should be_a(Logger)
|
26
|
+
container[:logger].appender.should be_a(Appender)
|
27
|
+
container[:printer].should be(Printer)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "container should return the same instance on each call" do
|
31
|
+
logger = container[:logger]
|
32
|
+
container[:logger].should == logger
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "eager_load_bean_classes" do
|
37
|
+
let(:container) do
|
38
|
+
container = Callme::Container.new
|
39
|
+
container.bean(:appender, class: 'Appender')
|
40
|
+
container.bean(:logger, class: 'Logger') do
|
41
|
+
attr :appender, ref: :appender
|
42
|
+
end
|
43
|
+
container.bean(:printer, class: 'Printer', instance: false)
|
44
|
+
container
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should eager load bean classes" do
|
48
|
+
container.eager_load_bean_classes
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
describe "#replace_bean" do
|
54
|
+
it "should replace bean definition" do
|
55
|
+
container = Callme::Container.new
|
56
|
+
container.bean(:appender, class: Appender)
|
57
|
+
container[:appender].should be_a(Appender)
|
58
|
+
|
59
|
+
container.replace_bean(:appender, class: Logger)
|
60
|
+
container[:appender].should be_a(Logger)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "passing bean definitions to container constructor" do
|
65
|
+
let(:resource) do
|
66
|
+
Proc.new do |c|
|
67
|
+
c.bean(:appender, class: 'Appender')
|
68
|
+
c.bean(:logger, class: Logger) do
|
69
|
+
attr :appender, ref: :appender
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should instanciate given bean definitions" do
|
75
|
+
container = Callme::Container.new_with_beans([resource])
|
76
|
+
container[:logger].should be_a(Logger)
|
77
|
+
container[:appender].should be_a(Appender)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "inheritance" do
|
83
|
+
class Form
|
84
|
+
inject :validator
|
85
|
+
end
|
86
|
+
|
87
|
+
class Circle < Form
|
88
|
+
inject :circle_validator
|
89
|
+
end
|
90
|
+
class Rectangle < Form
|
91
|
+
inject :rectangle_validator
|
92
|
+
end
|
93
|
+
|
94
|
+
class Validator
|
95
|
+
end
|
96
|
+
class CircleValidator
|
97
|
+
end
|
98
|
+
class RectangleValidator
|
99
|
+
end
|
100
|
+
|
101
|
+
let(:container) do
|
102
|
+
Callme::Container.new do |c|
|
103
|
+
c.bean(:circle, class: Circle)
|
104
|
+
c.bean(:rectangle, class: Rectangle)
|
105
|
+
c.bean(:validator, class: Validator)
|
106
|
+
c.bean(:circle_validator, class: CircleValidator)
|
107
|
+
c.bean(:rectangle_validator, class: RectangleValidator)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it "dependencies in subclasses shouldn't affect on each other" do
|
112
|
+
container[:circle].circle_validator.should be_a(CircleValidator)
|
113
|
+
container[:rectangle].rectangle_validator.should be_a(RectangleValidator)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "bean scopes" do
|
118
|
+
class ContactsService
|
119
|
+
inject :contacts_repository
|
120
|
+
inject :contacts_validator
|
121
|
+
end
|
122
|
+
class ContactsRepository
|
123
|
+
end
|
124
|
+
class ContactsValidator
|
125
|
+
end
|
126
|
+
|
127
|
+
let(:container) do
|
128
|
+
container = Callme::Container.new
|
129
|
+
container.bean(:contacts_repository, class: ContactsRepository, scope: :request)
|
130
|
+
container.bean(:contacts_service, class: ContactsService, scope: :singleton)
|
131
|
+
container.bean(:contacts_validator, class: ContactsValidator, scope: :prototype)
|
132
|
+
container
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should instanciate bean with :request scope on each request" do
|
136
|
+
first_repo = container[:contacts_service].contacts_repository
|
137
|
+
second_repo = container[:contacts_service].contacts_repository
|
138
|
+
first_repo.should == second_repo
|
139
|
+
RequestStore.clear! # new request
|
140
|
+
third_repo = container[:contacts_service].contacts_repository
|
141
|
+
first_repo.should_not == third_repo
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should instanciate bean with :prototype scope on each call" do
|
145
|
+
first_validator = container[:contacts_service].contacts_validator
|
146
|
+
second_validator = container[:contacts_service].contacts_validator
|
147
|
+
first_validator.should_not == second_validator
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "factory method" do
|
152
|
+
module Test
|
153
|
+
class Config
|
154
|
+
end
|
155
|
+
class ConfigsFactory
|
156
|
+
def load_config
|
157
|
+
Config.new
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
let(:container) do
|
163
|
+
Callme::Container.new do |c|
|
164
|
+
c.bean :config, class: Test::ConfigsFactory, factory_method: :load_config
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should instantiate bean using factory method" do
|
169
|
+
container[:config].should be_instance_of(Test::Config)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe "parent container" do
|
174
|
+
class ContactBook
|
175
|
+
inject :contacts_repository
|
176
|
+
inject :validator, ref: :contact_validator
|
177
|
+
end
|
178
|
+
class ContactBookService
|
179
|
+
inject :contacts_repository
|
180
|
+
inject :validator, ref: :contact_validator
|
181
|
+
end
|
182
|
+
class ContactsRepository
|
183
|
+
end
|
184
|
+
class ContactValidator
|
185
|
+
end
|
186
|
+
class TestContactValidator
|
187
|
+
end
|
188
|
+
|
189
|
+
class AnotherTestContactValidator
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
let(:parent){
|
194
|
+
Callme::Container.new do |c|
|
195
|
+
c.bean(:contacts_repository, class: ContactsRepository)
|
196
|
+
c.bean(:contact_validator, class: ContactValidator)
|
197
|
+
c.bean(:contact_book, class: ContactBook)
|
198
|
+
c.bean(:contact_book_service, class: "ContactBookService")
|
199
|
+
end
|
200
|
+
}
|
201
|
+
|
202
|
+
let(:container){
|
203
|
+
Callme::Container.with_parent(parent) do |c|
|
204
|
+
c.bean(:contact_validator, class: TestContactValidator)
|
205
|
+
end
|
206
|
+
}
|
207
|
+
|
208
|
+
it "works for direct beans" do
|
209
|
+
expect(container[:contact_validator]).to be_a(TestContactValidator)
|
210
|
+
expect(container[:contact_book_service].validator).to be_a(TestContactValidator)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "works for in-direct dependencies" do
|
214
|
+
expect(container[:contact_book_service].validator).to be_a(TestContactValidator)
|
215
|
+
end
|
216
|
+
|
217
|
+
it "does not consider changes to parent" do
|
218
|
+
expect(parent[:contact_book_service].validator).to be_a(ContactValidator)
|
219
|
+
parent.replace_bean(:contact_validator, class: AnotherTestContactValidator)
|
220
|
+
expect(parent[:contact_validator]).to be_a(AnotherTestContactValidator)
|
221
|
+
parent.reset!
|
222
|
+
expect(parent[:contact_book_service].validator).to be_a(AnotherTestContactValidator)
|
223
|
+
expect(container[:contact_book_service].validator).to be_a(TestContactValidator)
|
224
|
+
end
|
225
|
+
|
226
|
+
it "changes in child container do not affect parent container" do
|
227
|
+
expect(parent[:contact_book_service].validator).to be_a(ContactValidator)
|
228
|
+
container.replace_bean(:contact_validator, class: AnotherTestContactValidator)
|
229
|
+
parent.reset!
|
230
|
+
container.reset!
|
231
|
+
expect(parent[:contact_validator]).to be_a(ContactValidator)
|
232
|
+
expect(container[:contact_validator]).to be_a(AnotherTestContactValidator)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Ensures that :inject keyword works as it should
|
4
|
+
describe "Object.inject" do
|
5
|
+
class ContactBook
|
6
|
+
inject :contacts_repository
|
7
|
+
inject :validator, ref: :contact_validator
|
8
|
+
end
|
9
|
+
class ContactBookService
|
10
|
+
inject :contacts_repository
|
11
|
+
inject :validator, ref: :contact_validator
|
12
|
+
end
|
13
|
+
class ContactsRepository
|
14
|
+
end
|
15
|
+
class ContactValidator
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:container) do
|
19
|
+
Callme::Container.new do |c|
|
20
|
+
c.bean(:contacts_repository, class: ContactsRepository)
|
21
|
+
c.bean(:contact_validator, class: ContactValidator)
|
22
|
+
c.bean(:contact_book, class: ContactBook)
|
23
|
+
c.bean(:contact_book_service, class: "ContactBookService")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should autowire dependencies" do
|
28
|
+
container[:contact_book].contacts_repository.should be_a(ContactsRepository)
|
29
|
+
container[:contact_book].validator.should be_a(ContactValidator)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should lazy autowire dependencies for string class names" do
|
33
|
+
container[:contact_book_service].contacts_repository.should be_a(ContactsRepository)
|
34
|
+
container[:contact_book_service].validator.should be_a(ContactValidator)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should raise ArgumentError if non-symbol passed as dependency name" do
|
38
|
+
expect do
|
39
|
+
class SomeClass
|
40
|
+
inject 'bar'
|
41
|
+
end
|
42
|
+
end.to raise_error(ArgumentError, "dependency name should be a symbol")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "inject should define instance variable" do
|
46
|
+
container[:contact_book].instance_variable_get(:@contacts_repository).should be_a(ContactsRepository)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "inject should not define class variable" do
|
50
|
+
expect do
|
51
|
+
container[:contact_book].class.contacts_repository
|
52
|
+
end.to raise_error(NoMethodError)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|