foobara 0.0.125 → 0.0.127

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 (23) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +86 -19
  4. data/projects/builtin_types/src/attributes/supported_transformers/defaults/type_declaration_extension/extend_attributes_type_declaration/desugarizers/move_defaults_from_element_types_to_root.rb +1 -1
  5. data/projects/builtin_types/src/attributes/supported_validators/required/type_declaration_extension/extend_attributes_type_declaration/desugarizers/move_required_from_element_types_to_root.rb +1 -1
  6. data/projects/builtin_types/src/duck/supported_validators/instance_of/type_declaration_extension/extend_registered_type_declaration/desugarizers/class_type_desugarizer.rb +32 -0
  7. data/projects/builtin_types/src/duck/supported_validators/instance_of.rb +10 -0
  8. data/projects/command/src/command/concerns/shortcut_for_run.rb +2 -0
  9. data/projects/command/src/command_pattern_implementation/concerns/inputs.rb +0 -2
  10. data/projects/command/src/command_pattern_implementation/concerns/runtime.rb +2 -1
  11. data/projects/command/src/command_pattern_implementation/concerns/subcommands.rb +2 -0
  12. data/projects/command_connectors/src/command_connector.rb +5 -0
  13. data/projects/command_connectors/src/transformed_command.rb +25 -0
  14. data/projects/detached_entity/src/concerns/associations.rb +1 -1
  15. data/projects/domain_mapper/src/domain_mapper_lookups.rb +2 -2
  16. data/projects/model/src/extensions/type_declarations/handlers/extend_registered_model_type_declaration.rb +1 -0
  17. data/projects/nested_transactionable/lib/foobara/nested_transactionable.rb +31 -1
  18. data/projects/type_declarations/src/handlers/extend_attributes_type_declaration/hash_desugarizer.rb +2 -1
  19. data/projects/type_declarations/src/handlers/extend_attributes_type_declaration/to_type_transformer.rb +1 -1
  20. data/projects/type_declarations/src/handlers/registered_type_declaration.rb +3 -1
  21. data/projects/types/src/type.rb +23 -5
  22. data/projects/value/src/processor/casting.rb +3 -3
  23. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff16e115901fed01d02a1d3a82cb136672b4f2a903f4d982311a38ea03f711d3
4
- data.tar.gz: 206a0ea46c89ca30e036b9400870a237574cfac44783f3931e03eba1918bb06a
3
+ metadata.gz: 531c75e332f6e7cc297b0b56689d91f59737040fe533814a8b780defc937b2aa
4
+ data.tar.gz: 16dd8725423abe9155c1fd5b89c752271ce568ec83cca52c7654204ee114f559
5
5
  SHA512:
6
- metadata.gz: d0ca391c67892c58ac22452c084a4f7ce8f0de5b7fbfff756f57faa0ce822df2ef2786d3818c44871267203a9b323e5a89b810404d26508b29e9eb7ac6dc3b38
7
- data.tar.gz: 1dfc1aa32f61a7b832dd29cb0d3a966fc38bd2908a146ca208dcb9aadaab9880beb07efd4256cb57b820e3b2982aff37f98c4ecea6bb5a31bd1a3fcfaa217e0d
6
+ metadata.gz: 9c97ed1f00a114ea3ff8e1932cb32a13e8e538b1a0dc694f542060febafcae9e515e4888695cdbeede192e490ea280e6bddf8bf54b64e6339fe7adeaaeaa42a1
7
+ data.tar.gz: 4e414afd9090bc83685ce1b2e30fb31948963f4d57cff4d77ba4aa698547e716fd5a562442845f60cd8e6affb06263797fcf0c539a7f5478fceb9dec6b1412de
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # [0.0.127] - 2025-05-25
2
+
3
+ - Add better support for using ruby classes as types
4
+ - Support using :attributes as a type directly
5
+ - Make TransformedCommand#inputs reflect actual received inputs not transformed inputs
6
+ - Add NestedTransactionable.with_needed_transactions_for_type
7
+
8
+ # [0.0.126] - 2025-05-21
9
+
10
+ - Fix bugs with transactions, Command.depends_on
11
+ - Allow anonymous commands
12
+ - Add CommandConnector#all_exposed_type_names
13
+
1
14
  # [0.0.125] - 2025-05-15
