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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38b777d54529187c23d8a7f67337c5cb6f841d06ca01efe3635c003359f38b47
4
- data.tar.gz: 91bbdf579b22f6c2978180142846d8d5f14a7751840cc89b6b06d70248247f33
3
+ metadata.gz: a386511b5dd07ad9a067ce02cdf7dfef7436a9db51fe938e26e1dec2c42b836f
4
+ data.tar.gz: bf3f4960d49e6ca8f9a17ee7f4ceb7d3f04dd433172bc0028d8314081c933049
5
5
  SHA512:
6
- metadata.gz: f06ee9edaf6d45318abe7ad05b1a9932895443eb72a42d8fe25baa0ad5c2b666f1bacc38a14ea1d390bb75f5283ec64ccf77e2f0a3be1cc05b67e95422220371
7
- data.tar.gz: 1facc5ea85dfab04ec0f97d96090c512c75cb676cdebde0ebbed0c981d01d4cc8780d1c2533fb2f74b8322e0c62db0caa81a838ab8d09619458d943985dee065
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
@@ -12,6 +12,7 @@ module Foobara
12
12
  include Concerns::Persistence
13
13
  include Concerns::Initialization
14
14
  include Concerns::Reflection
15
+ include Concerns::AttributeHelpers
15
16
 
16
17
  class << self
17
18
  prepend NewPrepend
@@ -2,6 +2,7 @@ module Foobara
2
2
  module BuiltinTypes
3
3
  module Entity
4
4
  module Casters
5
+ # TODO: We need a way of disabling/enabling this and it should probably be disabled by default.
5
6
  class Hash < Attributes::Casters::Hash
6
7
  class << self
7
8
  def requires_parent_declaration_data?
@@ -11,11 +11,19 @@ module Foobara
11
11
  end
12
12
 
13
13
  def transform(record)
14
- record.mutable = if parent_declaration_data.key?(:mutable)
15
- parent_declaration_data[:mutable]
16
- else
17
- false
18
- end
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
@@ -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.include?(attribute_name)
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.14
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-16 00:00:00.000000000 Z
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