smart_container 0.5.0 → 0.9.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.
data/Rakefile CHANGED
@@ -4,6 +4,7 @@ require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
5
  require 'rubocop'
6
6
  require 'rubocop/rake_task'
7
+ require 'rubocop-rails'
7
8
  require 'rubocop-performance'
8
9
  require 'rubocop-rspec'
9
10
  require 'rubocop-rake'
@@ -11,8 +12,8 @@ require 'rubocop-rake'
11
12
  RuboCop::RakeTask.new(:rubocop) do |t|
12
13
  config_path = File.expand_path(File.join('.rubocop.yml'), __dir__)
13
14
  t.options = ['--config', config_path]
14
- t.requires << 'rubocop-performance'
15
15
  t.requires << 'rubocop-rspec'
16
+ t.requires << 'rubocop-performance'
16
17
  t.requires << 'rubocop-rake'
17
18
  end
18
19
 
@@ -7,10 +7,10 @@ require 'smart_core'
7
7
  module SmartCore
8
8
  # @api public
9
9
  # @since 0.1.0
10
- class Container
10
+ class Container # rubocop:disable Metrics/ClassLength
11
11
  require_relative 'container/version'
12
12
  require_relative 'container/errors'
13
- require_relative 'container/arbitary_lock'
13
+ require_relative 'container/arbitrary_lock'
14
14
  require_relative 'container/key_guard'
15
15
  require_relative 'container/entities'
16
16
  require_relative 'container/definition_dsl'
@@ -18,27 +18,77 @@ module SmartCore
18
18
  require_relative 'container/registry'
19
19
  require_relative 'container/registry_builder'
20
20
  require_relative 'container/dependency_resolver'
21
+ require_relative 'container/dependency_watcher'
22
+ require_relative 'container/host'
21
23
  require_relative 'container/mixin'
22
24
 
25
+ class << self
26
+ # @param initial_container_klass [Class<SmartCore::Container>]
27
+ # @param container_definitions [Block]
28
+ # @return [SmartCore::Container]
29
+ #
30
+ # @api public
31
+ # @since 0.7.0
32
+ def define(initial_container_klass = self, &container_definitions)
33
+ unless initial_container_klass <= SmartCore::Container
34
+ raise(SmartCore::Container::ArgumentError, <<~ERROR_MESSAGE)
35
+ Base class should be a type of SmartCore::Container
36
+ ERROR_MESSAGE
37
+ end
38
+
39
+ Class.new(initial_container_klass, &container_definitions).new
40
+ end
41
+ end
42
+
23
43
  # @since 0.4.0
24
44
  include ::Enumerable
25
45
 
26
46
  # @since 0.1.0
27
47
  include DefinitionDSL
28
48
 
49
+ # @return [NilClass]
50
+ #
51
+ # @api private
52
+ # @since 0.8.1
53
+ NO_HOST_CONTAINER = nil
54
+
55
+ # @return [NilClass]
56
+ #
57
+ # @api private
58
+ # @since 0.8.1
59
+ NO_HOST_PATH = nil
60
+
29
61
  # @return [SmartCore::Container::Registry]
30
62
  #
31
63
  # @api private
32
64
  # @since 0.1.0
33
65
  attr_reader :registry
34
66
 
67
+ # @return [SmartCore::Container::Host]
68
+ #
69
+ # @api private
70
+ # @since 0.8.1
71
+ attr_reader :host
72
+
73
+ # @return [SmartCore::Container::DependencyWatcher]
74
+ #
75
+ # @api private
76
+ # @since 0.8.0
77
+ attr_reader :watcher
78
+
79
+ # @option host_container [SmartCore::Container, NilClass]
80
+ # @option host_path [String, NilClass]
35
81
  # @return [void]
36
82
  #
37
83
  # @api public
38
84
  # @since 0.1.0
39
- def initialize
85
+ # @version 0.8.1
86
+ def initialize(host_container: NO_HOST_CONTAINER, host_path: NO_HOST_PATH)
87
+ @host = SmartCore::Container::Host.build(host_container, host_path)
40
88
  build_registry!
