smart_container 0.4.0 → 0.8.1

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]
@@ -114,6 +176,34 @@ module SmartCore
114
176
  thread_safe { registry.keys(all_variants: all_variants) }
115
177
  end
116
178
 
179
+ # @param key [String, Symbol]
180
+ # @return [Boolean]
181
+ #
182
+ # @api public
183
+ # @since 0.5.0
184
+ def key?(key)
185
+ thread_safe { DependencyResolver.key?(self, key) }
186
+ end
187
+
188
+ # @param namespace_path [String, Symbol]
189
+ # @return [Boolean]
190
+ #
191
+ # @api public
192
+ # @since 0.5.0
193
+ def namespace?(namespace_path)
194
+ thread_safe { DependencyResolver.namespace?(self, namespace_path) }
195
+ end
196
+
197
+ # @param dependency_path [String, Symbol]
198
+ # @option memoized [NilClass, Boolean]
199
+ # @return [Boolean]
200
+ #
201
+ # @api public
202
+ # @since 0.5.0
203
+ def dependency?(dependency_path, memoized: nil)
204
+ thread_safe { DependencyResolver.dependency?(self, dependency_path, memoized: memoized) }
205
+ end
206
+
117
207
  # @option yield_all [Boolean]
118
208
  # @param block [Block]
119
209
  # @yield [dependency_name, dependency_value]
@@ -143,12 +233,47 @@ module SmartCore
143
233
  alias_method :to_h, :hash_tree
144
234
  alias_method :to_hash, :hash_tree
145
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
+
146
270
  private
147
271
 
148
272
  # @return [void]
149
273
  #
150
274
  # @api private
151
275
  # @since 0.1.0
276
+ # @version 0.8.1
152
277
  def build_registry!
153
278
  @registry = RegistryBuilder.build(self)
154
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]
@@ -22,8 +22,57 @@ 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)
28
+ end
29
+
30
+ # @param container [SmartCore::Container]
31
+ # @param key [String, Symbol]
32
+ # @return [Boolean]
33
+ #
34
+ # @api private
35
+ # @since 0.5.0
36
+ def key?(container, key)
37
+ extract(container, key)
38
+ true
39
+ rescue SmartCore::Container::ResolvingError
40
+ false
41
+ end
42
+
43
+ # @param container [SmartCore::Container]
44
+ # @param namespace_path [String, Symbol]
45
+ # @return [Boolean]
46
+ #
47
+ # @api private
48
+ # @since 0.5.0
49
+ def namespace?(container, namespace_path)
50
+ extract(container, namespace_path).is_a?(SmartCore::Container::Entities::Namespace)
51
+ rescue SmartCore::Container::ResolvingError
52
+ false
53
+ end
54
+
55
+ # @param container [SmartCore::Container]
56
+ # @param dependency_path [String, Symbol]
57
+ # @option memoized [NilClass, Boolean]
58
+ # @return [Boolean]
59
+ #
60
+ # @api private
61
+ # @since 0.5.0
62
+ def dependency?(container, dependency_path, memoized: nil)
63
+ entity = extract(container, dependency_path)
64
+
65
+ case
66
+ when memoized.nil?
67
+ entity.is_a?(SmartCore::Container::Entities::Dependency)
68
+ when !!memoized == true
69
+ entity.is_a?(SmartCore::Container::Entities::MemoizedDependency)
70
+ when !!memoized == false
71
+ entity.is_a?(SmartCore::Container::Entities::Dependency) &&
72
+ !entity.is_a?(SmartCore::Container::Entities::MemoizedDependency)
73
+ end
74
+ rescue SmartCore::Container::ResolvingError
75
+ false
27
76
  end
28
77
 
29
78
  # @param container [SmartCore::Container]
@@ -38,13 +87,16 @@ module SmartCore::Container::DependencyResolver
38
87
  #
39
88
  # @api private
40
89
  # @since 0.1.0
41
- # @version 0.1.0
90
+ # @version 0.8.1
42
91
  def resolve(container, dependency_path)
43
92
  entity = container
93
+ host_container = container
94
+
44
95
  Route.build(dependency_path).each do |cursor|
45
96
  entity = entity.registry.resolve(cursor.current_path)
46
- prevent_cursor_overflow!(cursor, entity)
47
- entity = entity.reveal
97
+ prevent_ambiguous_resolving!(cursor, entity)
98
+ entity = entity.reveal(host_container)
99
+ host_container = entity.is_a?(SmartCore::Container) ? entity : nil
48
100
  end
49
101
  entity
50
102
  rescue SmartCore::Container::ResolvingError => error
@@ -53,6 +105,28 @@ module SmartCore::Container::DependencyResolver
53
105
 
54
106
  private
55
107
 
108
+ # @param container [SmartCore::Container]
109
+ # @param entity_path [String, Symbol]
110
+ # @return [SmartCore::Container::Entities::Base]
111
+ #
112
+ # @api private
113
+ # @since 0.5.0
114
+ # @version 0.8.1
115
+ def extract(container, entity_path)
116
+ resolved_entity = container
117
+ extracted_entity = container
118
+ host_container = container
119
+
120
+ Route.build(entity_path).each do |cursor|
121
+ resolved_entity = resolved_entity.registry.resolve(cursor.current_path)
122
+ extracted_entity = resolved_entity
123
+ resolved_entity = resolved_entity.reveal(host_container)
124
+ host_container = resolved_entity.is_a?(SmartCore::Container) ? resolved_entity : nil
125
+ end
126
+
127
+ extracted_entity
128
+ end
129
+
56
130
  # @param cursor [SmartCore::Container::DependencyResolver::Route::Cursor]
57
131
  # @param entity [SmartCore::Container::Entities::Base]
58
132
  # @return [void]
@@ -60,8 +134,8 @@ module SmartCore::Container::DependencyResolver
60
134
  # @raise [SmartCore::Container::ResolvingError]
61
135
  #
62
136
  # @api private
63
- # @since 0.1.0
64
- def prevent_cursor_overflow!(cursor, entity)
137
+ # @since 0.5.0
138
+ def prevent_ambiguous_resolving!(cursor, entity)
65
139
  if cursor.last? && entity.is_a?(SmartCore::Container::Entities::Namespace)
66
140
  raise(
67
141
  SmartCore::Container::ResolvingError.new(
@@ -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