foobara 0.1.9 → 0.1.11

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 (25) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/projects/command/src/command_pattern_implementation/concerns/errors.rb +9 -3
  4. data/projects/command/src/command_pattern_implementation/concerns/errors_type.rb +22 -3
  5. data/projects/command/src/command_pattern_implementation/concerns/inputs.rb +20 -0
  6. data/projects/command/src/command_pattern_implementation/concerns/inputs_type.rb +4 -0
  7. data/projects/command/src/command_pattern_implementation/concerns/runtime.rb +42 -1
  8. data/projects/common/src/error.rb +5 -0
  9. data/projects/domain/src/domain_module_extension.rb +0 -2
  10. data/projects/model/src/extensions/type_declarations/handlers/extend_model_type_declaration/to_type_transformer.rb +1 -1
  11. data/projects/model/src/extensions/type_declarations/lazy_element_types/model.rb +6 -4
  12. data/projects/namespace/src/is_namespace.rb +5 -3
  13. data/projects/namespace/src/namespace/lookup_mode.rb +1 -0
  14. data/projects/type_declarations/src/handlers/extend_array_type_declaration/to_type_transformer.rb +1 -1
  15. data/projects/type_declarations/src/handlers/extend_associative_array_type_declaration/to_type_transformer.rb +1 -1
  16. data/projects/type_declarations/src/handlers/extend_attributes_type_declaration/to_type_transformer.rb +1 -1
  17. data/projects/type_declarations/src/handlers/extend_tuple_type_declaration/to_type_transformer.rb +1 -1
  18. data/projects/type_declarations/src/lazy_element_types/array.rb +7 -5
  19. data/projects/type_declarations/src/lazy_element_types/attributes.rb +12 -10
  20. data/projects/type_declarations/src/lazy_element_types/hash.rb +18 -16
  21. data/projects/type_declarations/src/lazy_element_types/tuple.rb +9 -7
  22. data/projects/type_declarations/src/type_builder.rb +2 -8
  23. data/projects/types/src/type.rb +15 -18
  24. data/version.rb +1 -1
  25. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f058e7ba0dd38cc79d4fa591be119decc9fa12e8d2e9b94de732d01636072b7
4
- data.tar.gz: 71b3f8faf719fb02fc21a56a1feec8b506de34cc022244166158ed4b8482842c
3
+ metadata.gz: e3d84d4d11b964a30291cd92382117fa45a7736e0ee0b6a247224427a33e6598
4
+ data.tar.gz: d1a2b86a1983b354412c1e83e2dbfc2ad259feddba8d09a01d3ef39038a27163
5
5
  SHA512:
6
- metadata.gz: 8a928e7ff495266da1595fd702fe2f1d790de3e2fb21df0f4c0ea410a53766c00e724a7ea7c2da31d4be7d77dc9068d78ff77cee95a6945e03a0a81664b12d5c
7
- data.tar.gz: 8e70ae9f7fb74d23ab80e99854b318246c61a104ca86c4ef7a031437aa9878dd9213eb7bd952576bb111e7bdf84171f7e2141b959eb604c7bf81e733954ea329
6
+ metadata.gz: c0c8ed8a92fbc3c1e37172881f855aa0072ffb50cd5f3c652677ddaee2af021aa937a60c2aa35d5c61f110c5b2968474b3cfaf72c593d3f9c5a405f1a3ace968
7
+ data.tar.gz: e772dc00b34a3293111f49f958c7615507c206c47f10932c461852d0b4a1ada737a9c0c5f198da11bded280aa69d1f0f383e408b241977ec3521fa5e98e76796
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # [0.1.11] - 2025-09-24
2
+
3
+ - Make sure type builder caches are cleared when namespace caches are cleared
4
+ - Make sure element type resolution is performed in the right namespace
5
+ - Do not dual-purpose memoized @element_type(s) var, and memoize in IsNamespace.lru_cache instead
6
+
7
+ # [0.1.10] - 2025-09-11
8
+
9
+ - Add NotFoundError when an entity input doesn't exist
10
+ - Make #add_input_error more flexible
11
+ - Fix an error path array bug
12
+ - Fix a bug that might prevent inherited possible errors
13
+
1
14
  # [0.1.9] - 2025-09-08
2
15
 
3
16
  - Support passing load_paths: to Entity.load to pre-load certain associations
@@ -78,7 +78,7 @@ module Foobara
78
78
  elsif args.empty? || (args.size == 1 && args.first.is_a?(Hash))
79
79
  error_args = opts.merge(args.first || {})
80
80
  symbol = error_args[:symbol]
