activeitem 0.0.21 → 0.0.22

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: 976cb7f41429d4ca2489b0a67a87f39ca3720bc223b02ad9c1d4e2e244f6a700
4
- data.tar.gz: e5c0fd6be8a2ea1ccfc00793942d91973e53b9d434fec9b67e92b8da9660049d
3
+ metadata.gz: a7d5990fc9c0532dc9a65f803bd9886c1769dc2b4c3071fd4748c079fa7559b3
4
+ data.tar.gz: 1c480246c6a898502eacedda1b456e003f54910a3f8562893cc5c070c06f168e
5
5
  SHA512:
6
- metadata.gz: de5d8d0d4f7b6a986369e0604eec719dd686537009967b00302543abc507db28298b65222cb85e5fb22f9ee7e86568ec4934dc2f761956b6f880d89a74ad5456
7
- data.tar.gz: 160ca85fad18fcd70b5650f274077c2a8fc841f6943b6d601a1effc13408c74ce037593c25dc6a58b4aa0a04f465b1616159719416d710e77fd10e1a2a134737
6
+ metadata.gz: db67b991fe6c8c9ed9ce18a362dc15dfd9f1b46c63ed20a49e8b1f82bf0bb2dfe3738b1e2f7d7b29a31396e61d40ee3e6d528393b01a93a92bf81c2e9d9f234b
7
+ data.tar.gz: dc5a3c76df184f02bf6fa4f0cd040e9a98e42729326f35243b198fc2d7c8ae4e4a86429bdc457be7f7e9198892ba93aaddf5a4d5650806afcba6cb528d6d54e7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.22
4
+
5
+ ### Changed
6
+
7
+ - **Scoped `find` on associations** — `Relation#find(id)` now enforces association scope, matching Rails behavior. When called on an association (e.g., `@project.epics.find(id)`), it validates that the found record belongs to the association before returning it. If the record exists but belongs to a different parent, `ActiveItem::RecordNotFound` is raised. This prevents accidentally accessing records outside the association scope.
8
+
9
+ ```ruby
10
+ # Before: would return the epic even if it belonged to a different project
11
+ @project.epics.find(epic_id)
12
+
13
+ # After: raises RecordNotFound if epic.project_id != @project.id
14
+ @project.epics.find(epic_id)
15
+ ```
16
+
3
17
  ## 0.0.21
4
18
 
5
19
  ### Added
@@ -912,15 +912,11 @@ module ActiveItem
912
912
  indexes.each do |index_name, config|
913
913
  # Check partition key if it's being changed
914
914
  partition_key = config[:partition_key]&.to_s
915
- if partition_key && changed_dynamo_keys.key?(partition_key)
916
- validate_gsi_key_value!(partition_key, changed_dynamo_keys[partition_key], index_name)
917
- end
915
+ validate_gsi_key_value!(partition_key, changed_dynamo_keys[partition_key], index_name) if partition_key && changed_dynamo_keys.key?(partition_key)
918
916
 
919
917
  # Check sort key if it's being changed
920
918
  sort_key = config[:sort_key]&.to_s
921
- if sort_key && changed_dynamo_keys.key?(sort_key)
922
- validate_gsi_key_value!(sort_key, changed_dynamo_keys[sort_key], index_name)
923
- end
919
+ validate_gsi_key_value!(sort_key, changed_dynamo_keys[sort_key], index_name) if sort_key && changed_dynamo_keys.key?(sort_key)
924
920
  end
925
921
  end
926
922
 
@@ -953,9 +949,8 @@ module ActiveItem
953
949
  case value
954
950
  when String
955
951
  !value.empty? # Empty strings are not allowed for GSI keys
956
- when Integer, Float, BigDecimal
957
- true
958
- when StringIO
952
+ when Integer, Float, BigDecimal, StringIO
953
+ # Numeric (Integer, Float, BigDecimal) and Binary (StringIO) are valid GSI key types
959
954
  true
960
955
  else
961
956
  false
@@ -294,18 +294,26 @@ module ActiveItem
294
294
 
295
295
  # Find by id within the current scope, or find by block (like Enumerable#find)
296
296
  #
297
+ # When called on an association (e.g., @project.epics.find(id)), validates that
298
+ # the found record belongs to the association's scope. This matches Rails behavior
299
+ # where association.find(id) only returns records belonging to that association.
300
+ #
297
301
  # @overload find(id)
298
302
  # Find a record by ID within the current scope
299
303
  # @param id [String] The ID to find
300
- # @return [Object, nil] The found record or nil
304
+ # @return [Object] The found record
305
+ # @raise [ActiveItem::RecordNotFound] If record not found or not in scope
301
306
  #
302
307
  # @overload find(&block)
303
308
  # Find the first record matching the block condition (like Enumerable#find/detect)
304
309
  # @yield [record] Evaluates the block for each record
305
310
  # @return [Object, nil] The first record where block returns true, or nil
306
311
  #
307
- # @example Find by ID
308
- # User.where(status: 'active').find('user-123')
312
+ # @example Find by ID (scoped to association)
313
+ # @project.epics.find('epic-123') # Only finds if epic belongs to @project
314
+ #
315
+ # @example Find by ID (unscoped)
316
+ # Epic.where(status: 'active').find('epic-123') # Finds any active epic
309
317
  #
310
318
  # @example Find by block
311
319
  # User.where(status: 'active').find { |u| u.email.include?('@example.com') }
@@ -317,13 +325,20 @@ module ActiveItem
317
325
  elsif id
318
326
  # Use direct GetItem instead of scanning — O(1) vs O(n)
319
327
  record = resolved_model.find(id)
328
+
329
+ # When called on an association (has conditions), validate scope
330
+ # This ensures @project.epics.find(id) only returns epics belonging to @project
331
+ if conditions.any? && !conditions[:_empty]
332
+ foreign_key, expected_value = conditions.first
333
+ actual_value = record.send(foreign_key)
334
+ raise ActiveItem::RecordNotFound, "Couldn't find #{resolved_model.name} with id=#{id}" unless actual_value == expected_value
335
+ end
336
+
320
337
  preload_associations_for_records([record]) if includes_associations.any?
321
338
  record
322
339
  else
323
340
  raise ArgumentError, 'find requires either an ID or a block'
324
341
  end
325
- rescue ActiveItem::RecordNotFound
326
- nil
327
342
  end
328
343
 
329
344
  # Find by conditions within current scope
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveItem
4
- VERSION = '0.0.21'
4
+ VERSION = '0.0.22'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeitem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.21
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-08-13 00:00:00.000000000 Z
12
+ date: 2026-09-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel