Skip to content

Commit 02c07ab

Browse files
committed
[imports] dependency rebinding
1 parent c9db242 commit 02c07ab

File tree

10 files changed

+204
-17
lines changed

10 files changed

+204
-17
lines changed

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ PATH
22
remote: .
33
specs:
44
smart_injection (0.0.0.alpha2)
5-
smart_container (~> 0.7)
5+
smart_container (~> 0.8, >= 0.8.1)
66
smart_engine (~> 0.7)
77

88
GEM
@@ -79,7 +79,7 @@ GEM
7979
docile (~> 1.1)
8080
simplecov-html (~> 0.11)
8181
simplecov-html (0.12.2)
82-
smart_container (0.7.0)
82+
smart_container (0.8.1)
8383
smart_engine (~> 0.5)
8484
smart_engine (0.7.0)
8585
thread_safe (0.3.6)

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ class MiniService
8484
register_container(AppContainer, ServiceContainer)
8585

8686
# import dependencies to an instance
87-
import { db: 'data_storage.main' }, bind: :dynamic, access: :private
88-
import { rnd: 'rands.alphanum' }, bind: :static, memoize: true
87+
import({ db: 'data_storage.main' }, bind: :dynamic, access: :private)
88+
import({ rnd: 'rands.alphanum' }, bind: :static, memoize: true)
8989

9090
# import dependencies to a class
91-
import_static { cache: 'data_storage.cache', hexer: 'rands.hex' }, bind: :static
91+
import_static({ cache: 'data_storage.cache', hexer: 'rands.hex' }, bind: :static)
9292

9393
# import from a non-registered container
94-
import { phone_client: 'phone_clients.nexmo' }, from: GlobalContainer
94+
import({ phone_client: 'phone_clients.nexmo' }, from: GlobalContainer)
9595

9696
def call
9797
db # => returns data_storage.main

lib/smart_core/injection/injector/container_set.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# @api private
44
# @since 0.1.0
55
class SmartCore::Injection::Injector::ContainerSet
6+
require_relative 'container_set/adding_listener'
7+
68
# @since 0.1.0
79
include Enumerable
810

@@ -12,6 +14,7 @@ class SmartCore::Injection::Injector::ContainerSet
1214
# @since 0.1.0
1315
def initialize
1416
@containers = [] # NOTE: we use Array cuz we need an ordered set
17+
@adding_listeners = []
1518
@access_lock = SmartCore::Engine::Lock.new
1619
end
1720

@@ -25,6 +28,17 @@ def add(container)
2528
end
2629
alias_method :<<, :add
2730

31+
# @param listener [Block]
32+
# @yield [container]
33+
# @yieldparam container [SmartCore::Container]
34+
# @return [void]
35+
#
36+
# @api private
37+
# @since 0.1.0
38+
def listen_addings(&listener)
39+
thread_safe { add_adding_listener(listener) }
40+
end
41+
2842
# @param block [Block]
2943
# @yield [container]
3044
# @yieldparam container [SmartCore::Container]
@@ -63,6 +77,18 @@ def list
6377
# @since 0.1.0
6478
attr_reader :containers
6579

80+
# @return [Array<SmartCore::Injection::Injector::ContainerSet::AddingListener>]
81+
attr_reader :adding_listeners
82+
83+
# @param listener [Proc]
84+
# @return [void]
85+
#
86+
# @api private
87+
# @since 0.1.0
88+
def add_adding_listener(listener)
89+
adding_listeners << SmartCore::Injection::Injector::ContainerSet::AddingListener.new(listener)
90+
end
91+
6692
# @param container [SmartCore::Container]
6793
# @return [void]
6894
#
@@ -73,6 +99,7 @@ def append_container(container)
7399
# - #concant is used to prevent container duplications in ordered set;
74100
# - @containers should have an ordered unified container list;
75101
containers.concat([container])
102+
adding_listeners.each { |listener| listener.notify(container) }
76103
end
77104

78105
# @param block [Block]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
# @api private
4+
# @since 0.1.0
5+
class SmartCore::Injection::Injector::ContainerSet::AddingListener
6+
# @param listener [Proc]
7+
# @return [void]
8+
#
9+
# @api private
10+
# @since 0.1.0
11+
def initialize(listener)
12+
@listener = listener
13+
end
14+
15+
# @param cotnainer [SmartCore::Container]
16+
#
17+
# @api private
18+
# @since 0.1.0
19+
def notify(container)
20+
listener.call(container)
21+
end
22+
23+
private
24+
25+
# @return [Proc]
26+
#
27+
# @api private
28+
# @since 0.1.0
29+
attr_reader :listener
30+
end

