foobara 0.0.36 → 0.0.37

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 796f1d29d3a7ab80b6ae136f254a80549a75ec952ec1219701960abe24bd1256
4
- data.tar.gz: c99fc6d286203521a9242dbc7ed042a27161b12fb213eec3b44c02f4cca26236
3
+ metadata.gz: b44ab7dcd4b578702ab3f310f19a631fc804b06d46c804212b700f1cda1ef98f
4
+ data.tar.gz: e45e7c10ae665a2e4fd3a06c366018466ec26c7b31615ff9b9dca79dac42ea1d
5
5
  SHA512:
6
- metadata.gz: bd63e1dfe5afab9b6b29c855352d3db79a1fa4c04f912c385eeeeb187d2bedbf558fa929d021b276b510ce8d9b62aa4d41da92f8fba7aa6d12e6b752b47d80db
7
- data.tar.gz: 992b76e3ba595df506570c311a93174e9745ed9e9f5af310cd3b56c1b0274b668f4f38bd3344499c11dd352b5e37ab7bf400ef4421e0b9810d774f91290505c7
6
+ metadata.gz: c68ee020e1185a6446d4880a1b8557809a82bcb46cf2ad4309dcf965d850e9803cf2da8669a8d45a4a7cc5be2801756c581f4b9ca1c1696dd521f807c95618cc
7
+ data.tar.gz: 4846196d83db2d9fc9ab0b56e20092eda1abf8c4722a1f7a5dfc1c05c31981696b4f5787708252931c36f127edf745795e4386197810fbc42b55b56fd8ad3c37
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.0.37] - 2024-12-11
2
+
3
+ - Make DomainMappers extend Command so they have possible errors and statefulness/a cleaner API
4
+
1
5
  ## [0.0.36] - 2024-12-10
2
6
 
3
7
  - Fix bug with command-named convenience functions
data/README.md CHANGED
@@ -1518,8 +1518,18 @@ Note that we can automatically map `animal` to CreateCapybara inputs by calling
1518
1518
  Let's play with it:
1519
1519
 
1520
1520
  ```irb
1521
-
1521
+ > basil = FoobaraDemo::CapyCafe::ImportAnimal.run!(animal: { species: :capybara, first_name: "Basil", last_name: "Doe", birthday: "1000-01-01" })
1522
+ ==> <Capybara:3>
1523
+ > basil.age
1524
+ ==> 1024
1525
+ > basil.id
1526
+ ==> 3
1527
+ > FoobaraDemo::CapyCafe::IncrementAge.run!(capybara: basil)
1528
+ ==> <Capybara:3>
1529
+ > FoobaraDemo::CapyCafe::FindCapybara.run!(id: basil).age
1530
+ ==> 1025
1522
1531
  ```
1532
+
1523
1533
  TODO
1524
1534
 
1525
1535
  ### Code Generators
@@ -23,13 +23,13 @@ module Foobara
23
23
  result = run_subcommand!(subcommand_class, inputs)
24
24
 
25
25
  if has_result_type