41
- @access_lock = ArbitaryLock.new
89
+ @watcher = SmartCore::Container::DependencyWatcher.new(self)
90
+ @host_path = host_path
91
+ @access_lock = ArbitraryLock.new
42
92
  end
43
93
 
44
94
  # @param dependency_name [String, Symbol]
@@ -47,8 +97,16 @@ module SmartCore
47
97
  #
48
98
  # @api public
49
99
  # @sicne 0.1.0
50
- def register(dependency_name, &dependency_definition)
51
- thread_safe { registry.register_dependency(dependency_name, &dependency_definition) }
100
+ # @version 0.8.0
101
+ def register(
102
+ dependency_name,
103
+ memoize: SmartCore::Container::Registry::DEFAULT_MEMOIZATION_BEHAVIOR,
104
+ &dependency_definition
105
+ )
106
+ thread_safe do
107
+ registry.register_dependency(dependency_name, memoize: memoize, &dependency_definition)
108
+ watcher.notify(dependency_name)
109
+ end
52
110
  end
53
111
 
54
112
  # @param namespace_name [String, Symbol]
@@ -57,8 +115,12 @@ module SmartCore
57
115
  #
58
116
  # @api public
59
117
  # @since 0.1.0
118
+ # @version 0.8.0
60
119
  def namespace(namespace_name, &dependencies_definition)
61
- thread_safe { registry.register_namespace(namespace_name, &dependencies_definition) }
120
+ thread_safe do
121
+ registry.register_namespace(namespace_name, self, &dependencies_definition)
122
+ watcher.notify(namespace_name)
123
+ end
62
124
  end
63
125
 
64
126
  # @param dependency_path [String, Symbol]
@@ -171,12 +233,47 @@ module SmartCore
171
233
  alias_method :to_h, :hash_tree
172
234
  alias_method :to_hash, :hash_tree
173
235
 
236
+ # @param entity_path [String]
237
+ # @param observer [Block]
238
+ # @yield [entity_path, container]
239
+ # @yieldparam entity_path [String]
240
+ # @yieldparam container [SmartCore::Container]
241
+ # @return [SmartCore::Container::DependencyWatcher::Observer]
242
+ #
243
+ # @api public
244
+ # @since 0.8.0
245
+ def observe(entity_path, &observer) # TODO: support for pattern-based pathes
246
+ thread_safe { watcher.watch(entity_path, &observer) }
247
+ end
248
+ alias_method :subscribe, :observe
249
+
250
+ # @param observer [SmartCore::Container::DependencyWatcher::Observer]
251
+ # @return [Boolean]
252
+ #
253
+ # @api public
254
+ # @since 0.8.0
255
+ def unobserve(observer)
256
+ thread_safe { watcher.unwatch(observer) }
257
+ end
258
+ alias_method :unsubscribe, :unobserve
259
+
260
+ # @param entity_path [String, Symbol, NilClass]
261
+ # @return [void]
262
+ #
263
+ # @api public
264
+ # @since 0.8.0
265
+ def clear_observers(entity_path = nil) # TODO: support for pattern-based pathes
266
+ thread_safe { watcher.clear_listeners(entity_path) }
267
+ end
268
+ alias_method :clear_listeners, :clear_observers
269
+
174
270
  private
175
271
 
176
272
  # @return [void]
177
273
  #
178
274
  # @api private
179
275
  # @since 0.1.0
276
+ # @version 0.8.1
180
277
  def build_registry!
181
278
  @registry = RegistryBuilder.build(self)
182
279
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
- class SmartCore::Container::ArbitaryLock
5
+ class SmartCore::Container::ArbitraryLock
6
6
  # @return [void]
7
7
  #
8
8
  # @api private
@@ -17,7 +17,7 @@ class SmartCore::Container
17
17
  def included(base_klass)
