LittleWeasel 3.0.4 → 4.0.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.
Files changed (152) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.reek.yml +17 -0
  4. data/.rspec +4 -2
  5. data/.rubocop.yml +187 -0
  6. data/.ruby-version +1 -1
  7. data/.yardopts +2 -0
  8. data/Gemfile +3 -1
  9. data/LittleWeasel.gemspec +31 -18
  10. data/README.md +408 -42
  11. data/Rakefile +296 -3
  12. data/lib/LittleWeasel.rb +5 -184
  13. data/lib/LittleWeasel/block_results.rb +81 -0
  14. data/lib/LittleWeasel/configure.rb +98 -0
  15. data/lib/LittleWeasel/dictionary.rb +125 -0
  16. data/lib/LittleWeasel/dictionary_key.rb +48 -0
  17. data/lib/LittleWeasel/dictionary_manager.rb +85 -0
  18. data/lib/LittleWeasel/errors/dictionary_file_already_loaded_error.rb +9 -0
  19. data/lib/LittleWeasel/errors/dictionary_file_empty_error.rb +8 -0
  20. data/lib/LittleWeasel/errors/dictionary_file_not_found_error.rb +8 -0
  21. data/lib/LittleWeasel/errors/dictionary_file_too_large_error.rb +16 -0
  22. data/lib/LittleWeasel/errors/language_required_error.rb +8 -0
  23. data/lib/LittleWeasel/errors/must_override_error.rb +8 -0
  24. data/lib/LittleWeasel/filters/en_us/currency_filter.rb +19 -0
  25. data/lib/LittleWeasel/filters/en_us/numeric_filter.rb +19 -0
  26. data/lib/LittleWeasel/filters/en_us/single_character_word_filter.rb +21 -0
  27. data/lib/LittleWeasel/filters/word_filter.rb +59 -0
  28. data/lib/LittleWeasel/filters/word_filter_managable.rb +80 -0
  29. data/lib/LittleWeasel/filters/word_filter_validatable.rb +31 -0
  30. data/lib/LittleWeasel/filters/word_filterable.rb +19 -0
  31. data/lib/LittleWeasel/filters/word_filters_validatable.rb +29 -0
  32. data/lib/LittleWeasel/metadata/dictionary_metadata.rb +145 -0
  33. data/lib/LittleWeasel/metadata/invalid_words_metadata.rb +134 -0
  34. data/lib/LittleWeasel/metadata/invalid_words_service_results.rb +45 -0
  35. data/lib/LittleWeasel/metadata/metadata_observable_validatable.rb +22 -0
  36. data/lib/LittleWeasel/metadata/metadata_observerable.rb +90 -0
  37. data/lib/LittleWeasel/metadata/metadatable.rb +136 -0
  38. data/lib/LittleWeasel/modules/class_name_to_symbol.rb +26 -0
  39. data/lib/LittleWeasel/modules/configurable.rb +26 -0
  40. data/lib/LittleWeasel/modules/deep_dup.rb +11 -0
  41. data/lib/LittleWeasel/modules/dictionary_cache_keys.rb +34 -0
  42. data/lib/LittleWeasel/modules/dictionary_cache_servicable.rb +26 -0
  43. data/lib/LittleWeasel/modules/dictionary_cache_validatable.rb +20 -0
  44. data/lib/LittleWeasel/modules/dictionary_creator_servicable.rb +27 -0
  45. data/lib/LittleWeasel/modules/dictionary_file_loader.rb +67 -0
  46. data/lib/LittleWeasel/modules/dictionary_key_validatable.rb +19 -0
  47. data/lib/LittleWeasel/modules/dictionary_keyable.rb +24 -0
  48. data/lib/LittleWeasel/modules/dictionary_loader_servicable.rb +27 -0
  49. data/lib/LittleWeasel/modules/dictionary_metadata_servicable.rb +29 -0
  50. data/lib/LittleWeasel/modules/dictionary_metadata_validatable.rb +17 -0
  51. data/lib/LittleWeasel/modules/dictionary_sourceable.rb +26 -0
  52. data/lib/LittleWeasel/modules/dictionary_validatable.rb +30 -0
  53. data/lib/LittleWeasel/modules/language.rb +23 -0
  54. data/lib/LittleWeasel/modules/language_validatable.rb +16 -0
  55. data/lib/LittleWeasel/modules/locale.rb +40 -0
  56. data/lib/LittleWeasel/modules/order_validatable.rb +18 -0
  57. data/lib/LittleWeasel/modules/orderable.rb +17 -0
  58. data/lib/LittleWeasel/modules/region.rb +23 -0
  59. data/lib/LittleWeasel/modules/region_validatable.rb +16 -0
  60. data/lib/LittleWeasel/modules/tag_validatable.rb +16 -0
  61. data/lib/LittleWeasel/modules/taggable.rb +31 -0
  62. data/lib/LittleWeasel/modules/word_results_validatable.rb +28 -0
  63. data/lib/LittleWeasel/preprocessors/en_us/capitalize_preprocessor.rb +22 -0
  64. data/lib/LittleWeasel/preprocessors/preprocessed_word.rb +28 -0
  65. data/lib/LittleWeasel/preprocessors/preprocessed_word_validatable.rb +55 -0
  66. data/lib/LittleWeasel/preprocessors/preprocessed_words.rb +55 -0
  67. data/lib/LittleWeasel/preprocessors/preprocessed_words_validatable.rb +27 -0
  68. data/lib/LittleWeasel/preprocessors/word_preprocessable.rb +19 -0
  69. data/lib/LittleWeasel/preprocessors/word_preprocessor.rb +122 -0
  70. data/lib/LittleWeasel/preprocessors/word_preprocessor_managable.rb +114 -0
  71. data/lib/LittleWeasel/preprocessors/word_preprocessor_validatable.rb +40 -0
  72. data/lib/LittleWeasel/preprocessors/word_preprocessors_validatable.rb +24 -0
  73. data/lib/LittleWeasel/services/dictionary_cache_service.rb +262 -0
  74. data/lib/LittleWeasel/services/dictionary_creator_service.rb +94 -0
  75. data/lib/LittleWeasel/services/dictionary_file_loader_service.rb +37 -0
  76. data/lib/LittleWeasel/services/dictionary_killer_service.rb +35 -0
  77. data/lib/LittleWeasel/services/dictionary_loader_service.rb +59 -0
  78. data/lib/LittleWeasel/services/dictionary_metadata_service.rb +114 -0
  79. data/lib/LittleWeasel/services/invalid_words_service.rb +59 -0
  80. data/lib/LittleWeasel/version.rb +3 -1
  81. data/lib/LittleWeasel/word_results.rb +146 -0
  82. data/spec/factories/dictionary.rb +43 -0
  83. data/spec/factories/dictionary_cache_service.rb +95 -0
  84. data/spec/factories/dictionary_creator_service.rb +16 -0
  85. data/spec/factories/dictionary_file_loader_service.rb +13 -0
  86. data/spec/factories/dictionary_hash.rb +39 -0
  87. data/spec/factories/dictionary_key.rb +14 -0
  88. data/spec/factories/dictionary_killer_service.rb +14 -0
  89. data/spec/factories/dictionary_loader_service.rb +14 -0
  90. data/spec/factories/dictionary_manager.rb +10 -0
  91. data/spec/factories/dictionary_metadata.rb +16 -0
  92. data/spec/factories/dictionary_metadata_service.rb +16 -0
  93. data/spec/factories/numeric_filter.rb +12 -0
  94. data/spec/factories/preprocessed_word.rb +16 -0
  95. data/spec/factories/preprocessed_words.rb +41 -0
  96. data/spec/factories/single_character_word_filter.rb +12 -0
  97. data/spec/factories/word_results.rb +16 -0
  98. data/spec/lib/LittleWeasel/block_results_spec.rb +248 -0
  99. data/spec/lib/LittleWeasel/configure_spec.rb +74 -0
  100. data/spec/lib/LittleWeasel/dictionary_key_spec.rb +118 -0
  101. data/spec/lib/LittleWeasel/dictionary_manager_spec.rb +116 -0
  102. data/spec/lib/LittleWeasel/dictionary_spec.rb +289 -0
  103. data/spec/lib/LittleWeasel/filters/en_us/currency_filter_spec.rb +80 -0
  104. data/spec/lib/LittleWeasel/filters/en_us/numeric_filter_spec.rb +66 -0
  105. data/spec/lib/LittleWeasel/filters/en_us/single_character_word_filter_spec.rb +58 -0
  106. data/spec/lib/LittleWeasel/filters/word_filter_managable_spec.rb +180 -0
  107. data/spec/lib/LittleWeasel/filters/word_filter_spec.rb +151 -0
  108. data/spec/lib/LittleWeasel/filters/word_filter_validatable_spec.rb +94 -0
  109. data/spec/lib/LittleWeasel/filters/word_filters_validatable_spec.rb +48 -0
  110. data/spec/lib/LittleWeasel/integraton_tests/dictionary_integration_spec.rb +201 -0
  111. data/spec/lib/LittleWeasel/metadata/dictionary_creator_servicable_spec.rb +54 -0
  112. data/spec/lib/LittleWeasel/metadata/dictionary_metadata_spec.rb +209 -0
  113. data/spec/lib/LittleWeasel/metadata/invalid_words_metadata_spec.rb +155 -0
  114. data/spec/lib/LittleWeasel/metadata/metadata_observerable_spec.rb +31 -0
  115. data/spec/lib/LittleWeasel/metadata/metadatable_spec.rb +35 -0
  116. data/spec/lib/LittleWeasel/modules/class_name_to_symbol_spec.rb +21 -0
  117. data/spec/lib/LittleWeasel/modules/dictionary_file_loader_spec.rb +125 -0
  118. data/spec/lib/LittleWeasel/modules/dictionary_sourceable_spec.rb +44 -0
  119. data/spec/lib/LittleWeasel/modules/language_spec.rb +52 -0
  120. data/spec/lib/LittleWeasel/modules/locale_spec.rb +140 -0
  121. data/spec/lib/LittleWeasel/modules/region_spec.rb +52 -0
  122. data/spec/lib/LittleWeasel/preprocessors/en_us/capitalize_preprocessor_spec.rb +34 -0
  123. data/spec/lib/LittleWeasel/preprocessors/preprocessed_word_spec.rb +105 -0
  124. data/spec/lib/LittleWeasel/preprocessors/preprocessed_word_validatable_spec.rb +143 -0
  125. data/spec/lib/LittleWeasel/preprocessors/preprocessed_words_spec.rb +77 -0
  126. data/spec/lib/LittleWeasel/preprocessors/preprocessed_words_validatable_spec.rb +58 -0
  127. data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_managable_spec.rb +216 -0
  128. data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_spec.rb +175 -0
  129. data/spec/lib/LittleWeasel/preprocessors/word_preprocessor_validatable_spec.rb +109 -0
  130. data/spec/lib/LittleWeasel/preprocessors/word_preprocessors_validatable_spec.rb +49 -0
  131. data/spec/lib/LittleWeasel/services/dictionary_cache_service_spec.rb +444 -0
  132. data/spec/lib/LittleWeasel/services/dictionary_creator_service_spec.rb +119 -0
  133. data/spec/lib/LittleWeasel/services/dictionary_file_loader_service_spec.rb +71 -0
  134. data/spec/lib/LittleWeasel/services/dictionary_loader_service_spec.rb +50 -0
  135. data/spec/lib/LittleWeasel/services/dictionary_metadata_service_spec.rb +279 -0
  136. data/spec/lib/LittleWeasel/word_results_spec.rb +275 -0
  137. data/spec/lib/LittleWeasel/workflow/workflow_spec.rb +20 -0
  138. data/spec/spec_helper.rb +117 -6
  139. data/spec/support/factory_bot.rb +15 -0
  140. data/spec/support/file_helpers.rb +32 -0
  141. data/spec/support/files/empty-dictionary.txt +0 -0
  142. data/{lib/dictionary → spec/support/files/en-US-big.txt} +262156 -31488
  143. data/spec/support/files/en-US-tagged.txt +26 -0
  144. data/spec/support/files/en-US.txt +26 -0
  145. data/spec/support/files/en.txt +26 -0
  146. data/spec/support/files/es-ES.txt +27 -0
  147. data/spec/support/files/es.txt +27 -0
  148. data/spec/support/general_helpers.rb +68 -0
  149. data/spec/support/shared_contexts.rb +108 -0
  150. data/spec/support/shared_examples.rb +105 -0
  151. metadata +408 -65
  152. data/spec/checker/checker_spec.rb +0 -286
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Errors
5
+ # This class describes an error when a dictionary file is zero bytes.
6
+ class DictionaryFileEmptyError < StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Errors
5
+ # This class describes an error when a dictionary file is not found.
6
+ class DictionaryFileNotFoundError < StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Errors
5
+ # This class describes an error when a the dictionary file is too large.
6
+ class DictionaryFileTooLargeError < StandardError
7
+ def initialize(msg: nil)
8
+ unless msg.present?
9
+ msg = 'The dictionary file size is larger than ' \
10
+ "max_dictionary_file_megabytes: #{LittleWeasel.configuration.max_dictionary_file_megabytes}"
11
+ end
12
+ super msg
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Errors
5
+ # This class describes an error when a language is required but was missing.
6
+ class LanguageRequiredError < StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Errors
5
+ # This class describes an error when the method must be overridden.
6
+ class MustOverrideError < StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../errors/must_override_error'
4
+ require_relative '../word_filter'
5
+
6
+ module LittleWeasel
7
+ module Filters
8
+ module EnUs
9
+ # This class represents a currency filter.
10
+ class CurrencyFilter < WordFilter
11
+ class << self
12
+ def filter_match?(word)
13
+ /^[-+]?\$[[:digit:]]{1,3}(?:,?[[:digit:]]{3})*(?:\.[[:digit:]]{2})?$/.match? word.to_s
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../errors/must_override_error'
4
+ require_relative '../word_filter'
5
+
6
+ module LittleWeasel
7
+ module Filters
8
+ module EnUs
9
+ # This class represents a numeric filter.
10
+ class NumericFilter < WordFilter
11
+ class << self
12
+ def filter_match?(word)
13
+ /^[-+]?[[:digit:]]{1,3}(?:,?[[:digit:]]{3})*(?:\.[[:digit:]]{1,2})?$/.match? word.to_s
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../errors/must_override_error'
4
+ require_relative '../word_filter'
5
+
6
+ module LittleWeasel
7
+ module Filters
8
+ module EnUs
9
+ # This class represents a filter for single character words.
10
+ class SingleCharacterWordFilter < WordFilter
11
+ class << self
12
+ def filter_match?(word)
13
+ return false unless word.is_a? String
14
+
15
+ /^[aAI]{1}$/.match? word.to_s
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors/must_override_error'
4
+ require_relative '../modules/class_name_to_symbol'
5
+
6
+ module LittleWeasel
7
+ module Filters
8
+ # This module provides methods/functionality for filtering dictionary words.
9
+ class WordFilter
10
+ include Modules::ClassNameToSymbol
11
+
12
+ attr_reader :filter_on
13
+
14
+ def initialize
15
+ filter_on!
16
+ end
17
+
18
+ class << self
19
+ # Should return true if this word matches the filter criteria; false,
20
+ # otherwise. This class method is unlike the instance method in that it
21
+ # does not consider whether or not this filter is "on" or "off"; it
22
+ # simply returns true or false based on whether or not the word matches
23
+ # the filter.
24
+ def filter_match?(_word)
25
+ raise Errors::MustOverrideError
26
+ end
27
+ end
28
+
29
+ def filter_on=(value)
30
+ raise ArgumentError, "Argument value is not true or false: #{value.class}" \
31
+ unless [true, false].include? value
32
+
33
+ @filter_on = value
34
+ end
35
+
36
+ def filter_match?(word)
37
+ return false if filter_off?
38
+
39
+ self.class.filter_match? word
40
+ end
41
+
42
+ def filter_on!
43
+ self.filter_on = true
44
+ end
45
+
46
+ def filter_off!
47
+ self.filter_on = false
48
+ end
49
+
50
+ def filter_on?
51
+ filter_on
52
+ end
53
+
54
+ def filter_off?
55
+ !filter_on?
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'word_filterable'
4
+ require_relative 'word_filters_validatable'
5
+
6
+ module LittleWeasel
7
+ module Filters
8
+ # This module provides methods/functionality to manage word filters.
9
+ # Word fliters are processes through which words are passed; if the
10
+ # process returns true, the word should be considered valid and all
11
+ # subsequent filters ignored; if the process returns false, the word
12
+ # should be passed to the next filter, and so on. When using word
13
+ # filters, you need to consider whether or not metadata observers
14
+ # should be notified of the word now that it is considered "valid"
15
+ # although may not literally be a valid word in the dictionary.
16
+ module WordFilterManagable
17
+ include WordFilterable
18
+ include WordFiltersValidatable
19
+
20
+ # Override attr_reader word_filter found in WordFilterable
21
+ # so that we don't raise nil errors when using word_filters.
22
+ def word_filters
23
+ @word_filters ||= []
24
+ end
25
+
26
+ def clear_filters
27
+ self.word_filters = []
28
+ end
29
+
30
+ # Appends word filters to the #word_filters Array.
31
+ #
32
+ # If Argument word_filter is nil, a block must be passed to populate
33
+ # the word_filters with an Array of valid word filter objects.
34
+ #
35
+ # This method is used for adding/appending word filters to the
36
+ # word_filters Array. To replace word filters, use #replace_filters;
37
+ # to perform any other manipulation of the word_filters Array,
38
+ # use #word_filters directly.
39
+ def add_filters(word_filters: nil)
40
+ return if word_filters.is_a?(Array) && word_filters.blank?
41
+
42
+ raise 'A block is required if argument word_filters is nil' if word_filters.nil? && !block_given?
43
+
44
+ word_filters ||= []
45
+ yield word_filters if block_given?
46
+
47
+ validate_word_filters word_filters: word_filters
48
+
49
+ self.word_filters.concat word_filters
50
+ end
51
+ alias append_filters add_filters
52
+
53
+ def replace_filters(word_filters:)
54
+ clear_filters
55
+ add_filters word_filters: word_filters
56
+ end
57
+
58
+ def filters_on=(on)
59
+ raise ArgumentError, "Argument on is not true or false: #{on.class}" unless [true, false].include?(on)
60
+
61
+ word_filters.each { |word_filter| word_filter.filter_on = on }
62
+ end
63
+
64
+ def filters_matched(word)
65
+ raise ArgumentError, "Argument word is not a String: #{word.class}" unless word.is_a? String
66
+
67
+ return [] if word_filters.blank?
68
+ return [] if word.empty?
69
+
70
+ word_filters.filter_map do |word_filter|
71
+ word_filter.to_sym if word_filter.filter_match?(word)
72
+ end
73
+ end
74
+
75
+ def filter_match?(word)
76
+ filters_matched(word).present?
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LittleWeasel
4
+ module Filters
5
+ # This module validates word filter types.
6
+ module WordFilterValidatable
7
+ def self.validate(word_filter:)
8
+ raise ArgumentError, "Argument word_filter does not quack right: #{word_filter.class}" \
9
+ unless valid_word_filter?(word_filter: word_filter)
10
+ end
11
+
12
+ # You can use your own word filter types as long as they quack correctly;
13
+ # however, you are responsible for the behavior of these required methods/
14
+ # attributes. It's probably better to follow the pattern of existing word
15
+ # filter objects (e.g. Filters::EnUs::NumericFilter) and inherit from
16
+ # Filters::WordFilter.
17
+ def self.valid_word_filter?(word_filter:)
18
+ word_filter.respond_to?(:filter_on?) &&
19
+ word_filter.respond_to?(:filter_off?) &&
20
+ word_filter.respond_to?(:filter_on) &&
21
+ word_filter.respond_to?(:filter_on=) &&
22
+ word_filter.respond_to?(:filter_match?) &&
23
+ word_filter.class.respond_to?(:filter_match?)
24
+ end
25
+
26
+ def validate_word_filter(word_filter:)
27
+ WordFilterValidatable.validate word_filter: word_filter
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'word_filters_validatable'
4
+
5
+ module LittleWeasel
6
+ module Filters
7
+ # This module provides the word_filters attribute for objects that support
8
+ # word filters.
9
+ module WordFilterable
10
+ @word_filters = []
11
+
12
+ attr_reader :word_filters
13
+
14
+ private
15
+
16
+ attr_writer :word_filters
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'word_filter_validatable'
4
+
5
+ module LittleWeasel
6
+ module Filters
7
+ # This module provides methods to validate an Array of word filters.
8
+ module WordFiltersValidatable
9
+ extend WordFilterValidatable
10
+
11
+ def self.validate(word_filters:)
12
+ return if word_filters.blank?
13
+
14
+ unless word_filters.is_a? Array
15
+ raise ArgumentError,
16
+ "Argument word_filters is not an Array: #{word_filters.class}"
17
+ end
18
+
19
+ word_filters.each do |word_filter|
20
+ validate_word_filter word_filter: word_filter
21
+ end
22
+ end
23
+
24
+ def validate_word_filters(word_filters:)
25
+ WordFiltersValidatable.validate word_filters: word_filters
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'observer'
5
+ require_relative '../modules/class_name_to_symbol'
6
+ require_relative '../modules/configurable'
7
+ require_relative '../modules/dictionary_cache_keys'
8
+ require_relative '../modules/dictionary_cache_servicable'
9
+ require_relative '../modules/dictionary_metadata_servicable'
10
+ require_relative 'metadata_observable_validatable'
11
+ require_relative 'metadatable'
12
+
13
+ module LittleWeasel
14
+ module Metadata
15
+ # This class manages metadata objects related to dictionaries. Metadata
16
+ # objects defined in LittleWeasel::Configuration#metadata_observers are
17
+ # added as observers, provided they are in a state to observe
18
+ # (see Metadata::Metadatable, Metadata::InvalidWords::InvalidWordsMetadata,
19
+ # etc.).
20
+ class DictionaryMetadata
21
+ include Observable
22
+ include Metadata::Metadatable
23
+ include Metadata::MetadataObservableValidatable
24
+ include Modules::ClassNameToSymbol
25
+ include Modules::Configurable
26
+ include Modules::DictionaryCacheKeys
27
+ include Modules::DictionaryCacheServicable
28
+ include Modules::DictionaryMetadataServicable
29
+
30
+ delegate :[], to: :observers
31
+
32
+ attr_reader :dictionary_words, :observers
33
+
34
+ def initialize(dictionary_words:, dictionary_key:, dictionary_cache:, dictionary_metadata:)
35
+ validate_dictionary_key dictionary_key: dictionary_key
36
+ self.dictionary_key = dictionary_key
37
+
38
+ validate_dictionary_cache dictionary_cache: dictionary_cache
39
+ self.dictionary_cache = dictionary_cache
40
+
41
+ validate_dictionary_metadata dictionary_metadata: dictionary_metadata
42
+ self.dictionary_metadata = dictionary_metadata
43
+
44
+ unless dictionary_words.is_a? Hash
45
+ raise ArgumentError,
46
+ "Argument dictionary_words is not a Hash: #{dictionary_words.class.name}."
47
+ end
48
+
49
+ self.dictionary_words = dictionary_words
50
+ self.observers = {}
51
+
52
+ refresh
53
+ end
54
+
55
+ def init(_params: nil)
56
+ dictionary_metadata_service.init(metadata_key: metadata_key)
57
+ self.metadata = {}
58
+ notify action: :init
59
+ refresh_local_metadata
60
+ self
61
+ end
62
+
63
+ def refresh(_params: nil)
64
+ refresh_local_metadata
65
+ if metadata.present?
66
+ # If there is metadata in the dictionary cache, notify the observers
67
+ # to use it...
68
+ notify action: :refresh
69
+ else
70
+ # ...otherwise, notify the observers to initialize themselves.
71
+ init
72
+ end
73
+ end
74
+
75
+ def notify(action:, params: nil)
76
+ if count_observers.positive?
77
+ changed
78
+ notify_observers action, params
79
+ end
80
+ self
81
+ end
82
+
83
+ def add_observers(force: false)
84
+ delete_observers if force
85
+
86
+ raise 'Observers have already been added; use #add_observers(force: true) instead' if count_observers.positive?
87
+
88
+ observer_classes = config.metadata_observers
89
+ yield observer_classes if block_given?
90
+
91
+ observer_classes.each do |o|
92
+ # If the medatata observer is not in a state to observe,
93
+ # or is turned "off", skip it...
94
+ #
95
+ # See Metadata::MetadataObserverable.observe? comments.
96
+ next unless o.observe?
97
+
98
+ # If this observer has already beed added, don't add it again.
99
+ next if observers.key? o.metadata_key
100
+
101
+ observer = o.new(dictionary_metadata_object: self,
102
+ dictionary_words: dictionary_words,
103
+ dictionary_key: dictionary_key,
104
+ dictionary_cache: dictionary_cache,
105
+ dictionary_metadata: dictionary_metadata)
106
+
107
+ # Only add metadata objects that are capable of observing
108
+ # (i.e. #observe?).
109
+ add_observer observer if observer.observe?
110
+ end
111
+ # This is how each metadata object gets initialized. Only notify if
112
+ # there are any observers.
113
+ notify(action: :init) if count_observers.positive?
114
+ self
115
+ end
116
+
117
+ def add_observer(observer, func = :update)
118
+ validate_metadata_observable observer
119
+
120
+ super
121
+ observers[observer.metadata_key] = observer
122
+ end
123
+
124
+ def delete_observer(observer)
125
+ validate_metadata_observable observer
126
+
127
+ super
128
+ observers.delete(observer.metadata_key)
129
+ end
130
+
131
+ def delete_observers
132
+ super
133
+ self.observers = {}
134
+ end
135
+
136
+ private
137
+
138
+ attr_writer :dictionary_words, :observers
139
+
140
+ def update_dictionary_metadata(value:)
141
+ dictionary_metadata_service.set_dictionary_metadata(value: value, metadata_key: metadata_key)
142
+ end
143
+ end
144
+ end
145
+ end