foobara 0.0.14 → 0.0.16
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/projects/entity/src/concerns/attribute_helpers.rb +190 -0
- data/projects/entity/src/entity.rb +1 -0
- data/projects/entity/src/extensions/builtin_types/entity/casters/hash.rb +1 -0
- data/projects/model/src/extensions/builtin_types/model/transformers/mutable.rb +13 -5
- data/projects/model/src/model.rb +2 -1
- metadata +3 -3
- data/projects/command/src/command/entity_helpers.rb +0 -184
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a386511b5dd07ad9a067ce02cdf7dfef7436a9db51fe938e26e1dec2c42b836f
|
4
|
+
data.tar.gz: bf3f4960d49e6ca8f9a17ee7f4ceb7d3f04dd433172bc0028d8314081c933049
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7cd69b5365baa7bb77534875bed988c638b2c48d79d28119337adca49c8a2551bd958bd3835da682fc143d57186ba7efbf8a36630fc573d0991ae0c10c4cf6bb
|
7
|
+
data.tar.gz: e00097bd3e762179bd14bfa9e292a2b93c6f063d5dd67b82ec6942d7578af206dd97322a560a7f0e9771bb5b799d2acb34d07e08db4d3f46f6f22ab48f2be642
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## [0.0.16] - 2024-11-22
|
2
|
+
|
3
|
+
- Fix bug where mutable not being set explicitly raises
|
4
|
+
- Also, make it so models don't default to mutable false when processed by a model type
|
5
|
+
|
6
|
+
## [0.0.15] - 2024-11-20
|
7
|
+
|
8
|
+
- Move entity attributes type declaration helpers from Command::EntityHelpers to Entity for convenience.
|
9
|
+
|
1
10
|
## [0.0.14] - 2024-11-15
|
2
11
|
|
3
12
|
- Provide a default execute because why not...
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Foobara
|
2
|
+
class Entity < Model
|
3
|
+
module Concerns
|
4
|
+
module AttributeHelpers
|
5
|
+
include Foobara::Concern
|
6
|
+
|
7
|
+
def update_aggregate(value, type = self.class.model_type)
|
8
|
+
# is this a smell?
|
9
|
+
self.class.update_aggregate(self, value, type)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def attributes_for_update
|
14
|
+
attributes_for_aggregate_update
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: we should have metadata on the entity about whether it required a primary key
|
18
|
+
# upon creation or not instead of an option here.
|
19
|
+
def attributes_for_create(includes_primary_key: false)
|
20
|
+
return attributes_type if includes_primary_key
|
21
|
+
|
22
|
+
declaration = attributes_type.declaration_data
|
23
|
+
# TODO: just slice out the element type declarations
|
24
|
+
declaration = Util.deep_dup(declaration)
|
25
|
+
|
26
|
+
if declaration.key?(:required) && declaration[:required].include?(primary_key_attribute)
|
27
|
+
declaration[:required].delete(primary_key_attribute)
|
28
|
+
end
|
29
|
+
|
30
|
+
if declaration.key?(:defaults) && declaration[:defaults].include?(primary_key_attribute)
|
31
|
+
declaration[:defaults].delete(primary_key_attribute)
|
32
|
+
end
|
33
|
+
|
34
|
+
if declaration.key?(:element_type_declarations)
|
35
|
+
if declaration[:element_type_declarations].key?(primary_key_attribute)
|
36
|
+
declaration[:element_type_declarations].delete(primary_key_attribute)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
handler = Domain.global.foobara_type_builder.handler_for_class(
|
41
|
+
TypeDeclarations::Handlers::ExtendAttributesTypeDeclaration
|
42
|
+
)
|
43
|
+
|
44
|
+
handler.desugarize(declaration)
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes_for_aggregate_update(initial = true)
|
48
|
+
declaration = attributes_type.declaration_data
|
49
|
+
# TODO: just slice out the element type declarations
|
50
|
+
declaration = Util.deep_dup(declaration)
|
51
|
+
|
52
|
+
declaration.delete(:defaults)
|
53
|
+
declaration.delete(:required)
|
54
|
+
|
55
|
+
if initial
|
56
|
+
declaration[:required] = [primary_key_attribute]
|
57
|
+
end
|
58
|
+
|
59
|
+
associations.each_pair do |data_path, type|
|
60
|
+
if type.extends?(BuiltinTypes[:entity])
|
61
|
+
target_class = type.target_class
|
62
|
+
|
63
|
+
entry = type_declaration_value_at(declaration, DataPath.new(data_path).path)
|
64
|
+
entry.clear
|
65
|
+
entry.merge!(target_class.attributes_for_aggregate_update(false))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
declaration
|
70
|
+
end
|
71
|
+
|
72
|
+
def attributes_for_atom_update
|
73
|
+
declaration = attributes_type.declaration_data
|
74
|
+
# TODO: just slice out the element type declarations
|
75
|
+
declaration = Util.deep_dup(declaration)
|
76
|
+
|
77
|
+
declaration.delete(:defaults)
|
78
|
+
declaration[:required] = [primary_key_attribute]
|
79
|
+
|
80
|
+
# expect all associations to be expressed as primary key values
|
81
|
+
# TODO: should we have a special type for encapsulating primary keys types??
|
82
|
+
associations.each_pair do |data_path, type|
|
83
|
+
if type.extends?(BuiltinTypes[:entity])
|
84
|
+
target_class = type.target_class
|
85
|
+
# TODO: do we really need declaration_data? Why cant we use the type directly?
|
86
|
+
# TODO: make this work with the type directly for performance reasons.
|
87
|
+
primary_key_type_declaration = target_class.primary_key_type.declaration_data
|
88
|
+
entry = type_declaration_value_at(declaration, DataPath.new(data_path).path)
|
89
|
+
entry.clear
|
90
|
+
entry.merge!(primary_key_type_declaration)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
declaration
|
95
|
+
end
|
96
|
+
|
97
|
+
def attributes_for_find_by
|
98
|
+
element_type_declarations = {}
|
99
|
+
|
100
|
+
attributes_type.element_types.each_pair do |attribute_name, attribute_type|
|
101
|
+
element_type_declarations[attribute_name] = attribute_type.reference_or_declaration_data
|
102
|
+
end
|
103
|
+
|
104
|
+
handler = Domain.global.foobara_type_builder.handler_for_class(
|
105
|
+
TypeDeclarations::Handlers::ExtendAttributesTypeDeclaration
|
106
|
+
)
|
107
|
+
|
108
|
+
handler.desugarize(
|
109
|
+
type: "::attributes",
|
110
|
+
element_type_declarations:
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_aggregate(object, value, type = object.class.model_type)
|
115
|
+
return value if object.nil?
|
116
|
+
|
117
|
+
if type.extends?(BuiltinTypes[:model])
|
118
|
+
element_types = type.element_types.element_types
|
119
|
+
|
120
|
+
value.each_pair do |attribute_name, new_value|
|
121
|
+
current_value = object.read_attribute(attribute_name)
|
122
|
+
|
123
|
+
attribute_type = element_types[attribute_name]
|
124
|
+
|
125
|
+
updated_value = update_aggregate(current_value, new_value, attribute_type)
|
126
|
+
|
127
|
+
object.write_attribute(attribute_name, updated_value)
|
128
|
+
end
|
129
|
+
|
130
|
+
object
|
131
|
+
elsif type.extends?(BuiltinTypes[:attributes])
|
132
|
+
element_types = type.element_types
|
133
|
+
|
134
|
+
object = object.dup
|
135
|
+
object ||= {}
|
136
|
+
|
137
|
+
value.each_pair do |attribute_name, new_value|
|
138
|
+
current_value = object[attribute_name]
|
139
|
+
attribute_type = element_types[attribute_name]
|
140
|
+
|
141
|
+
updated_value = update_aggregate(current_value, new_value, attribute_type)
|
142
|
+
|
143
|
+
object[attribute_name] = updated_value
|
144
|
+
end
|
145
|
+
|
146
|
+
object
|
147
|
+
elsif type.extends?(BuiltinTypes[:tuple])
|
148
|
+
# :nocov:
|
149
|
+
raise "Tuple not yet supported"
|
150
|
+
# :nocov:
|
151
|
+
elsif type.extends?(BuiltinTypes[:associative_array])
|
152
|
+
# :nocov:
|
153
|
+
raise "Associated array not yet supported"
|
154
|
+
# :nocov:
|
155
|
+
elsif type.extends?(BuiltinTypes[:array])
|
156
|
+
element_type = type.element_type
|
157
|
+
|
158
|
+
value.map.with_index do |element, index|
|
159
|
+
update_aggregate(object[index], element, element_type)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
value
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def type_declaration_value_at(declaration, path_parts)
|
169
|
+
return declaration if path_parts.empty?
|
170
|
+
|
171
|
+
path_part, *path_parts = path_parts
|
172
|
+
|
173
|
+
declaration = case path_part
|
174
|
+
when :"#"
|
175
|
+
declaration[:element_type_declaration]
|
176
|
+
when Symbol, Integer
|
177
|
+
declaration[:element_type_declarations][path_part]
|
178
|
+
else
|
179
|
+
# :nocov:
|
180
|
+
raise "Bad path part #{path_part}"
|
181
|
+
# :nocov:
|
182
|
+
end
|
183
|
+
|
184
|
+
type_declaration_value_at(declaration, path_parts)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -11,11 +11,19 @@ module Foobara
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def transform(record)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
if parent_declaration_data.key?(:mutable)
|
15
|
+
# hmmmm.... can we really just arbitrarily clobber this?
|
16
|
+
# wouldn't that be surprising to calling code that passes in a record/model?
|
17
|
+
# One use-case of this seems to be to reduce the amount of possible errors a command reports
|
18
|
+
# by declaring that only some subset or none of the attributes are mutable.
|
19
|
+
# However, we shouldn't react to this by clobbering the mutable state of the record because it might not
|
20
|
+
# be a fresh record fetched from a primary key it might be an already loaded record/model from some other
|
21
|
+
# context and that context might be surprised to learn that we've clobbered its mutability status.
|
22
|
+
# Solutions?
|
23
|
+
# 1. In the case of models, we could duplicate the model if the mutable value is different.
|
24
|
+
# 2. But what about entities? We almost need some sort of proxy entity that tightens the mutability?
|
25
|
+
record.mutable = parent_declaration_data[:mutable]
|
26
|
+
end
|
19
27
|
|
20
28
|
record
|
21
29
|
end
|
data/projects/model/src/model.rb
CHANGED
@@ -207,6 +207,7 @@ module Foobara
|
|
207
207
|
end
|
208
208
|
end
|
209
209
|
|
210
|
+
# why do we default to true here but false in the transformers?
|
210
211
|
mutable = options.key?(:mutable) ? options[:mutable] : true
|
211
212
|
|
212
213
|
self.mutable = if mutable.is_a?(::Array)
|
@@ -227,7 +228,7 @@ module Foobara
|
|
227
228
|
def write_attribute(attribute_name, value)
|
228
229
|
attribute_name = attribute_name.to_sym
|
229
230
|
|
230
|
-
if mutable == true || mutable
|
231
|
+
if mutable == true || mutable&.include?(attribute_name)
|
231
232
|
outcome = cast_attribute(attribute_name, value)
|
232
233
|
attributes[attribute_name] = outcome.success? ? outcome.result : value
|
233
234
|
else
|
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.
|
4
|
+
version: 0.0.16
|
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-11-
|
11
|
+
date: 2024-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: foobara-util
|
@@ -116,7 +116,6 @@ files:
|
|
116
116
|
- projects/callback/src/set.rb
|
117
117
|
- projects/command/lib/foobara/command.rb
|
118
118
|
- projects/command/src/command.rb
|
119
|
-
- projects/command/src/command/entity_helpers.rb
|
120
119
|
- projects/command/src/concerns/callbacks.rb
|
121
120
|
- projects/command/src/concerns/description.rb
|
122
121
|
- projects/command/src/concerns/domain_mappers.rb
|
@@ -187,6 +186,7 @@ files:
|
|
187
186
|
- projects/domain/src/organization_module_extension.rb
|
188
187
|
- projects/entity/lib/foobara/entity.rb
|
189
188
|
- projects/entity/src/concerns/associations.rb
|
189
|
+
- projects/entity/src/concerns/attribute_helpers.rb
|
190
190
|
- projects/entity/src/concerns/attributes.rb
|
191
191
|
- projects/entity/src/concerns/callbacks.rb
|
192
192
|
- projects/entity/src/concerns/initialization.rb
|
@@ -1,184 +0,0 @@
|
|
1
|
-
module Foobara
|
2
|
-
class Command
|
3
|
-
module EntityHelpers
|
4
|
-
module_function
|
5
|
-
|
6
|
-
# Just for convenience
|
7
|
-
def attributes_for_update(entity_class)
|
8
|
-
type_declaration_for_record_aggregate_update(entity_class)
|
9
|
-
end
|
10
|
-
|
11
|
-
# TODO: we should have metadata on the entity about whether it required a primary key
|
12
|
-
# upon creation or not instead of an option here.
|
13
|
-
def attributes_for_create(entity_class, includes_primary_key: false)
|
14
|
-
attributes_type = entity_class.attributes_type
|
15
|
-
|
16
|
-
return attributes_type if includes_primary_key
|
17
|
-
|
18
|
-
declaration = attributes_type.declaration_data
|
19
|
-
# TODO: just slice out the element type declarations
|
20
|
-
declaration = Util.deep_dup(declaration)
|
21
|
-
|
22
|
-
primary_key_attribute = entity_class.primary_key_attribute
|
23
|
-
|
24
|
-
if declaration.key?(:required) && declaration[:required].include?(primary_key_attribute)
|
25
|
-
declaration[:required].delete(primary_key_attribute)
|
26
|
-
end
|
27
|
-
|
28
|
-
if declaration.key?(:defaults) && declaration[:defaults].include?(primary_key_attribute)
|
29
|
-
declaration[:defaults].delete(primary_key_attribute)
|
30
|
-
end
|
31
|
-
|
32
|
-
if declaration.key?(:element_type_declarations)
|
33
|
-
if declaration[:element_type_declarations].key?(primary_key_attribute)
|
34
|
-
declaration[:element_type_declarations].delete(primary_key_attribute)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
handler = Domain.global.foobara_type_builder.handler_for_class(
|
39
|
-
TypeDeclarations::Handlers::ExtendAttributesTypeDeclaration
|
40
|
-
)
|
41
|
-
|
42
|
-
handler.desugarize(declaration)
|
43
|
-
end
|
44
|
-
|
45
|
-
def type_declaration_for_record_aggregate_update(entity_class, initial = true)
|
46
|
-
declaration = entity_class.attributes_type.declaration_data
|
47
|
-
# TODO: just slice out the element type declarations
|
48
|
-
declaration = Util.deep_dup(declaration)
|
49
|
-
|
50
|
-
declaration.delete(:defaults)
|
51
|
-
declaration.delete(:required)
|
52
|
-
|
53
|
-
if initial
|
54
|
-
declaration[:required] = [entity_class.primary_key_attribute]
|
55
|
-
end
|
56
|
-
|
57
|
-
entity_class.associations.each_pair do |data_path, type|
|
58
|
-
if type.extends?(BuiltinTypes[:entity])
|
59
|
-
target_class = type.target_class
|
60
|
-
|
61
|
-
entry = type_declaration_value_at(declaration, DataPath.new(data_path).path)
|
62
|
-
entry.clear
|
63
|
-
entry.merge!(type_declaration_for_record_aggregate_update(target_class, false))
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
declaration
|
68
|
-
end
|
69
|
-
|
70
|
-
def type_declaration_for_record_atom_update(entity_class)
|
71
|
-
declaration = entity_class.attributes_type.declaration_data
|
72
|
-
# TODO: just slice out the element type declarations
|
73
|
-
declaration = Util.deep_dup(declaration)
|
74
|
-
|
75
|
-
declaration.delete(:defaults)
|
76
|
-
declaration[:required] = [entity_class.primary_key_attribute]
|
77
|
-
|
78
|
-
# expect all associations to be expressed as primary key values
|
79
|
-
# TODO: should we have a special type for encapsulating primary keys types??
|
80
|
-
entity_class.associations.each_pair do |data_path, type|
|
81
|
-
if type.extends?(BuiltinTypes[:entity])
|
82
|
-
target_class = type.target_class
|
83
|
-
# TODO: do we really need declaration_data? Why cant we use the type directly?
|
84
|
-
# TODO: make this work with the type directly for performance reasons.
|
85
|
-
primary_key_type_declaration = target_class.primary_key_type.declaration_data
|
86
|
-
entry = type_declaration_value_at(declaration, DataPath.new(data_path).path)
|
87
|
-
entry.clear
|
88
|
-
entry.merge!(primary_key_type_declaration)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
declaration
|
93
|
-
end
|
94
|
-
|
95
|
-
def type_declaration_for_find_by(entity_class)
|
96
|
-
element_type_declarations = {}
|
97
|
-
|
98
|
-
entity_class.attributes_type.element_types.each_pair do |attribute_name, attribute_type|
|
99
|
-
element_type_declarations[attribute_name] = attribute_type.reference_or_declaration_data
|
100
|
-
end
|
101
|
-
|
102
|
-
handler = Domain.global.foobara_type_builder.handler_for_class(
|
103
|
-
TypeDeclarations::Handlers::ExtendAttributesTypeDeclaration
|
104
|
-
)
|
105
|
-
|
106
|
-
handler.desugarize(
|
107
|
-
type: "::attributes",
|
108
|
-
element_type_declarations:
|
109
|
-
)
|
110
|
-
end
|
111
|
-
|
112
|
-
def update_aggregate(object, value, type = object.class.model_type)
|
113
|
-
return value if object.nil?
|
114
|
-
|
115
|
-
if type.extends?(BuiltinTypes[:model])
|
116
|
-
element_types = type.element_types.element_types
|
117
|
-
|
118
|
-
value.each_pair do |attribute_name, new_value|
|
119
|
-
current_value = object.read_attribute(attribute_name)
|
120
|
-
|
121
|
-
attribute_type = element_types[attribute_name]
|
122
|
-
|
123
|
-
updated_value = update_aggregate(current_value, new_value, attribute_type)
|
124
|
-
|
125
|
-
object.write_attribute(attribute_name, updated_value)
|
126
|
-
end
|
127
|
-
|
128
|
-
object
|
129
|
-
elsif type.extends?(BuiltinTypes[:attributes])
|
130
|
-
element_types = type.element_types
|
131
|
-
|
132
|
-
object = object.dup
|
133
|
-
object ||= {}
|
134
|
-
|
135
|
-
value.each_pair do |attribute_name, new_value|
|
136
|
-
current_value = object[attribute_name]
|
137
|
-
attribute_type = element_types[attribute_name]
|
138
|
-
|
139
|
-
updated_value = update_aggregate(current_value, new_value, attribute_type)
|
140
|
-
|
141
|
-
object[attribute_name] = updated_value
|
142
|
-
end
|
143
|
-
|
144
|
-
object
|
145
|
-
elsif type.extends?(BuiltinTypes[:tuple])
|
146
|
-
# :nocov:
|
147
|
-
raise "Tuple not yet supported"
|
148
|
-
# :nocov:
|
149
|
-
elsif type.extends?(BuiltinTypes[:associative_array])
|
150
|
-
# :nocov:
|
151
|
-
raise "Associated array not yet supported"
|
152
|
-
# :nocov:
|
153
|
-
elsif type.extends?(BuiltinTypes[:array])
|
154
|
-
element_type = type.element_type
|
155
|
-
|
156
|
-
value.map.with_index do |element, index|
|
157
|
-
update_aggregate(object[index], element, element_type)
|
158
|
-
end
|
159
|
-
else
|
160
|
-
value
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def type_declaration_value_at(declaration, path_parts)
|
165
|
-
return declaration if path_parts.empty?
|
166
|
-
|
167
|
-
path_part, *path_parts = path_parts
|
168
|
-
|
169
|
-
declaration = case path_part
|
170
|
-
when :"#"
|
171
|
-
declaration[:element_type_declaration]
|
172
|
-
when Symbol, Integer
|
173
|
-
declaration[:element_type_declarations][path_part]
|
174
|
-
else
|
175
|
-
# :nocov:
|
176
|
-
raise "Bad path part #{path_part}"
|
177
|
-
# :nocov:
|
178
|
-
end
|
179
|
-
|
180
|
-
type_declaration_value_at(declaration, path_parts)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|