81
- path = Util.array(error_args[:input] || error_args[:path])
81
+ path = error_args[:input] || error_args[:path]
82
82
 
83
83
  error_args = error_args.except(:input)
84
84
 
@@ -100,8 +100,14 @@ module Foobara
100
100
  input, symbol, message = args
101
101
  context = opts
102
102
 
103
- error_class = self.class.lookup_input_error_class(symbol, input)
104
- error_class.new(path: Util.array(input), symbol:, context:, message:)
103
+ if symbol.is_a?(::Class) && symbol < Foobara::Error
104
+ error_class = symbol
105
+ symbol = error_class.symbol
106
+ else
107
+ error_class = self.class.lookup_input_error_class(symbol, input)
108
+ end
109
+
110
+ error_class.new(path: input, symbol:, context:, message:)
105
111
  else
106
112
  # :nocov:
107
113
  raise ArgumentError,
@@ -81,6 +81,11 @@ module Foobara
81
81
  symbol = error_class.symbol
82
82
 
83
83
  possible_error = PossibleError.new(error_class, symbol:, data:)
84
+
85
+ if path.is_a?(::String) || path.is_a?(::Symbol)
86
+ path = path.to_s.split(".")
87
+ end
88
+
84
89
  possible_error.prepend_path!(path)
85
90
 
86
91
  possible_error.manually_added = true
@@ -93,10 +98,24 @@ module Foobara
93
98
 
94
99
  # TODO: kill this method in favor of possible_errors
95
100
  def error_context_type_map
101
+ return @error_context_type_map if defined?(@error_context_type_map)
102
+
96
103
  process_error_constants
97
- @error_context_type_map ||= if superclass < Foobara::Command
98
- superclass.error_context_type_map.dup
99
- end || {}
104
+ map = if superclass < Foobara::Command
105
+ superclass.error_context_type_map.dup
106
+ end || {}
107
+
108
+ @error_context_type_map = if @error_context_type_map
109
+ map.merge(@error_context_type_map)
110
+ else
111
+ map
112
+ end
113
+
114
+ inputs_association_paths&.each do |data_path|
115
+ possible_input_error(data_path.to_sym, CommandPatternImplementation::NotFoundError)
116
+ end
117
+
118
+ @error_context_type_map
100
119
  end
101
120
 
102
121
  def register_possible_error_class(possible_error)
@@ -6,6 +6,26 @@ module Foobara
6
6
 
7
7
  include Concern
8
8
 
9
+ module ClassMethods
10
+ def inputs_association_paths
11
+ return @inputs_association_paths if defined?(@inputs_association_paths)
12
+
13
+ @inputs_association_paths = if inputs_type.nil?
14
+ nil
15
+ else
16
+ keys = Entity.construct_associations(inputs_type).keys
17
+
18
+ if keys.empty?
19
+ nil
20
+ else
21
+ keys.map do |key|
22
+ DataPath.new(key)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
9
29
  attr_reader :inputs, :raw_inputs
10
30
 
11
31
  def initialize(inputs = {})
@@ -12,6 +12,10 @@ module Foobara
12
12
  unregister_possible_error_if_registered(possible_error)
13
13
  end
14
14
 
15
+ if defined?(@inputs_association_paths)
16
+ remove_instance_variable(:@inputs_association_paths)
17
+ end
18
+
15
19
  type = type_for_declaration(...)
16
20
 
17
21
  if type.extends?(BuiltinTypes[:model]) && !type.extends?(BuiltinTypes[:entity])
@@ -7,6 +7,13 @@ module Foobara
7
7
  class CannotHaltWithoutAddingErrors < StandardError; end
8
8
  class Halt < StandardError; end
9
9
 
10
+ class NotFoundError < Foobara::DataError
11
+ context do
12
+ entity_class :string, :required
13
+ criteria :duck
14
+ end
15
+ end
16
+
10
17
  module ClassMethods
11
18
  def run(...)
12
19
  new(...).run
@@ -81,7 +88,41 @@ module Foobara
81
88
  end
82
89
 
83
90
  def validate_records
