foobara 0.0.14 → 0.0.16

Sign up to get free protection for your applications and to get access to all the features.
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