26
- result_mapper = self.class.domain.foobara_domain_mapper_registry.lookup!(
26
+ result_mapper = self.class.domain.lookup_matching_domain_mapper!(
27
27
  from: result,
28
28
  to: result_type,
29
29
  strict: true
30
30
  )
31
31
 
32
- result = result_mapper.map(result)
32
+ result = result_mapper.map!(result)
33
33
  end
34
34
 
35
35
  result
@@ -14,12 +14,6 @@ module Foobara
14
14
  Namespace.global.foobara_add_category(:organization) { is_a?(Module) && foobara_organization? }
15
15
  Namespace.global.foobara_add_category(:domain) { is_a?(Module) && foobara_domain? }
16
16
  end
17
-
18
- def reset_all
19
- if Foobara::DomainMapper.instance_variable_defined?(:@foobara_domain_mappers_to_process)
20
- Foobara::DomainMapper.remove_instance_variable(:@foobara_domain_mappers_to_process)
21
- end
22
- end
23
17
  end
24
18
  end
25
19
  end
@@ -148,27 +148,19 @@ module Foobara
148
148
  value
149
149
  end
150
150
 
151
- mapper = foobara_domain_mapper_registry.lookup(from:, to:, strict:)
151
+ mapper = lookup_matching_domain_mapper(from:, to:, strict:)
152
152
 
153
- mapper&.call(value)
153
+ mapper&.map!(value)
154
154
  end
155
155
 
156
156
  def foobara_domain_map!(value, from: value, to: nil, strict: false)
157
- mapper = foobara_domain_mapper_registry.lookup(from:, to:, strict:)
157
+ mapper = lookup_matching_domain_mapper(from:, to:, strict:)
158
+
158
159
  unless mapper
159
- raise Foobara::DomainMapper::NoDomainMapperFoundError.new(from, to, value:)
160
+ raise Foobara::DomainMapperLookups::NoDomainMapperFoundError.new(from, to, value:)
160
161
  end
161
162
 
162
- mapper.call(value)
163
- end
164
-
165
- def foobara_domain_mapper(mapper)
166
- foobara_domain_mapper_registry(skip_check: true).register(mapper)
167
- end
168
-
169
- def foobara_domain_mapper_registry(skip_check: false)
170
- Foobara::DomainMapper.foobara_process_domain_mappers unless skip_check
171
- @foobara_domain_mapper_registry ||= DomainMapper::Registry.new
163
+ mapper.map!(value)
172
164
  end
173
165
 
174
166
  def foobara_domain_name
@@ -0,0 +1,10 @@
1
+ module Foobara
2
+ class DomainMapper < Foobara::Command
3
+ class << self
4
+ def install!
5
+ Namespace.global.foobara_add_category_for_subclass_of(:domain_mapper, self)
6
+ Domain::DomainModuleExtension.include Foobara::DomainMapperLookups
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,106 @@
1
+ module Foobara
2
+ # TODO: would be nice to inherit from something other than Command. Can we hoist a bunch of common behavior up
3
+ # out of Command into some other class maybe called Service or Runnable or something?
4
+ class DomainMapper < Foobara::Command
5
+ class << self
6
+ def map(value)
7
+ new(from: value).run
8
+ end
9
+
10
+ def map!(value)
11
+ new(from: value).run!
12
+ end
13
+
14
+ # A bit hacky because Command only supports attributes inputs at the moment, ugg.
15
+ def from(...)
16
+ from_type = args_to_type(...)
17
+
18
+ inputs do
19
+ from from_type, :required
20
+ end
21
+ end
22
+
23
+ def to(...)
24
+ result_type = args_to_type(...)
25
+ result(result_type)
26
+ end
27
+
28
+ def from_type
29
+ inputs_type.element_types[:from]
30
+ end
31
+
32
+ def to_type
33
+ result_type
34
+ end
35
+
36
+ def applicable?(from_value, to_value)
37
+ matches?(from_type, from_value) && matches?(to_type, to_value)
38
+ end
39
+
40
+ def matches?(type_indicator, value)
41
+ return true if type_indicator.nil? || value.nil? || type_indicator == value
42
+
43
+ type = object_to_type(type_indicator)
44
+
45
+ return true if type.nil? || type == value
46
+ return true if type.applicable?(value) && type.process_value(value).success?
47
+
48
+ if value.is_a?(Types::Type)
49
+ if !value.registered? && !type.registered?
50
+ value.declaration_data == type.declaration_data
51
+ end
52
+ else
53
+ value_type = object_to_type(value)
54
+
55
+ if value_type
56
+ matches?(type, value_type)
57
+ end
58
+ end
59
+ end
60
+
61
+ # TODO: should this be somewhere more general-purpose?
62
+ def args_to_type(*args, **opts, &block)
63
+ if args.size == 1 && opts.empty? && block.nil?
64
+ object_to_type(args.first)
65
+ else
66
+ domain.foobara_type_from_declaration(*args, **opts, &block)
67
+ end
68
+ end
69
+
70
+ def object_to_type(object)
71
+ if object
72
+ if object.is_a?(::Class)
73
+ if object < Foobara::Model
74
+ object.model_type
75
+ elsif object < Foobara::Command
76
+ object.inputs_type
77
+ else
78
+ domain.foobara_type_from_declaration(object)
79
+ end
80
+ else
81
+ case object
82
+ when Types::Type
83
+ object
84
+ when ::Symbol
85
+ domain.foobara_lookup_type!(object)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ def execute
93
+ map
94
+ end
95
+
96
+ def from
97
+ inputs[:from]
98
+ end
99
+
100
+ def map
101
+ # :nocov:
102
+ raise "subclass responsibility"
103
+ # :nocov:
104
+ end
105
+ end
106
+ end
@@ -1,5 +1,5 @@
1
1
  module Foobara
2
- class DomainMapper
2
+ module DomainMapperLookups
3
3
  class NoDomainMapperFoundError < StandardError
4
4
  attr_accessor :value, :from, :to, :has_value
5
5
 
@@ -25,30 +25,28 @@ module Foobara
25
25
  end
26
26
  end
27
27
 
28
- class Registry
29
- class AmbiguousDomainMapperError < StandardError
30
- attr_accessor :candidates, :from, :to
28
+ class AmbiguousDomainMapperError < StandardError
29
+ attr_accessor :candidates, :from, :to
31
30
 
32
- def initialize(from, to, candidates)
33
- self.to = to
34
- self.from = from
35
- self.candidates = [*candidates].flatten
31
+ def initialize(from, to, candidates)
32
+ self.to = to
33
+ self.from = from
34
+ self.candidates = [*candidates].flatten
36
35
 
37
- super("#{candidates.size} ambiguous candidates found.")
38
- end
36
+ super("#{candidates.size} ambiguous candidates found.")
39
37
  end
38
+ end
40
39
 
41
- def register(mapper)
42
- mappers << mapper
43
- end
40
+ include Concern
44
41
 
45
- def lookup!(from: nil, to: nil, strict: false)
46
- result = lookup(from:, to:, strict:)
42
+ module ClassMethods
43
+ def lookup_matching_domain_mapper!(from: nil, to: nil, strict: false)
44
+ result = lookup_matching_domain_mapper(from:, to:, strict:)
47
45
 
48
46
  result || raise(NoDomainMapperFoundError.new(from, to))
49
47
  end
50
48
 
51
- def lookup(from: nil, to: nil, strict: false)
49
+ def lookup_matching_domain_mapper(from: nil, to: nil, strict: false)
52
50
  candidates = mappers.select do |mapper|
53
51
  mapper.applicable?(from, to)
54
52
  end
@@ -63,15 +61,15 @@ module Foobara
63
61
 
64
62
  unless strict
65
63
  if from
66
- lookup(from: nil, to:)
64
+ lookup_matching_domain_mapper(from: nil, to:)
67
65
  elsif to
68
- lookup(from:, to: nil)
66
+ lookup_matching_domain_mapper(from:, to: nil)
69
67
  end
70
68
  end
71
69
  end
72
70
 
73
71
  def mappers
74
- @mappers ||= []
72
+ @mappers ||= foobara_all_domain_mapper
75
73
  end
76
74
  end
77
75
  end
@@ -18,12 +18,11 @@ module Foobara
18
18
  "state_machine",
19
19
  "namespace"
20
20
 
21
- project "domain"
22
-
23
21
  # various components of the foobara framework that have some level of coupling.
24
22
  # for example, Error in common knows about (or could be implemented to know about)
25
23
  # type declarations to expose its context type.
26
- projects "common",
24
+ projects "domain",
25
+ "common",
27
26
  "value",
28
27
  "types",
29
28
  "type_declarations",
@@ -32,6 +31,7 @@ module Foobara
32
31
  "detached_entity",
33
32
  "entity",
34
33
  "command",
34
+ "domain_mapper",
35
35
  "persistence",
36
36
  "in_memory_crud_driver_minimal",
37
37
  "in_memory_crud_driver",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.36
4
+ version: 0.0.37
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-10 00:00:00.000000000 Z
11
+ date: 2024-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: foobara-util
@@ -193,8 +193,6 @@ files:
193
193
  - projects/detached_entity/src/extensions/type_declarations/handlers/extend_detached_entity_type_declaration/validate_primary_key_references_attribute.rb
194
194
  - projects/domain/lib/foobara/domain.rb
195
195
  - projects/domain/src/domain.rb
196
- - projects/domain/src/domain_mapper.rb
197
- - projects/domain/src/domain_mapper/registry.rb
198
196
  - projects/domain/src/domain_module_extension.rb
199
197
  - projects/domain/src/extensions/foobara.rb
200
198
  - projects/domain/src/global_domain.rb
@@ -204,6 +202,9 @@ files:
204
202
  - projects/domain/src/module_extension.rb
205
203
  - projects/domain/src/organization.rb
206
204
  - projects/domain/src/organization_module_extension.rb
205
+ - projects/domain_mapper/lib/foobara/domain_mapper.rb
206
+ - projects/domain_mapper/src/domain_mapper.rb
207
+ - projects/domain_mapper/src/domain_mapper_lookups.rb
207
208
  - projects/entity/lib/foobara/entity.rb
208
209
  - projects/entity/src/concerns/attribute_helpers.rb
209
210
  - projects/entity/src/concerns/attributes.rb
@@ -393,6 +394,7 @@ require_paths:
393
394
  - "./projects/delegate/lib"
394
395
  - "./projects/detached_entity/lib"
395
396
  - "./projects/domain/lib"
397
+ - "./projects/domain_mapper/lib"
396
398
  - "./projects/entity/lib"
397
399
  - "./projects/enumerated/lib"
398
400
  - "./projects/foobara/lib"
@@ -1,166 +0,0 @@
1
- module Foobara
2
- class DomainMapper
3
- class << self
4
- def call(value)
5
- instance.call(value)
6
- end
7
-
8
- def from(*args)
9
- if args.empty?
10
- @from
11
- else
12
- if args.size > 1
13
- # :nocov:
14
- raise ArgumentError, "only one argument allowed"
15
- # :nocov:
16
- end
17
-
18
- @from = args.first
19
- end
20
- end
21
-
22
- def to(*args)
23
- if args.empty?
24
- @to
25
- else
26
- if args.size > 1
27
- # :nocov:
28
- raise ArgumentError, "only one argument allowed"
29
- # :nocov:
30
- end
31
-
32
- @to = args.first
33
- end
34
- end
35
-
36
- def from_type
37
- return @from_type if defined?(@from_type)
38
-
39
- @from_type = object_to_type(from)
40
- end
41
-
42
- def to_type
43
- return @to_type if defined?(@to_type)
44
-
45
- @to_type = object_to_type(to)
46
- end
47
-
48
- def instance
49
- @instance ||= new
50
- end
51
-
52
- def inherited(subclass)
53
- foobara_domain_mapper_to_process(subclass.instance)
54
-
55
- super
56
- end
57
-
58
- def foobara_domain_mapper_to_process(mapper)
59
- foobara_domain_mappers_to_process << mapper
60
- end
61
-
62
- def foobara_domain_mappers_to_process
63
- @foobara_domain_mappers_to_process ||= []
64
- end
65
-
66
- def foobara_process_domain_mappers
67
- if defined?(@foobara_domain_mappers_to_process)
68
- @foobara_domain_mappers_to_process.each do |mapper|
69
- mapper.domain.foobara_domain_mapper(mapper)
70
- end
71
- remove_instance_variable(:@foobara_domain_mappers_to_process)
72
- end
73
- end
74
-
75
- def domain
76
- candidate = self
77
-
78
- loop do
79
- candidate = Util.module_for(candidate)
80
-
81
- if candidate.nil?
82
- # :nocov:
83
- raise "Domain mapper must be scoped within a domain but #{self.class.name} is not in a domain"
84
- # :nocov:
85
- elsif candidate.foobara_domain?
86
- return candidate
87
- end
88
- end
89
- end
90
-
91
- def matches?(type_indicator, value)
92
- return true if type_indicator.nil? || value.nil? || type_indicator == value
93
-
94
- type = object_to_type(type_indicator)
95
-
96
- return true if type.nil? || type == value
97
- return true if type.applicable?(value) && type.process_value(value).success?
98
-
99
- if value.is_a?(Types::Type)
100
- if !value.registered? && !type.registered?
101
- value.declaration_data == type.declaration_data
102
- end
103
- else
104
- value_type = object_to_type(value)
105
-
106
- if value_type
107
- matches?(type, value_type)
108
- end
109
- end
110
- end
111
-
112
- def object_to_type(object)
113
- if object
114
- if object.is_a?(::Class)
115
- if object < Foobara::Model
116
- object.model_type
117
- elsif object < Foobara::Command
118
- object.inputs_type
119
- else
120
- domain.foobara_type_from_declaration(object)
121
- end
122
- else
123
- case object
124
- when Types::Type
125
- object
126
- when ::Symbol
127
- domain.foobara_lookup_type!(object)
128
- end
129
- end
130
- end
131
- end
132
- end
133
-
134
- def from_type
135
- self.class.from_type
136
- end
137
-
138
- def to_type
139
- self.class.to_type
140
- end
141
-
142
- def call(from_value)
143
- from_value = from_type.process_value!(from_value)
144
-
145
- mapped_value = map(from_value)
146
-
147
- to_type.process_value!(mapped_value)
148
- end
149
-
150
- # TODO: can we make _from_value passed to #initialize instead? That way it doesn't have to be passed around
151
- # between various helper methods
152
- def map(_from_value)
153
- # :nocov:
154
- raise "subclass repsonsibility"
155
- # :nocov:
156
- end
157
-
158
- def applicable?(from_value, to_value)
159
- self.class.matches?(from_type, from_value) && self.class.matches?(to_type, to_value)
160
- end
161
-
162
- def domain
163
- self.class.domain
164
- end
165
- end
166
- end