84
- # noop
91
+ self.class.inputs_association_paths&.each do |data_path|
92
+ if data_path.last == :"#"
93
+ records = data_path.values_at(@inputs)
94
+
95
+ records.each.with_index do |record, index|
96
+ if record&.persisted? && !record.loaded?
97
+ begin
98
+ record.class.load(record)
99
+ rescue Foobara::Entity::NotFoundError => e
100
+ add_input_error(
101
+ [*data_path.path[..-2], index],
102
+ CommandPatternImplementation::NotFoundError,
103
+ criteria: e.criteria,
104
+ entity_class: record.class.model_type.scoped_full_name
105
+ )
106
+ end
107
+ end
108
+ end
109
+ else
110
+ record = data_path.value_at(@inputs)
111
+
112
+ if record&.persisted? && !record.loaded?
113
+ begin
114
+ record.class.load(record)
115
+ rescue Foobara::Entity::NotFoundError => e
116
+ add_input_error(
117
+ data_path.to_s,
118
+ CommandPatternImplementation::NotFoundError,
119
+ criteria: e.criteria,
120
+ entity_class: record.class.model_type.scoped_full_name
121
+ )
122
+ end
123
+ end
124
+ end
125
+ end
85
126
  end
86
127
 
87
128
  def validate
@@ -215,6 +215,11 @@ module Foobara
215
215
  self.message = message
216
216
  self.context = context
217
217
  self.category = category
218
+
219
+ if path.is_a?(::String) || path.is_a?(::Symbol)
220
+ path = path.to_s.split(".").map(&:to_sym)
221
+ end
222
+
218
223
  self.path = path
219
224
  self.runtime_path = runtime_path
220
225
  self.is_fatal = is_fatal
@@ -65,8 +65,6 @@ module Foobara
65
65
  def foobara_unregister(scoped)
66
66
  scoped = to_scoped(scoped)
67
67
 
68
- foobara_type_builder.clear_cache
69
-
70
68
  if scoped.is_a?(Foobara::Types::Type)
71
69
  parent_mod = nil
72
70
 
@@ -77,7 +77,7 @@ module Foobara
77
77
  if outcome.success?
78
78
  type = outcome.result
79
79
 
80
- type.element_types = :Model
80
+ type.element_types_loader = LazyElementTypes::Model
81
81
 
82
82
  model_class = type.target_class
83
83
  existing_model_type = model_class.model_type
@@ -5,11 +5,13 @@ module Foobara
5
5
  module_function
6
6
 
7
7
  def resolve(type)
8
- attributes_type_declaration = type.declaration_data[:attributes_declaration]
8
+ Namespace.use type.created_in_namespace do
9
+ attributes_type_declaration = type.declaration_data[:attributes_declaration]
9
10
 
10
- type.element_types = TypeDeclarations.strict do
11
- handler = Domain.current.foobara_type_builder.handler_for_class(Handlers::ExtendAttributesTypeDeclaration)
12
- handler.process_value!(TypeDeclaration.new(attributes_type_declaration))
11
+ type.element_types = TypeDeclarations.strict do
12
+ handler = Domain.current.foobara_type_builder.handler_for_class(Handlers::ExtendAttributesTypeDeclaration)
13
+ handler.process_value!(TypeDeclaration.new(attributes_type_declaration))
14
+ end
13
15
  end
14
16
  end
15
17
  end
@@ -5,11 +5,13 @@ module Foobara
5
5
  module IsNamespace
6
6
  class << self
7
7
  def lru_cache
8
- @lru_cache ||= Foobara::LruCache.new(100)
8
+ @lru_cache ||= Foobara::LruCache.new(200)
9
9
  end
10
10
 
11
11
  def clear_lru_cache!
12
- @lru_cache = nil
12
+ if @lru_cache
13
+ lru_cache.reset!
14
+ end
13
15
  end
14
16
  end
15
17
 
@@ -141,7 +143,7 @@ module Foobara
141
143
  LookupMode.validate!(mode)
142
144
  path = Namespace.to_registry_path(path)
143
145
 
144
- lru_cache.cached([path, mode, self, filter]) do
146
+ lru_cache.cached([self, path, mode, *filter]) do
145
147
  visited = Set.new
146
148
  foobara_lookup_without_cache(path, filter:, mode:, visited:)
147
149
  end
@@ -27,6 +27,7 @@ module Foobara
27
27
  # parent: n
28
28
  # dependent: n
29
29
  # TODO: don't we have an enumerated class/project for this?
30
+ # Maybe use bitmasks for the above 3 places to look instead of a list of 7 lookup types? (There should be 8...)
30
31
  module LookupMode
31
32
  GENERAL = :general
32
33
  RELAXED = :relaxed
@@ -8,7 +8,7 @@ module Foobara
8
8
  class ToTypeTransformer < ExtendAssociativeArrayTypeDeclaration::ToTypeTransformer
9
9
  def transform(strict_type_declaration)
10
10
  type = super
11
- type.element_type = :Array
11
+ type.element_type_loader = LazyElementTypes::Array
12
12
  type
13
13
  end
14
14
  end
@@ -10,7 +10,7 @@ module Foobara
10
10
  class ToTypeTransformer < ExtendRegisteredTypeDeclaration::ToTypeTransformer