18
18
  base_klass.instance_variable_set(:@__container_definition_commands__, CommandSet.new)
19
19
  base_klass.instance_variable_set(:@__container_instantiation_commands__, CommandSet.new)
20
- base_klass.instance_variable_set(:@__container_definition_lock__, ArbitaryLock.new)
20
+ base_klass.instance_variable_set(:@__container_definition_lock__, ArbitraryLock.new)
21
21
  base_klass.singleton_class.send(:attr_reader, :__container_definition_commands__)
22
22
  base_klass.singleton_class.send(:attr_reader, :__container_instantiation_commands__)
23
23
  base_klass.extend(ClassMethods)
@@ -36,7 +36,7 @@ class SmartCore::Container
36
36
  def inherited(child_klass)
37
37
  child_klass.instance_variable_set(:@__container_definition_commands__, CommandSet.new)
38
38
  child_klass.instance_variable_set(:@__container_instantiation_commands__, CommandSet.new)
39
- child_klass.instance_variable_set(:@__container_definition_lock__, ArbitaryLock.new)
39
+ child_klass.instance_variable_set(:@__container_definition_lock__, ArbitraryLock.new)
40
40
  SmartCore::Container::DefinitionDSL::Inheritance.inherit(base: self, child: child_klass)
41
41
  child_klass.singleton_class.prepend(ClassInheritance)
42
42
  super
@@ -18,7 +18,7 @@ class SmartCore::Container::DefinitionDSL::CommandSet
18
18
  # @since 0.1.0
19
19
  def initialize
20
20
  @commands = []
21
- @access_lock = SmartCore::Container::ArbitaryLock.new
21
+ @access_lock = SmartCore::Container::ArbitraryLock.new
22
22
  end
23
23
 
24
24
  # @param [SmartCore::Container::DefinitionDSL::Commands::Base]
@@ -36,7 +36,7 @@ module SmartCore::Container::DefinitionDSL::Commands::Definition
36
36
  # @since 0.1.0
37
37
  # @version 0.2.0
38
38
  def call(registry)
39
- registry.register_dependency(dependency_name, memoize, &dependency_definition)
39
+ registry.register_dependency(dependency_name, memoize: memoize, &dependency_definition)
40
40
  end
41
41
 
42
42
  # @return [SmartCore::Container::DefinitionDSL::Commands::Definition::Register]
@@ -13,6 +13,7 @@ module SmartCore::Container::DependencyCompatability::Definition
13
13
  #
14
14
  # @api private
15
15
  # @since 0.1.0
16
+ # rubocop:disable Lint/EmptyBlock
16
17
  def potential_namespace_overlap?(container_klass, dependency_name)
17
18
  anonymous_container = Class.new(container_klass).new
18
19
  anonymous_container.register(dependency_name, &(proc {}))
@@ -20,6 +21,7 @@ module SmartCore::Container::DependencyCompatability::Definition
20
21
  rescue SmartCore::Container::DependencyOverNamespaceOverlapError
21
22
  true
22
23
  end
24
+ # rubocop:enable Lint/EmptyBlock
23
25
 
24
26
  # @param container_klass [Class<SmartCore::Container>]
25
27
  # @param namespace_name [String, Symbol]
@@ -27,6 +29,7 @@ module SmartCore::Container::DependencyCompatability::Definition
27
29
  #
28
30
  # @api private
29
31
  # @since 0.1.0
32
+ # rubocop:disable Lint/EmptyBlock
30
33
  def potential_dependency_overlap?(container_klass, namespace_name)
31
34
  anonymous_container = Class.new(container_klass).new
32
35
  anonymous_container.namespace(namespace_name, &(proc {}))
@@ -34,5 +37,6 @@ module SmartCore::Container::DependencyCompatability::Definition
34
37
  rescue SmartCore::Container::NamespaceOverDependencyOverlapError
35
38
  true
36
39
  end
40
+ # rubocop:enable Lint/EmptyBlock
37
41
  end
38
42
  end