lib/smart_core/injection/locator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def resolve_dependency
3939
# @api private
4040
# @since 0.1.0
4141
def rebind_dependency
42-
dependency.rebind { container_proxy.resolve(import_path) }
42+
dependency.rebind { container_proxy.resolve_dependency(import_path) }
4343
end
4444
alias_method :rebind!, :rebind_dependency
4545

lib/smart_core/injection/locator/container_proxy.rb

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class SmartCore::Injection::Locator::ContainerProxy
1212
def initialize(registered_containers, explicitly_passed_container)
1313
@registered_containers = registered_containers
1414
@explicitly_passed_container = explicitly_passed_container
15+
@observers = Hash.new { |h, k| h[k] = [] }
16+
registered_containers.listen_addings { |container| listen_changes(container) }
1517
end
1618

1719
# @param dependency_path [String]
@@ -49,11 +51,21 @@ def resolve_dependency(dependency_path)
4951
# @api private
5052
# @since 0.1.0
5153
def observe(import_path, &observer)
52-
# TODO: implement
54+
register_observer(import_path, observer)
55+
56+
each_container do |container|
57+
container.observe(import_path) { |path, cntr| process_changement(cntr, path) }
58+
end
5359
end
5460

5561
private
5662

63+
# @return [Hash<String,Proc>]
64+
#
65+
# @api private
66+
# @since 0.1.0
67+
attr_reader :observers
68+
5769
# @return [SmartCore::Injection::Injector::ContainerSet]
5870
#
5971
# @api private
@@ -66,15 +78,65 @@ def observe(import_path, &observer)
6678
# @since 0.1.0
6779
attr_reader :explicitly_passed_container
6880

81+
# @param import_path [NilClass, String]
82+
# @param container [SmartCore::Container]
83+
# @return [void]
84+
#
85+
# @api private
86+
# @since 0.1.0
87+
def process_changement(container, import_path = nil)
88+
observed_pathes = import_path ? [import_path] : observed_import_pathes
89+
90+
observed_pathes.each do |observed_path|
91+
suitable_container = each_container.find { |cntr| cntr.key?(observed_path) }
92+
break unless suitable_container
93+
notify_observers(observed_path) if suitable_container == container
94+
end
95+
end
96+
97+
# @param import_path [String]
98+
# @param observer [SmartCore::Container]
99+
# @return [void]
100+
#
101+
# @api private
102+
# @since 0.1.0
103+
def register_observer(import_path, observer)
104+
observers[import_path] << observer
105+
end
106+
107+
# @return [Array<String>]
108+
#
109+
# @api private
110+
# @since 0.1.0
111+
def observed_import_pathes
112+
observers.keys
113+
end
114+
115+
# @param import_pathes [String]
116+
# @return [void]
117+
#
118+
# @api private
119+
# @since 0.1.0
120+
def notify_observers(*import_pathes)
121+
import_pathes.each { |import_path| observers.fetch(import_path).each(&:call) }
122+
end
123+
69124
# @param block [Block]
70125
# @yield [container]
71126
# @yieldparam container [SmartCore::Container]
72-
# @return [void]
127+
# @return [Enumerable]
73128
#
74129
# @api private
75130
# @since 0.1.0
76131
def each_container(&block)
77-
yield(explicitly_passed_container) if explicitly_passed_container
78-
registered_containers.reverse_each(&block)
132+
enumerator = Enumerator.new do |yielder|
133+
if explicitly_passed_container
134+
yielder.yield(explicitly_passed_container)
135+
else
136+
registered_containers.reverse_each(&yielder)
137+
end
138+
end
139+
140+
block_given? ? enumerator.each(&block) : enumerator.each
79141
end
80142
end

lib/smart_core/injection/locator/dependency.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def initialize
2020
# @since 0.1.0
2121
def rebind(&block)
2222
with_barrier do
23-
@bind = false
23+
@binded = false
2424
bind(&block)
2525
end
2626
end
@@ -36,7 +36,7 @@ def bind(&block)
3636
@value
3737
else
3838
@binded = true
39-
@value = yield(@value)
39+
@value = yield
4040
end
4141
end
4242
end