11
11
  def transform(strict_type_declaration)
12
12
  type = super
13
- type.element_types = :Hash
13
+ type.element_types_loader = LazyElementTypes::Hash
14
14
  type
15
15
  end
16
16
  end
@@ -8,7 +8,7 @@ module Foobara
8
8
  class ToTypeTransformer < ExtendAssociativeArrayTypeDeclaration::ToTypeTransformer
9
9
  def transform(strict_type_declaration)
10
10
  type = super
11
- type.element_types = :Attributes
11
+ type.element_types_loader = LazyElementTypes::Attributes
12
12
  type
13
13
  end
14
14
  end
@@ -8,7 +8,7 @@ module Foobara
8
8
  class ToTypeTransformer < ExtendAssociativeArrayTypeDeclaration::ToTypeTransformer
9
9
  def transform(strict_type_declaration)
10
10
  type = super
11
- type.element_types = :Tuple
11
+ type.element_types_loader = LazyElementTypes::Tuple
12
12
  type
13
13
  end
14
14
  end
@@ -5,13 +5,15 @@ module Foobara
5
5
  module_function
6
6
 
7
7
  def resolve(type)
8
- element_type_declaration = type.declaration_data[:element_type_declaration]
8
+ Namespace.use type.created_in_namespace do
9
+ element_type_declaration = type.declaration_data[:element_type_declaration]
9
10
 
10
- type.element_type = if element_type_declaration
11
- TypeDeclarations.strict do
12
- Domain.current.foobara_type_from_declaration(element_type_declaration)
11
+ type.element_type = if element_type_declaration
12
+ TypeDeclarations.strict do
13
+ Domain.current.foobara_type_from_declaration(element_type_declaration)
14
+ end
13
15
  end
14
- end
16
+ end
15
17
  end
16
18
  end
17
19
  end
@@ -5,21 +5,23 @@ module Foobara
5
5
  module_function
6
6
 
7
7
  def resolve(type)
8
- type_declarations = type.declaration_data[:element_type_declarations]
8
+ Namespace.use type.created_in_namespace do
9
+ type_declarations = type.declaration_data[:element_type_declarations]
9
10
 
10
- type.element_types = if type_declarations
11
- if type_declarations.empty?
12
- {}
13
- else
14
- TypeDeclarations.strict do
15
- domain = Domain.current
11
+ type.element_types = if type_declarations
12
+ if type_declarations.empty?
13
+ {}
14
+ else
15
+ TypeDeclarations.strict do
16
+ domain = Domain.current
16
17
 
17
- type_declarations.transform_values do |attribute_declaration|
18
- domain.foobara_type_from_declaration(attribute_declaration)
18
+ type_declarations.transform_values do |attribute_declaration|
19
+ domain.foobara_type_from_declaration(attribute_declaration)
20
+ end
19
21
  end
20
22
  end
21
23
  end
22
- end
24
+ end
23
25
  end
24
26
  end
25
27
  end
@@ -7,30 +7,32 @@ module Foobara
7
7
  module_function
8
8
 
9
9
  def resolve(type)
10
- declaration_data = type.declaration_data
10
+ Namespace.use type.created_in_namespace do
11
+ declaration_data = type.declaration_data
11
12
 
12
- key_type_declaration = declaration_data[:key_type_declaration]
13
- value_type_declaration = declaration_data[:value_type_declaration]
13
+ key_type_declaration = declaration_data[:key_type_declaration]
14
+ value_type_declaration = declaration_data[:value_type_declaration]
14
15
 
15
- type.element_types = if key_type_declaration || value_type_declaration
16
- TypeDeclarations.strict do
17
- domain = Domain.current
16
+ type.element_types = if key_type_declaration || value_type_declaration
17
+ TypeDeclarations.strict do
18
+ domain = Domain.current
18
19
 
19
- key_declaration = if key_type_declaration
20
- domain.foobara_type_from_declaration(key_type_declaration)
21
- else
22
- BuiltinTypes[:duck]
23
- end
24
-
25
- value_declaration = if value_type_declaration
26
- domain.foobara_type_from_declaration(value_type_declaration)
20
+ key_declaration = if key_type_declaration
21
+ domain.foobara_type_from_declaration(key_type_declaration)
27
22
  else
28
23
  BuiltinTypes[:duck]
29
24
  end
30
25
 
31
- [key_declaration, value_declaration]
26
+ value_declaration = if value_type_declaration
27
+ domain.foobara_type_from_declaration(value_type_declaration)
28
+ else
29
+ BuiltinTypes[:duck]
30
+ end
31
+
32
+ [key_declaration, value_declaration]
33
+ end
32
34
  end
