smart_container 0.5.0 → 0.9.0

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