lib/smart_core/injection/locator/factory.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class << self
1111
# @since 0.1.0
1212
def create(injection_settings, import_key, import_path)
1313
container_proxy = create_container_proxy(injection_settings)
14-
locator = create_locator(import_path, container_proxy).tap do
14+
create_locator(import_path, container_proxy).tap do |locator|
1515
control_injection_memoization(injection_settings, container_proxy, locator, import_path)
1616
end
1717
end
@@ -47,7 +47,9 @@ def create_locator(import_path, container_proxy)
4747
# @api private
4848
# @since 0.1.0
4949
def control_injection_memoization(injection_settings, container_proxy, locator, import_path)
50-
container_proxy.observe(import_path) { locator.rebind! } unless injection_settings.memoize
50+
container_proxy.observe(import_path) do
51+
locator.rebind!
52+
end unless injection_settings.memoize
5153
end
5254
end
5355
end

smart_injection.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
3131
spec.require_paths = ['lib']
3232

3333
spec.add_runtime_dependency 'smart_engine', '~> 0.7'
34-
spec.add_runtime_dependency 'smart_container', '~> 0.7'
34+
spec.add_runtime_dependency 'smart_container', '~> 0.8', '>= 0.8.1'
3535

3636
spec.add_development_dependency 'bundler', '~> 2.1'
3737
spec.add_development_dependency 'rake', '~> 13.0'

spec/smart_injection_spec.rb

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,71 @@
55
expect(SmartCore::Injection::VERSION).not_to be nil
66
end
77

8+
specify 'dependency changement and rebinding (considering the memoization and not)' do
9+
SimpleContainer = SmartCore::Container.define do
10+
namespace(:database) do
11+
register(:logger) { 'simple_logger' }
12+
end
13+
14+
register(:game) { 'overwatch' }
15+
end
16+
17+
AnotherSimpleContainer = SmartCore::Container.define do
18+
namespace(:database) do
19+
register(:logger) { 'another_simple_logger' }
20+
end
21+
end
22+
23+
class SimpleService
24+
include SmartCore::Injection
25+
26+
register_container(SimpleContainer)
27+
28+
import({ db_logger: 'database.logger' })
29+
import({ another_db_logger: 'database.logger' }, from: AnotherSimpleContainer)
30+
import({ game: 'game' }, memoize: true)
31+
32+
def call
33+
{ db_logger: db_logger, another_db_logger: another_db_logger, game: game }
34+
end
35+
end
36+
37+
service = SimpleService.new
38+
39+
expect(service.call).to eq({
40+
db_logger: 'simple_logger',
41+
another_db_logger: 'another_simple_logger',
42+
game: 'overwatch'
43+
})
44+
45+
# re-registration of non-memoized import
46+
SimpleContainer.fetch(:database).register(:logger) { 'changed_simple_logger' }
47+
48+
expect(service.call).to eq({
49+
db_logger: 'changed_simple_logger', # changed
50+
another_db_logger: 'another_simple_logger',
51+
game: 'overwatch'
52+
})
53+
54+
# re-registration of non-memoized import
55+
AnotherSimpleContainer.fetch(:database).register(:logger) { 'changed_another_logger' }
56+
57+
expect(service.call).to eq({
58+
db_logger: 'changed_simple_logger',
59+
another_db_logger: 'changed_another_logger', # changed
60+
game: 'overwatch'
61+
})
62+
63+
# re-registration of memoized import
64+
SimpleContainer.register(:game) { 'warcraft' }
65+
66+
expect(service.call).to eq({
67+
db_logger: 'changed_simple_logger',
68+
another_db_logger: 'changed_another_logger',
69+
game: 'overwatch' # not changed (memoized value)
70+
})
71+
end
72+
873
specify do
974
AppContainer = SmartCore::Container.define do
1075
namespace(:database) do
@@ -73,7 +138,8 @@ def call
73138

74139
# inheritance from inherited entity
75140
class Hydra < Chimera
76-
import({ db_logger: 'database.logger', kickbox: 'clients.kickbox', head: 'heads.snake' }, bind: :static, access: :private)
141+
import({ db_logger: 'database.logger', kickbox: 'clients.kickbox', head: 'heads.snake' },
142+
bind: :static, access: :private)
77143
end
78144

79145
hydra = Hydra.new

0 commit comments

Comments
 (0)