2
15
 
3
16
  - Create a yaml inputs sugar
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
- Foobara is a software framework with a focus on projects that have
2
- a complicated business domain. It accomplishes this by helping to
3
- build projects that are command-centric and discoverable, as well as some other features that aid in the mission.
1
+ Foobara is a software framework to help you encapsulate your high-level
2
+ domain operations in commands, and automatically expose machine-readable formal metadata about those
3
+ commands so that integration code can be decoupled and abstracted away.
4
+
5
+ This, as well as some other features of foobara, help manage domain complexity and produce
6
+ more flexible systems with better management of domain complexity.
4
7
 
5
8
  You can watch a video that gives a good overview of what Foobara is and its goals here:
6
9
  [Introduction to the Foobara software framework](https://youtu.be/SSOmQqjNSVY)
@@ -24,6 +27,7 @@ You can watch a video that gives a good overview of what Foobara is and its goal
24
27
  * [HTTP Command Connectors](#http-command-connectors)
25
28
  * [Rack Connector](#rack-connector)
26
29
  * [Rails Connector](#rails-connector)
30
+ * [MCP Command Connector](#mcp-command-connector)
27
31
  * [Async Command Connectors](#async-command-connectors)
28
32
  * [Scheduler Command Connectors](#scheduler-command-connectors)
29
33
  * [Intermediate Foobara](#intermediate-foobara)
@@ -51,6 +55,7 @@ You can watch a video that gives a good overview of what Foobara is and its goal
51
55
  * [Namespaces](#namespaces)
52
56
  * [Value processors](#value-processors)
53
57
  * [Additional learning materials/Documentation](#additional-learning-materialsdocumentation)
58
+ * [Help](#help)
54
59
  * [Contributing](#contributing)
55
60
  * [Developing locally](#developing-locally)
56
61
  * [Monorepo Structure](#monorepo-structure)
@@ -67,7 +72,7 @@ You can watch a video that gives a good overview of what Foobara is and its goal
67
72
 
68
73
  ## Discoverability
69
74
 
70
- * This means there is a formal machine-readable description of the systems/subsystems
75
+ * This means there is a formal, machine-readable, description of the systems/subsystems
71
76
  * The implication of this is that integration code can be abstracted away.
72
77
 
73
78
  ## Implications of command-centric + discoverability
@@ -75,6 +80,7 @@ You can watch a video that gives a good overview of what Foobara is and its goal
75
80
  * The system better communicates the mental model of the problem and the chosen solution
76
81
  * Engineers are able to spend more time writing code relevant to the domain and less time
77
82
  writing code related to specific tech-stack, software pattern, or architecture decisions.
83
+ * Engineers are much less likely to harmfully couple domain logic to integration logic.
78
84
  * Engineers can spend more time operating within a specific mental model at a time instead of
79
85
  multiple mental models all at once.
80
86
 
@@ -86,14 +92,14 @@ You can watch a video that gives a good overview of what Foobara is and its goal
86
92
  * Domains are namespaces of Commands, types, and errors
87
93
  * Domains (and commands) have explicit, unidirectional dependencies on other domains
88
94
  * Organizations are namespaces of Domains
89
- * Domain mappers
90
- * These can map a concept from one domain to another
91
- * This separation of concerns leads to commands that have code
92
- that reflects the domain they belong to as opposed to logic from many different domains
93
95
  * Remote commands
94
96
  * These have the same interface as commands that live in other systems and act as a proxy to them
95
97
  * This allows rearchitecting of systems without changing interfaces and so reducing refactoring/testing required
96
98
  * These currently exist for both Ruby and Typescript
99
+ * Domain mappers
100
+ * These can map a concept from one domain to another
101
+ * This separation of concerns leads to commands that have code
102
+ that reflects the domain they belong to as opposed to logic from many different domains
97
103
  * Code generators
98
104
  * Similar to remote commands, discoverability enables other types of tooling, including code generators,
99
105
  documentation tools, etc
@@ -107,8 +113,6 @@ You can watch a video that gives a good overview of what Foobara is and its goal
107
113
  # Installation
108
114
 
109
115
  To add foobara to an existing project, you can add `foobara` gem to your Gemfile or .gemspec as you normally would.
110
- You can also `gem install foobara` and whatever additional foobara gems you need and use them in
111
- scripts by requiring them.
112
116
 
113
117
  You could also use a generator to create a new Ruby Foobara project using the `foob` gem with `gem install foob` and
114
118
  then run `foob generate ruby-project --name your-org/your-new-project-name`
@@ -344,7 +348,7 @@ We have so far seen one Foobara type which is `integer` but there are many other
344
348
  We used :integer to type the operands of our Add command. There are many ways to express types in Foobara
345
349
  but in this case we used the attributes DSL. It has the form:
346
350
 
347
- `<attribute_naem> <type_symbol> [processors] [description]`
351
+ `<attribute_name> <type_symbol> [processors] [description]`
348
352
 
349
353
  We used a processor `:required` but there are many others and you can create your own.
350
354
 
@@ -830,6 +834,67 @@ end
830
834
 
831
835
  This has the same effect as the previous code and is just a stylistic alternative.
832
836
 
837
+ ### MCP Command Connector
838
+
839
+ We can have an MCP server for free for our commands. Let's try it!
840
+
841
+ Let's simulate some bad data and expose some commands over MCP and ask an
842
+ MCP client like Claude Code to find/fix the bad data.
843
+
844
+ ```ruby
845
+ CreateCapybara.run!(name: "Fumiko", year_of_birth: 2020)
846
+ # Simulate a year accidentally entered with a 2-digit format where a 4-digit format was expected:
847
+ CreateCapybara.run!(name: "Barbara", year_of_birth: 19)
848
+ CreateCapybara.run!(name: "Basil", year_of_birth: 2021)
849
+
850
+ mcp_connector = Foobara::McpConnector.new
851
+
852
+ mcp_connector.connect(FindAllCapybaras)
853
+ mcp_connector.connect(UpdateCapybara)
854
+
855
+ mcp_connector.run_stdio_server
856
+ ```
857
+
858
+ Oh whoops, somebody entered Barbara's year of birth as 19 instead of 2019, whoops!
859
+ Let's ask Claude Code to find/fix the bad record:
860
+
861
+ ```
862
+ $ claude
863
+ > Hi! There's a Capybara whose birth year was entered incorrectly. Can you find which one
864
+ and fix it? Thanks!
865
+ ● I'll help find and fix the capybara with the incorrect birth year. Let me search for the
866
+ capybaras first.
867
+ ● mcp-test:FindAllCapybaras (MCP)()…
868
+ ⎿ [
869
+ {
870
+ "name": "Fumiko",
871
+ "year_of_birth": 2020,
872
+ "id": 1
873
+
874
+ … +7 lines (ctrl+r to expand)
875
+
876
+ "name": "Basil",
877
+ "year_of_birth": 2021,
878
+ "id": 3
879
+ }
880
+ ]
881
+ ● It looks like Barbara (id 2) has an incorrect birth year of 19, which is too low. Let me
882
+ fix that to 2019.
883
+ ● mcp-test:UpdateCapybara (MCP)(id: 2, year_of_birth: 2019)…
884
+ ⎿ {
885
+ "name": "Barbara",
886
+ "year_of_birth": 2019,
887
+ "id": 2
888
+ }
889
+ ● Fixed! Barbara's birth year has been updated from 19 to 2019.
890
+ > Great! Thanks!
891
+ ● You're welcome!
892
+ ```
893
+ Record fixed! A benefit of encapsulating your high-level domain operations behind commands that have machine-readable
894
+ formal manifests describing them is easy tooling like this.
895
+
896
+ See https://github.com/foobara/mcp-connector for details on how to do this if interested.
897
+
833
898
  #### Async Command Connectors
834
899
 
835
900
  Let's connect a command to some sort of async job solution. We'll connect our IncrementAge command to Resque:
@@ -2041,17 +2106,19 @@ TODO: give some code examples
2041
2106
  * All docs combined: https://docs.foobara.com/all/
2042
2107
  * Per-repository docs: https://foobara.com/docs
2043
2108
 
2044
- # Contributing
2109
+ # Help
2045
2110
 
2046
- Probably a good idea to reach out if you'd like to contribute code or documentation or other
2047
- forms of help. We could pair on what you have in mind and you could drive or at least we can make sure
2048
- it's a good use of time. I can be reached at azimux@gmail.com
2111
+ Are you actually trying to build something with Foobara? Happy to help! miles@foobara.com
2049
2112
 
2050
- You can contribute via a github pull request as is typical
2113
+ # Contributing
2051
2114
 
2052
- Make sure the test suite and linter pass locally before opening a pull request
2115
+ There are many ways you could help! There are tasks in the codebase for all skill levels including absolute beginner.
2116
+ There's also much need for improved documentation. If you're interested in helping but don't know how, then
2117
+ please reach out! miles@foobara.com
2053
2118
 
2054
- The build will fail if test coverage is below 100%
2119
+ You are welcome to also contribute via a github pull request or open issues for bugs.
2120
+ Make sure the test suite and linter pass locally before opening a pull request as
2121
+ the build will fail if test coverage is below 100%.
2055
2122
 
2056
2123
  ## Developing locally
2057
2124
 
@@ -2068,7 +2135,7 @@ And if the tests/linter pass then you could dive into modifying the code
2068
2135
 
2069
2136
  ## Monorepo Structure
2070
2137
 
2071
- Foobara is split up into many projects
2138
+ Foobara is split up into many, many projects
2072
2139
 
2073
2140
  Many are in separate repositories which you can see at: https://github.com/orgs/foobara/repositories
2074
2141
 
@@ -8,7 +8,7 @@ module Foobara
8
8
  module Desugarizers
9
9
  class MoveDefaultsFromElementTypesToRoot < TypeDeclarations::Desugarizer
10
10
  def applicable?(value)
11
- value.is_a?(::Hash) && value[:type] == :attributes
11
+ value.is_a?(::Hash) && value[:type] == :attributes && value.key?(:element_type_declarations)
12
12
  end
13
13
 
14
14
  def desugarize(rawish_type_declaration)
@@ -8,7 +8,7 @@ module Foobara
8
8
  module Desugarizers
9
9
  class MoveRequiredFromElementTypesToRoot < TypeDeclarations::Desugarizer
10
10
  def applicable?(value)
11
- value.is_a?(::Hash) && value[:type] == :attributes
11
+ value.is_a?(::Hash) && value[:type] == :attributes && value.key?(:element_type_declarations)
12
12
  end
13
13
 
14
14
  def desugarize(rawish_type_declaration)
@@ -0,0 +1,32 @@
1
+ module Foobara
2
+ module BuiltinTypes
3
+ module Duck
4
+ module SupportedValidators
5
+ class InstanceOf < TypeDeclarations::Validator
6
+ module TypeDeclarationExtension
7
+ module ExtendRegisteredTypeDeclaration
8
+ module Desugarizers
9
+ class ClassTypeDesugarizer < TypeDeclarations::Desugarizer
10
+ def applicable?(rawish_type_declaration)
11
+ return false unless rawish_type_declaration.is_a?(::Hash)
12
+
13
+ rawish_type_declaration[:type].is_a?(::Class)
14
+ end
15
+
16
+ def desugarize(rawish_type_declaration)
17
+ klass = rawish_type_declaration[:type]
18
+ rawish_type_declaration.merge(type: :duck, instance_of: klass.name)
19
+ end
20
+
21
+ def priority
22
+ Priority::LOWEST + 1
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -14,6 +14,16 @@ module Foobara
14
14
  end
15
15
  end
16
16
 
17
+ class << self
18
+ def requires_parent_declaration_data?
19
+ true
20
+ end
21
+ end
22
+
23
+ def applicable?(value)
24
+ !value.nil? || !parent_declaration_data[:allow_nil]
25
+ end
26
+
17
27
  def expected_class_name
18
28
  declaration_data
19
29
  end
@@ -19,6 +19,8 @@ module Foobara
19
19
 
20
20
  module ClassMethods
21
21
  def define_command_named_function
22
+ return if name.nil?
23
+
22
24
  command_class = self
23
25
  convenience_method_name = Foobara::Util.non_full_name(command_class)
24
26
  containing_module = Foobara::Util.module_for(command_class) || Object
@@ -31,8 +31,6 @@ module Foobara
31
31
  inputs_type&.element_types&.key?(method_name)
32
32
  end
33
33
 
34
- foobara_delegate :inputs_type, to: :class
35
-
36
34
  def cast_and_validate_inputs
37
35
  if inputs_type.nil? && (raw_inputs.nil? || raw_inputs.empty?)
38
36
  @inputs = {}
@@ -67,7 +67,8 @@ module Foobara
67
67
  end
68
68
 
69
69
  def run_execute
70
- result = process_result_using_result_type(execute)
70
+ raw_result = execute
71
+ result = process_result_using_result_type(raw_result)
71
72
  @outcome = Outcome.success(result)
72
73
  end
73
74
 
@@ -65,6 +65,8 @@ module Foobara
65
65
  else
66
66
  superclass.depends_on.dup
67
67
  end
68
+
69
+ return @depends_on
68
70
  end
69
71
 
70
72
  if subcommand_classes.length == 1
@@ -644,5 +644,10 @@ module Foobara
644
644
 
645
645
  command_registry.foobara_all_command(mode: Namespace::LookupMode::ABSOLUTE)
646
646
  end
647
+
648
+ def all_exposed_type_names
649
+ # TODO: cache this or better yet cache #foobara_manifest
650
+ foobara_manifest[:type].keys.sort.map(&:to_s)
651
+ end
647
652
  end
648
653
  end
@@ -578,6 +578,7 @@ module Foobara
578
578
  def run
579
579
  apply_allowed_rule
580
580
  apply_pre_commit_transformers
581
+ set_inputs
581
582
  run_command
582
583
  # this gives us primary keys
583
584
  flush_transactions
@@ -609,6 +610,22 @@ module Foobara
609
610
  end
610
611
  end
611
612
 
613
+ def inputs
614
+ return @inputs if defined?(@inputs)
615
+
616
+ @inputs = if inputs_type
617
+ outcome = inputs_type.process_value(untransformed_inputs)
618
+
619
+ if outcome.success?
620
+ outcome.result
621
+ else
622
+ untransformed_inputs
623
+ end
624
+ else
625
+ {}
626
+ end
627
+ end
628
+
612
629
  def transform_result
613
630
  if self.class.result_transformer
614
631
  self.outcome = Outcome.success(self.class.result_transformer.process_value!(result))
@@ -723,6 +740,14 @@ module Foobara
723
740
  end
724
741
  end
725
742
 
743
+ def set_inputs
744
+ if self.class.inputs_type
745
+ command.after_cast_and_validate_inputs do |**|
746
+ inputs
747
+ end
748
+ end
749
+ end
750
+
726
751
  def run_command
727
752
  outcome = command.run
728
753
  self.outcome = outcome if outcome
@@ -187,7 +187,7 @@ module Foobara
187
187
  construct_associations(element_type, path.append(:"#"), result, initial: false)
188
188
  end
189
189
  elsif type.extends?(BuiltinTypes[:attributes]) # TODO: matches attributes itself instead of only subtypes
190
- type.element_types.each_pair do |attribute_name, element_type|
190
+ type.element_types&.each_pair do |attribute_name, element_type|
191
191
  if remove_sensitive && element_type.sensitive?
192
192
  next
193
193
  end
@@ -89,9 +89,9 @@ module Foobara
89
89
 
90
90
  unless strict
91
91
  if from
92
- lookup_matching_domain_mapper(from: nil, to:)
92
+ lookup_matching_domain_mapper(from: nil, to:, criteria:)
93
93
  elsif to
94
- lookup_matching_domain_mapper(from:, to: nil)
94
+ lookup_matching_domain_mapper(from:, to: nil, criteria:)
95
95
  end
96
96
  end
97
97
  end
@@ -10,6 +10,7 @@ module Foobara
10
10
  type_symbol = strict_type_declaration[:type]
11
11
 
12
12
  return false if type_symbol == expected_type_symbol
13
+ return false unless type_symbol.is_a?(::Symbol) || type_symbol.is_a?(::String)
13
14
 
14
15
  if type_registered?(type_symbol)
15
16
  type = lookup_type!(type_symbol)
@@ -17,6 +17,33 @@ module Foobara
17
17
 
18
18
  entity_classes.uniq
19
19
  end
20
+
21
+ def with_needed_transactions_for_type(type, &)
22
+ relevant_entity_classes = relevant_entity_classes_for_type(type)
23
+
24
+ if relevant_entity_classes.empty?
25
+ return yield
26
+ end
27
+
28
+ tx_class = Class.new
29
+ tx_class.include NestedTransactionable
30
+
31
+ tx_class.define_method(:relevant_entity_classes) do
32
+ relevant_entity_classes
33
+ end
34
+
35
+ tx_instance = tx_class.new
36
+
37
+ begin
38
+ tx_instance.open_transaction
39
+ result = Persistence::EntityBase.using_transactions(tx_instance.transactions, &)
40
+ tx_instance.commit_transaction
41
+ result
42
+ rescue
43
+ tx_instance.rollback_transaction
44
+ raise
45
+ end
46
+ end
20
47
  end
21
48
 
22
49
  def relevant_entity_classes_for_type(type)
@@ -45,7 +72,10 @@ module Foobara
45
72
 
46
73
  bases.each do |base|
47
74
  tx = base.current_transaction
48
- transactions << tx if tx
75
+
76
+ if tx&.open?
77
+ transactions << tx
78
+ end
49
79
  end
50
80
  end
51
81
 
@@ -17,7 +17,8 @@ module Foobara
17
17
  type_symbol = sugary_type_declaration[:type]
18
18
 
19
19
  if [:attributes, "attributes"].include?(type_symbol)
20
- Util.all_symbolizable_keys?(sugary_type_declaration[:element_type_declarations])
20
+ sugary_type_declaration.key?(:element_type_declarations) &&
21
+ Util.all_symbolizable_keys?(sugary_type_declaration[:element_type_declarations])
21
22
  elsif type_symbol.is_a?(::Symbol)
22
23
  # Why is this done?
23
24
  !type_registered?(type_symbol)
@@ -9,7 +9,7 @@ module Foobara
9
9
  def transform(strict_type_declaration)
10
10
  super.tap do |type|
11
11
  type_declarations = type.declaration_data[:element_type_declarations]
12
- type.element_types = type_declarations.transform_values do |attribute_declaration|
12
+ type.element_types = type_declarations&.transform_values do |attribute_declaration|
13
13
  type_for_declaration(attribute_declaration)
14
14
  end
15
15
  end
@@ -13,7 +13,9 @@ module Foobara
13
13
  # we only handle case where it's a builtin type not an extension of one
14
14
  if strict_type_declaration.keys == [:type]
15
15
  type_symbol = strict_type_declaration[:type]
16
- type_registered?(type_symbol)
16
+ if type_symbol.is_a?(::Symbol) || type_symbol.is_a?(::String)
17
+ type_registered?(type_symbol)
18
+ end
17
19
  end
18
20
  end
19
21
 
@@ -35,7 +35,7 @@ module Foobara
35
35
  :element_type
36
36
 
37
37
  def initialize(
38
- *,
38
+ declaration_data,
39
39
  target_classes:,
40
40
  base_type:,
41
41
  description: nil,
@@ -52,10 +52,12 @@ module Foobara
52
52
  sensitive_exposed: nil,
53
53
  **opts
54
54
  )
55
+ self.declaration_data = declaration_data
55
56
  self.sensitive = sensitive
56
57
  self.sensitive_exposed = sensitive_exposed
57
58
  self.base_type = base_type
58
59
  self.description = description
60
+ self.name = name
59
61
  self.casters = [*casters, *base_type&.casters]
60
62
  self.transformers = [*transformers, *base_type&.transformers]
61
63
  self.validators = [*validators, *base_type&.validators]
@@ -65,11 +67,10 @@ module Foobara
65
67
  # TODO: combine these maybe with the term "children_types"?
66
68
  self.element_types = element_types
67
69
  self.element_type = element_type
68
- self.name = name
69
70
  self.target_classes = Util.array(target_classes)
70
71
  self.processor_classes_requiring_type = processor_classes_requiring_type
71
72
 
72
- super(*, **opts.merge(processors:, prioritize: false))
73
+ super(declaration_data, **opts.merge(processors:, prioritize: false))
73
74
 
74
75
  apply_all_processors_needing_type!
75
76
 
@@ -274,8 +275,25 @@ module Foobara
274
275
  end
275
276
 
276
277
  def value_caster
277
- # TODO: how can declaration_data be blank? That seems really strange...
278
- Value::Processor::Casting.new({ cast_to: declaration_data }, casters:, target_classes:)
278
+ # TODO: figure out what would be needed to successfully memoize this
279
+ # return @value_caster if defined?(@value_caster)
280
+
281
+ # We make this exception for :duck because it will match any instance of
282
+ # Object but AllowNil will match nil which is also an instance of Object.
283
+ # This results in two matching casters. Instead of figuring out a way to make one
284
+ # conditional on the other we will just turn off this unique enforcement for :duck
285
+ enforce_unique = if declaration_data.is_a?(::Hash)
286
+ declaration_data[:type] != :duck
287
+ else
288
+ true
289
+ end
290
+
291
+ Value::Processor::Casting.new(
292
+ { cast_to: declaration_data },
293
+ casters:,
294
+ target_classes:,
295
+ enforce_unique:
296
+ )
279
297
  end
280
298
 
281
299
  def applicable?(value)
@@ -39,7 +39,7 @@ module Foobara
39
39
 
40
40
  attr_accessor :target_classes
41
41
 
42
- def initialize(*, casters:, target_classes: nil)
42
+ def initialize(*, casters:, target_classes: nil, **)
43
43
  self.target_classes = Util.array(target_classes)
44
44
 
45
45
  processors = [
@@ -47,7 +47,7 @@ module Foobara
47
47
  *casters
48
48
  ]
49
49
 
50
- super(*, processors:)
50
+ super(*, processors:, **)
51
51
  end
52
52
 
53
53
  def needs_cast?(value)
@@ -113,7 +113,7 @@ module Foobara
113
113
  build_error(*args)
114
114
  elsif error_class == MoreThanOneApplicableProcessorError
115
115
  # :nocov:
116
- raise "Matched too many casters for #{args.inspect} with #{opts.inspect}"
116
+ raise "Matched too many casters for #{args.map(&:inspect).join(",")} with #{opts.inspect}"
117
117
  # :nocov:
118
118
  else
119
119
  super
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.125
4
+ version: 0.0.127
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2025-05-26 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bigdecimal
@@ -116,6 +116,7 @@ files:
116
116
  - projects/builtin_types/src/duck/supported_casters/allow_nil.rb
117
117
  - projects/builtin_types/src/duck/supported_validators/instance_of.rb
118
118
  - projects/builtin_types/src/duck/supported_validators/instance_of/type_declaration_extension/extend_registered_type_declaration/desugarizers/class_desugarizer.rb
119
+ - projects/builtin_types/src/duck/supported_validators/instance_of/type_declaration_extension/extend_registered_type_declaration/desugarizers/class_type_desugarizer.rb
119
120
  - projects/builtin_types/src/duck/supported_validators/instance_of/type_declaration_extension/extend_registered_type_declaration/desugarizers/instance_of_class_desugarizer.rb
120
121
  - projects/builtin_types/src/duck/supported_validators/instance_of/type_declaration_extension/extend_registered_type_declaration/desugarizers/instance_of_symbol_desugarizer.rb
121
122
  - projects/builtin_types/src/duck/supported_validators/instance_of/type_declaration_extension/extend_registered_type_declaration/type_declaration_validators/is_valid_class.rb
@@ -528,7 +529,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
528
529
  - !ruby/object:Gem::Version
529
530
  version: '0'
530
531
  requirements: []
531
- rubygems_version: 3.6.7
532
+ rubygems_version: 3.6.2
532
533
  specification_version: 4
533
534
  summary: A command-centric and discoverable software framework with a focus on domain
534
535
  concepts and abstracting away integration code