33
- end
35
+ end
34
36
  end
35
37
  end
36
38
  end
@@ -5,17 +5,19 @@ module Foobara
5
5
  module_function
6
6
 
7
7
  def resolve(type)
8
- element_type_declarations = type.declaration_data[:element_type_declarations]
8
+ Namespace.use type.created_in_namespace do
9
+ element_type_declarations = type.declaration_data[:element_type_declarations]
9
10
 
10
- type.element_types = if element_type_declarations
11
- TypeDeclarations.strict do
12
- domain = Domain.current
11
+ type.element_types = if element_type_declarations
12
+ TypeDeclarations.strict do
13
+ domain = Domain.current
13
14
 
14
- element_type_declarations.map do |element_type_declaration|
15
- domain.foobara_type_from_declaration(element_type_declaration)
15
+ element_type_declarations.map do |element_type_declaration|
16
+ domain.foobara_type_from_declaration(element_type_declaration)
17
+ end
16
18
  end
17
19
  end
18
- end
20
+ end
19
21
  end
20
22
  end
21
23
  end
@@ -129,7 +129,7 @@ module Foobara
129
129
  end
130
130
 
131
131
  def type_for_declaration(*type_declaration_bits, &block)
132
- lru_cache.cached([type_declaration_bits, block]) do
132
+ lru_cache.cached([self, *block&.object_id, *type_declaration_bits]) do
133
133
  type_for_declaration_without_cache(*type_declaration_bits, &block)
134
134
  end
135
135
  rescue NoTypeDeclarationHandlerFoundError
@@ -152,16 +152,10 @@ module Foobara
152
152
  handler.process_value!(type_declaration)
153
153
  end
154
154
 
155
- def clear_cache
156
- if @lru_cache
157
- lru_cache.reset!
158
- end
159
- end
160
-
161
155
  private
162
156
 
163
157
  def lru_cache
164
- @lru_cache ||= Foobara::LruCache.new(100)
158
+ Namespace::IsNamespace.lru_cache
165
159
  end
166
160
  end
167
161
  end
@@ -20,7 +20,9 @@ module Foobara
20
20
  :name,
21
21
  :description,
22
22
  :sensitive,
23
- :sensitive_exposed
23
+ :sensitive_exposed,
24
+ :element_type_loader,
25
+ :element_types_loader
24
26
 
25
27
  attr_reader :type_symbol,
26
28
  :casters,
@@ -44,8 +46,6 @@ module Foobara
44
46
  transformers: [],
45
47
  validators: [],
46
48
  element_processors: nil,
47
- element_type: nil,
48
- element_types: nil,
49
49
  structure_count: nil,
50
50
  processor_classes_requiring_type: nil,
51
51
  sensitive: nil,
@@ -64,9 +64,6 @@ module Foobara
64
64
  self.element_processors = [*element_processors, *base_type&.element_processors]
65
65
 
66
66
  self.structure_count = structure_count
67
- # TODO: combine these maybe with the term "children_types"?
68
- self.element_types = element_types
69
- self.element_type = element_type
70
67
  self.target_classes = Util.array(target_classes)
71
68
  self.processor_classes_requiring_type = processor_classes_requiring_type
72
69
 
@@ -95,23 +92,23 @@ module Foobara
95
92
  end
96
93
 
97
94
  def element_type
98
- type = @element_type || base_type&.element_type
99
-
100
- if type.is_a?(::Symbol)
101
- type = @element_type = TypeDeclarations::LazyElementTypes.const_get(type).resolve(self)
95
+ lru_cache.cached([self, :element_type]) do
96
+ if element_type_loader
97
+ element_type_loader.resolve(self)
98
+ else
99
+ base_type&.element_type
100
+ end
102
101
  end
103
-
104
- type
105
102
  end
106
103
 
107
104
  def element_types
108
- types = @element_types || base_type&.element_types
109
-
110
- if types.is_a?(::Symbol)
111
- types = @element_types = TypeDeclarations::LazyElementTypes.const_get(types).resolve(self)
105
+ lru_cache.cached([self, :element_types]) do
106
+ if element_types_loader
107
+ element_types_loader.resolve(self)
108
+ else
109
+ base_type&.element_types
110
+ end
112
111
  end
113
-
114
- types
115
112
  end
116
113
 
117
114
  def has_sensitive_types?
data/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Foobara
2
2
  module Version
3
- VERSION = "0.1.9".freeze
3
+ VERSION = "0.1.11".freeze
4
4
  MINIMUM_RUBY_VERSION = ">= 3.4.0".freeze
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi