foobara 0.0.101 → 0.0.103

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: 72936127f925aeecd5c813edff203628a88e65408a1fb4f9d0b68657d664fd1b
4
- data.tar.gz: aec6b40247986426ff94202cbc00057b37476e5e95652f8ac2d58c99647b50bf
3
+ metadata.gz: 506c445dbdf8c1bd6d1a2db990e317e3a8893754aea2f1d607aa03dccc516749
4
+ data.tar.gz: e7170ed694b6bc096391a98e724080bfeab36b3b2528bd49ad105a1e1f3453fe
5
5
  SHA512:
6
- metadata.gz: adba03dad2ca90ca4202cf6e67e67c335748958f856d4fa75ef2bc3545b5e31a0acf5ef84002312252fd7cc3e113daf1fb5ec54308843902c1418a39ad4ea33b
7
- data.tar.gz: 0bee2126b8262f17b673c0f6b0158cdd37b80bb27ba4c42d9a09abf584a0aa900bfbc55dfd1e58cc261aa6c98898e7f4c2bef2a138f51a0f754fe85a9c8ca129
6
+ metadata.gz: 5f5350c1728e42deb835e3d4d32b9ffcc7e5ae56cb4054a0738b9932c82d87d0fb6c608954f5bc0f30b55ecb7f688e6da46af364a80f40ed07c152f0c9ae6e50
7
+ data.tar.gz: fbadbfb1a7a086101a2b0e02fefb06e15ad12954c816566fadd6b17728a0b27db140d3e737617ace5fd3e80cb56b57752e50caac8d7063ac61a22b13bc44a48c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ # [0.0.103] - 2025-04-17
2
+
3
+ - Fix bugs in complicated entity query calls involving mixtures of models/records/primary keys/attributes
4
+ - Fix bugs re: .construct_associations/_deep_associations resulting in terribad performance in some projects
5
+ - Allow lambdas to be used as allowed rules
6
+ - Improve .delegate_attribute interface
7
+
8
+ # [0.0.102] - 2025-04-13
9
+
10
+ - Extract ThreadParent to its own repository/gem
11
+
1
12
  # [0.0.101] - 2025-04-12
2
13
 
3
14
  - Add Entity#update
@@ -1,3 +1,5 @@
1
+ require "inheritable_thread_vars"
2
+
1
3
  module Foobara
2
4
  module BuiltinTypes
3
5
  module Attributes
@@ -10,7 +12,8 @@ module Foobara
10
12
  end
11
13
 
12
14
  def applicable?(hash)
13
- Thread.foobara_var_get(:foobara_ignore_unexpected_attributes) && unexpected_attributes(hash).any?
15
+ Thread.inheritable_thread_local_var_get(:foobara_ignore_unexpected_attributes) &&
16
+ unexpected_attributes(hash).any?
14
17
  end
15
18
 
16
19
  def transform(hash)
@@ -35,6 +35,10 @@ module Foobara
35
35
  entity_classes += Entity.construct_associations(
36
36
  result_type
37
37
  ).values.uniq.map(&:target_class)
38
+
39
+ if result_type.extends?(BuiltinTypes[:entity])
40
+ entity_classes << result_type.target_class
41
+ end
38
42
  end
39
43
 
40
44
  entity_classes += entity_classes.uniq.map do |entity_class|
@@ -556,9 +556,7 @@ module Foobara
556
556
 
557
557
  if rule
558
558
  command.after_load_records do |command:, **|
559
- # NOTE: apparently no way to convert a lambda to a proc but lambda's won't work here...
560
- # TODO: raise exception here if rule.lambda? is true, if this starts becoming a common error
561
- is_allowed = instance_eval(&rule)
559
+ is_allowed = instance_exec(&rule)
562
560
 
563
561
  unless is_allowed
564
562
  explanation = allowed_rule.explanation
@@ -568,7 +566,18 @@ module Foobara
568
566
  end
569
567
 
570
568
  if explanation.nil?
571
- explanation = allowed_rule.block.source || "No explanation."
569
+ source = begin
570
+ allowed_rule.block.source
571
+ rescue MethodSource::SourceNotFoundError
572
+ # This path is hit if the way the source code is extracted
573
+ # doesn't result in valid Ruby, for example, as part of a hash such as:
574
+ # allowed_rule: -> () { whatever?(something) },
575
+ # :nocov:
576
+ allowed_rule.block.source_location.join(":")
577
+ # :nocov:
578
+ end
579
+
580
+ explanation = source || "No explanation."
572
581
  end
573
582
 
574
583
  error = CommandConnector::NotAllowedError.new(rule_symbol: rule.symbol, explanation:)
@@ -374,7 +374,7 @@ module Foobara
374
374
  request_command = request.command
375
375
 
376
376
  request_command.after_load_records do |command:, **|
377
- authenticated_user = request.instance_eval(&authenticator)
377
+ authenticated_user = request.instance_exec(&authenticator)
378
378
 
379
379
  request_command.authenticated_user = authenticated_user
380
380
 
@@ -154,8 +154,13 @@ module Foobara
154
154
  def construct_associations(
155
155
  type = attributes_type,
156
156
  path = DataPath.new,
157
- result = {}
157
+ result = {},
158
+ initial: true
158
159
  )
160
+ if initial && type.extends?(BuiltinTypes[:detached_entity])
161
+ return construct_associations(type.element_types, path, result, initial: false)
162
+ end
163
+
159
164
  remove_sensitive = TypeDeclarations.foobara_manifest_context_remove_sensitive?
160
165
 
161
166
  if type.extends?(BuiltinTypes[:entity])
@@ -171,7 +176,7 @@ module Foobara
171
176
  end
172
177
 
173
178
  element_types&.each&.with_index do |element_type, index|
174
- construct_associations(element_type, path.append(index), result)
179
+ construct_associations(element_type, path.append(index), result, initial: false)
175
180
  end
176
181
  elsif type.extends?(BuiltinTypes[:array])
177
182
  # TODO: what to do about an associative array type?? Unclear how to make a key from that...
@@ -179,7 +184,7 @@ module Foobara
179
184
  element_type = type.element_type
180
185
 
181
186
  if element_type && (!remove_sensitive || !element_type.sensitive?)
182
- construct_associations(element_type, path.append(:"#"), result)
187
+ construct_associations(element_type, path.append(:"#"), result, initial: false)
183
188
  end
184
189
  elsif type.extends?(BuiltinTypes[:attributes]) # TODO: matches attributes itself instead of only subtypes
185
190
  type.element_types.each_pair do |attribute_name, element_type|
@@ -187,7 +192,7 @@ module Foobara
187
192
  next
188
193
  end
189
194
 
190
- construct_associations(element_type, path.append(attribute_name), result)
195
+ construct_associations(element_type, path.append(attribute_name), result, initial: false)
191
196
  end
192
197
  elsif type.extends?(BuiltinTypes[:model])
193
198
  target_class = type.target_class
@@ -196,7 +201,7 @@ module Foobara
196
201
  attributes_type = target_class.send(method)
197
202
 
198
203
  if !remove_sensitive || !attributes_type.sensitive?
199
- construct_associations(attributes_type, path, result)
204
+ construct_associations(attributes_type, path, result, initial: false)
200
205
  end
201
206
  elsif type.extends?(BuiltinTypes[:associative_array])
202
207
  # not going to bother testing this for now
@@ -9,9 +9,8 @@ module Foobara
9
9
 
10
10
  module Monorepo
11
11
  # could be independent projects
12
- projects "delegate",
12
+ projects "delegate", # Let's just kill delegate
13
13
  "concerns",
14
- "thread_parent",
15
14
  "weak_object_set",
16
15
  "enumerated",
17
16
  "callback",
@@ -173,11 +173,16 @@ module Foobara
173
173
 
174
174
  def delegate_attributes(delegates)
175
175
  delegates.each_pair do |attribute_name, delegate_info|
176
- delegate_attribute(attribute_name, delegate_info[:data_path], writer: delegate_info[:writer])
176
+ data_path = DataPath.for(delegate_info[:data_path])
177
+ delegate_attribute(attribute_name, data_path, writer: delegate_info[:writer])
177
178
  end
178
179
  end
179
180
 
180
181
  def delegate_attribute(attribute_name, data_path, writer: false)
182
+ if data_path.is_a?(::Symbol) || data_path.is_a?(::String)
183
+ data_path = [data_path, attribute_name]
184
+ end
185
+
181
186
  data_path = DataPath.for(data_path)
182
187
 
183
188
  delegate_manifest = { data_path: data_path.to_s }
@@ -1,3 +1,5 @@
1
+ require "inheritable_thread_vars"
2
+
1
3
  module Foobara
2
4
  # TODO: either make this an abstract base class of ValueModel and Entity or rename it to ValueModel
3
5
  # and have Entity inherit from it...
@@ -180,7 +182,7 @@ module Foobara
180
182
  end
181
183
 
182
184
  if options[:ignore_unexpected_attributes]
183
- Thread.foobara_with_var(:foobara_ignore_unexpected_attributes, true) do
185
+ Thread.with_inheritable_thread_local_var(:foobara_ignore_unexpected_attributes, true) do
184
186
  initialize(attributes, options.except(:ignore_unexpected_attributes))
185
187
  return
186
188
  end
@@ -195,7 +197,7 @@ module Foobara
195
197
  # :nocov:
196
198
  end
197
199
  else
198
- if Thread.foobara_var_get(:foobara_ignore_unexpected_attributes)
200
+ if Thread.inheritable_thread_local_var_get(:foobara_ignore_unexpected_attributes)
199
201
  outcome = attributes_type.process_value(attributes)
200
202
 
201
203
  if outcome.success?
@@ -1,4 +1,5 @@
1
1
  module Foobara
2
+ # Might be best to rename this to CrudDrivers or CrudDriver instead of Persistence?
2
3
  module Persistence
3
4
  class EntityAttributesCrudDriver
4
5
  attr_accessor :raw_connection, :tables
@@ -156,29 +157,39 @@ module Foobara
156
157
 
157
158
  def matches_attributes_filter?(attributes, attributes_filter)
158
159
  attributes_filter.all? do |attribute_name_or_path, value|
159
- type = nil
160
+ value = normalize_attribute_filter_value(value)
160
161
 
161
162
  if attribute_name_or_path.is_a?(::Array)
162
163
  values = DataPath.values_at(attribute_name_or_path, attributes)
163
164
 
164
- if values.include?(value)
165
- true
166
- else
167
- type ||= entity_class.model_type.type_at_path(attribute_name_or_path)
168
- if type.extends?(:detached_entity)
169
- values.any? do |v|
170
- value.primary_key == v
171
- end
172
- end
165
+ values.any? do |attribute_value|
166
+ normalize_attribute_filter_value(attribute_value) == value
173
167
  end
174
- elsif attributes[attribute_name_or_path] == value
175
- true
176
168
  else
177
- type ||= entity_class.model_type.type_at_path(attribute_name_or_path)
178
- if type.extends?(:detached_entity)
179
- value.primary_key == attributes[attribute_name_or_path]
180
- end
169
+ attribute_value = attributes[attribute_name_or_path]
170
+ normalize_attribute_filter_value(attribute_value) == value
171
+ end
172
+ end
173
+ end
174
+
175
+ def normalize_attribute_filter_value(value)
176
+ case value
177
+ when ::Array
178
+ value.map { |v| normalize_attribute_filter_value(v) }
179
+ when ::Hash
180
+ value.to_h do |k, v|
181
+ [normalize_attribute_filter_value(k), normalize_attribute_filter_value(v)]
182
+ end
183
+ when DetachedEntity
184
+ if value.persisted?
185
+ normalize_attribute_filter_value(value.primary_key)
186
+ else
187
+ value
181
188
  end
189
+ when Model
190
+ normalize_attribute_filter_value(value.attributes)
191
+ else
192
+ value
182
193
  end
183
194
  end
184
195
 
@@ -258,8 +258,6 @@ module Foobara
258
258
  [attribute_name, element_types[attribute_name].process_value!(value)]
259
259
  end
260
260
 
261
- attributes_filter = to_persistable(attributes_filter, false)
262
-
263
261
  tracked_records.each do |record|
264
262
  next if hard_deleted?(record)
265
263
 
@@ -1,3 +1,5 @@
1
+ require "inheritable_thread_vars"
2
+
1
3
  module Foobara
2
4
  module Persistence
3
5
  class EntityBase
@@ -43,11 +45,11 @@ module Foobara
43
45
  end
44
46
 
45
47
  def current_transaction
46
- Thread.foobara_var_get(transaction_key)
48
+ Thread.inheritable_thread_local_var_get(transaction_key)
47
49
  end
48
50
 
49
51
  def set_current_transaction(transaction)
50
- Thread.foobara_var_set(transaction_key, transaction)
52
+ Thread.inheritable_thread_local_var_set(transaction_key, transaction)
51
53
  end
52
54
 
53
55
  VALID_MODES = [:use_existing, :open_nested, :open_new, nil].freeze
@@ -1,3 +1,5 @@
1
+ require "inheritable_thread_vars"
2
+
1
3
  module Foobara
2
4
  require_project_file("type_declarations", "type_builder")
3
5
  require_project_file("type_declarations", "error_extension")
@@ -91,7 +93,7 @@ module Foobara
91
93
  end
92
94
 
93
95
  def foobara_manifest_context
94
- Thread.foobara_var_get("foobara_manifest_context")
96
+ Thread.inheritable_thread_local_var_get("foobara_manifest_context")
95
97
  end
96
98
 
97
99
  allowed_context_keys = %i[detached to_include mode remove_sensitive]
@@ -117,10 +119,10 @@ module Foobara
117
119
  old_context = foobara_manifest_context
118
120
  begin
119
121
  new_context = (old_context || {}).merge(context)
120
- Thread.foobara_var_set("foobara_manifest_context", new_context)
122
+ Thread.inheritable_thread_local_var_set("foobara_manifest_context", new_context)
121
123
  yield
122
124
  ensure
123
- Thread.foobara_var_set("foobara_manifest_context", old_context)
125
+ Thread.inheritable_thread_local_var_set("foobara_manifest_context", old_context)
124
126
  end
125
127
  end
126
128
  end
@@ -20,8 +20,6 @@ module Foobara
20
20
  :validators,
21
21
  :element_processors,
22
22
  :structure_count,
23
- :element_types,
24
- :element_type,
25
23
  :is_builtin,
26
24
  :raw_declaration_data,
27
25
  :name,
@@ -33,6 +31,9 @@ module Foobara
33
31
 
34
32
  attr_reader :type_symbol
35
33
 
34
+ attr_writer :element_types,
35
+ :element_type
36
+
36
37
  def initialize(
37
38
  *,
38
39
  target_classes:,
@@ -83,6 +84,14 @@ module Foobara
83
84
  sensitive_exposed
84
85
  end
85
86
 
87
+ def element_type
88
+ @element_type || base_type&.element_type
89
+ end
90
+
91
+ def element_types
92
+ @element_types || base_type&.element_types
93
+ end
94
+
86
95
  def has_sensitive_types?
87
96
  return true if sensitive?
88
97
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.101
4
+ version: 0.0.103
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: 0.0.11
54
+ - !ruby/object:Gem::Dependency
55
+ name: inheritable-thread-vars
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.0.1
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.0.1
54
68
  description: A command-centric and discoverable software framework with a focus on
55
69
  domain concepts and abstracting away integration code
56
70
  email:
@@ -364,8 +378,6 @@ files:
364
378
  - projects/state_machine/src/sugar.rb
365
379
  - projects/state_machine/src/transition_log.rb
366
380
  - projects/state_machine/src/validations.rb
367
- - projects/thread_parent/lib/foobara/thread_parent.rb
368
- - projects/thread_parent/src/thread_parent.rb
369
381
  - projects/type_declarations/lib/foobara/type_declarations.rb
370
382
  - projects/type_declarations/src/attributes.rb
371
383
  - projects/type_declarations/src/attributes_transformers/only.rb
@@ -471,7 +483,6 @@ require_paths:
471
483
  - "./projects/namespace/lib"
472
484
  - "./projects/persistence/lib"
473
485
  - "./projects/state_machine/lib"
474
- - "./projects/thread_parent/lib"
475
486
  - "./projects/type_declarations/lib"
476
487
  - "./projects/types/lib"
477
488
  - "./projects/value/lib"
@@ -1 +0,0 @@
1
- module Foobara::ThreadParent; end
@@ -1,53 +0,0 @@
1
- module Foobara
2
- module ThreadParent
3
- module ThreadClassExtensions
4
- def new(...)
5
- super.tap { |thread| thread.instance_variable_set("@foobara_parent", Thread.current) }
6
- end
7
- end
8
- end
9
- end
10
-
11
- class Thread
12
- class << self
13
- prepend(Foobara::ThreadParent::ThreadClassExtensions)
14
-
15
- def foobara_var_get(...)
16
- Thread.current.foobara_var_get(...)
17
- end
18
-
19
- def foobara_var_set(...)
20
- Thread.current.foobara_var_set(...)
21
- end
22
-
23
- def foobara_with_var(...)
24
- Thread.current.foobara_with_var(...)
25
- end
26
- end
27
-
28
- attr_reader :foobara_parent
29
-
30
- # NOTE: because there's not a way to unset a thread variable, storing nil is used as deletion.
31
- # this means that a thread local variable with nil can't have any semantic meaning and should be
32
- # treated the same as if #thread_variable? had returned false.
33
- def foobara_var_get(...)
34
- value = thread_variable_get(...)
35
-
36
- value.nil? ? foobara_parent&.foobara_var_get(...) : value
37
- end
38
-
39
- def foobara_var_set(...)
40
- thread_variable_set(...)
41
- end
42
-
43
- def foobara_with_var(key, value, &block)
44
- old_value = foobara_var_get(key)
45
-
46
- begin
47
- foobara_var_set(key, value)
48
- block.call
49
- ensure
50
- foobara_var_set(key, old_value)
51
- end
52
- end
53
- end