@@ -22,8 +22,9 @@ module SmartCore::Container::DependencyResolver
22
22
  #
23
23
  # @api private
24
24
  # @since 0.1.0
25
+ # @version 0.8.1
25
26
  def fetch(container, dependency_path)
26
- container.registry.resolve(dependency_path).reveal
27
+ container.registry.resolve(dependency_path).reveal(container)
27
28
  end
28
29
 
29
30
  # @param container [SmartCore::Container]
@@ -62,7 +63,7 @@ module SmartCore::Container::DependencyResolver
62
63
  entity = extract(container, dependency_path)
63
64
 
64
65
  case
65
- when memoized.nil?
66
+ when memoized == nil
66
67
  entity.is_a?(SmartCore::Container::Entities::Dependency)
67
68
  when !!memoized == true
68
69
  entity.is_a?(SmartCore::Container::Entities::MemoizedDependency)
@@ -86,12 +87,16 @@ module SmartCore::Container::DependencyResolver
86
87
  #
87
88
  # @api private
88
89
  # @since 0.1.0
90
+ # @version 0.8.1
89
91
  def resolve(container, dependency_path)
90
92
  entity = container
93
+ host_container = container
94
+
91
95
  Route.build(dependency_path).each do |cursor|
92
96
  entity = entity.registry.resolve(cursor.current_path)
93
97
  prevent_ambiguous_resolving!(cursor, entity)
94
- entity = entity.reveal
98
+ entity = entity.reveal(host_container)
99
+ host_container = entity.is_a?(SmartCore::Container) ? entity : nil
95
100
  end
96
101
  entity
97
102
  rescue SmartCore::Container::ResolvingError => error
@@ -106,14 +111,17 @@ module SmartCore::Container::DependencyResolver
106
111
  #
107
112
  # @api private
108
113
  # @since 0.5.0
114
+ # @version 0.8.1
109
115
  def extract(container, entity_path)
110
116
  resolved_entity = container
111
117
  extracted_entity = container
118
+ host_container = container
112
119
 
113
120
  Route.build(entity_path).each do |cursor|
114
121
  resolved_entity = resolved_entity.registry.resolve(cursor.current_path)
115
122
  extracted_entity = resolved_entity
116
- resolved_entity = resolved_entity.reveal
123
+ resolved_entity = resolved_entity.reveal(host_container)
124
+ host_container = resolved_entity.is_a?(SmartCore::Container) ? resolved_entity : nil
117
125
  end
118
126
 
119
127
  extracted_entity
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.8.0
5
+ class SmartCore::Container::DependencyWatcher
6
+ require_relative 'dependency_watcher/observer'
7
+
8
+ # @param container [SmartCore::Container]
9
+ # @return [void]
10
+ #
11
+ # @api private
12
+ # @since 0.8.0
13
+ def initialize(container)
14
+ @container = container
15
+ @observers = Hash.new { |h, k| h[k] = [] }
16
+ @access_lock = SmartCore::Container::ArbitraryLock.new
17
+ end
18
+
19
+ # @param entity_path [String, Symbol]
20
+ # @return [void]
21
+ #
22
+ # @api private
23
+ # @since 0.8.0
24
+ def notify(entity_path)
25
+ thread_safe { notify_listeners(entity_path) }
26
+ end
27
+
28
+ # @param entity_path [String, Symbol]
29
+ # @param observer [Block]
30
+ # @return [SmartCore::Container::DependencyWatcher::Observer]
31
+ #
32
+ # @api private
33
+ # @since 0.8.0
34
+ def watch(entity_path, &observer) # TODO: support for pattern-based pathes
35
+ thread_safe { listen(entity_path, observer) }
36
+ end
37
+
38
+ # @param observer [SmartCore::Container::DependencyWatcher::Observer]
39
+ # @return [Boolean]
40
+ #
41
+ # @api private
42
+ # @since 0.8.0
43
+ def unwatch(observer)
44
+ thread_safe { remove_listener(observer) }
45
+ end
46
+
47
+ # @param entity_path [String, Symbol, NilClass]
48
+ # @return [void]
49
+ #
50
+ # @api private
51
+ # @since 0.8.0
52
+ def clear_listeners(entity_path = nil) # TODO: support for pattern-based pathes
53
+ thread_safe { remove_listeners(entity_path) }
54
+ end
55
+
56
+ private
57
+
58
+ # @return [SmartCore::Container]
59
+ #
60
+ # @api private
61
+ # @since 0.8.0
62
+ attr_reader :container
63
+
64
+ # @return [Hash<String,SmartCore::Container::DependencyWatcher::Observer>]
65
+ #
66
+ # @api private
67
+ # @since 0.8.0
68
+ attr_reader :observers
69
+
70
+ # @param entity_path [String, Symbol]
71
+ # @return [void]
72
+ #
73
+ # @api private
74
+ # @since 0.8.0
75
+ # @version 0.8.1
76
+ def notify_listeners(entity_path)
77
+ entity_path = indifferently_accessable_path(entity_path)
78
+ observers.fetch(entity_path).each(&:notify!) if observers.key?(entity_path)
79
+ container.host.notify_about_nested_changement(entity_path)
80
+ end
81
+
82
+ # @param entity_path [String, Symbol]
83
+ # @param observer [Proc]
84
+ # @return [SmartCore::Container::DependencyWatcher::Observer]
85
+ #
86
+ # @api private
87
+ # @since 0.8.0
88
+ def listen(entity_path, observer) # TODO: support for pattern-based pathes
89
+ raise(SmartCore::Container::ArgumentError, <<~ERROR_MESSAGE) unless observer.is_a?(Proc)
90
+ Observer is missing: you should provide an observer proc object (block).
91
+ ERROR_MESSAGE
92
+
93
+ entity_path = indifferently_accessable_path(entity_path)
94
+ Observer.new(container, entity_path, observer).tap { |obs| observers[entity_path] << obs }
95
+ end
96
+
97
+ # @param observer [SmartCore::Container::DependencyWatcher::Observer]
98
+ # @return [Boolean]
99
+ #
100
+ # @api private
101
+ # @since 0.8.0
102
+ def remove_listener(observer)
103
+ unless observer.is_a?(SmartCore::Container::DependencyWatcher::Observer)
104
+ raise(SmartCore::Container::ArgumentError, <<~ERROR_MESSAGE)
105
+ You should provide an observer object for unsubscribion
106
+ (an instance of SmartCore::Container::DependencyWatcher::Observer).
107
+ ERROR_MESSAGE
108
+ end
109
+
110
+ unsubscribed = false
111
+ observers.each_value do |observer_list|
112
+ if observer_list.delete(observer)
113
+ unsubscribed = true
114
+ break
115
+ end
116
+ end
117
+ unsubscribed
118
+ end
119
+
120
+ # @param entity_path [String, Symbol]
121
+ # @return [void]
122
+ #
123
+ # @api private
124
+ # @since 0.8.0
125
+ def remove_listeners(entity_path) # TODO: support for pattern-based pathes
126
+ if entity_path == nil
127
+ observers.each_value(&:clear)
128
+ else
129
+ entity_path = indifferently_accessable_path(entity_path)
130
+ observers[entity_path].clear if observers.key?(entity_path)
131
+ end
132
+ end
133
+
134
+ # @param entity_path [String, Symbol]
135
+ # @return [String]
136
+ #
137
+ # @api private
138
+ # @since 0.8.0
139
+ def indifferently_accessable_path(entity_path)
140
+ SmartCore::Container::KeyGuard.indifferently_accessable_key(entity_path)
141
+ end
142
+
143
+ # @param block [Block]
144
+ # @return [Any]
145
+ #
146
+ # @api private
147
+ # @since 0.8.0
148
+ def thread_safe(&block)
149
+ @access_lock.thread_safe(&block)
150
+ end
151
+ end