activeitem 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/lib/active_item/relation.rb +90 -6
- data/lib/active_item/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6460db8a74ca7bcb5d28c0bfdb7c4a6c1eba5c5d68318245612dad78d1d1d282
|
|
4
|
+
data.tar.gz: 9fe2d9707efa0a41b74fa1c07c5c24453605baa5d24e3f1880c248c92c5a03b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61ad96dc3dd1a28bda5a9af288bd17e1eef4f11b450f08efae26c1ea8f8060819727dde8d855d562e0fbbf2e6012096e159952d44c0d22255abc2384d85d80a9
|
|
7
|
+
data.tar.gz: 741d0e3a257199f62373c16960bea2d90fe26e37e9f4dcb8516cfb049170caac8a237c90ef1ba17dde88b356218b12f7e5e7d5f4a9d96abee7f64225639762f8
|
data/lib/active_item/relation.rb
CHANGED
|
@@ -374,6 +374,39 @@ module ActiveItem
|
|
|
374
374
|
record
|
|
375
375
|
end
|
|
376
376
|
|
|
377
|
+
# Build a new record with the foreign key pre-set (does not save).
|
|
378
|
+
# Usage: conversation.messages.build(role: "user", body: "hello")
|
|
379
|
+
# @param attributes [Hash] Attributes for the new record
|
|
380
|
+
# @return [ActiveItem::Base] The unsaved record
|
|
381
|
+
def build(attributes = {})
|
|
382
|
+
foreign_key, foreign_value = conditions.first
|
|
383
|
+
resolved_model.new(attributes.merge(foreign_key => foreign_value))
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Build and save a new record with the foreign key pre-set.
|
|
387
|
+
# Returns the record (may be invalid — check .persisted? or .errors).
|
|
388
|
+
# Usage: conversation.messages.create(role: "user", body: "hello")
|
|
389
|
+
# @param attributes [Hash] Attributes for the new record
|
|
390
|
+
# @return [ActiveItem::Base] The record (saved if valid)
|
|
391
|
+
def create(attributes = {})
|
|
392
|
+
record = build(attributes)
|
|
393
|
+
record.save
|
|
394
|
+
reload
|
|
395
|
+
record
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Build and save a new record with the foreign key pre-set.
|
|
399
|
+
# Raises ActiveItem::RecordInvalid if validations fail.
|
|
400
|
+
# Usage: conversation.messages.create!(role: "user", body: "hello")
|
|
401
|
+
# @param attributes [Hash] Attributes for the new record
|
|
402
|
+
# @return [ActiveItem::Base] The persisted record
|
|
403
|
+
def create!(attributes = {})
|
|
404
|
+
record = build(attributes)
|
|
405
|
+
record.save!
|
|
406
|
+
reload
|
|
407
|
+
record
|
|
408
|
+
end
|
|
409
|
+
|
|
377
410
|
# Returns the DynamoDB operation that would be executed, without running it.
|
|
378
411
|
# Analogous to ActiveRecord's .to_sql — shows the operation type, table, index,
|
|
379
412
|
# key conditions, filters, and limits in DynamoDB terms.
|
|
@@ -514,7 +547,8 @@ module ActiveItem
|
|
|
514
547
|
if effective_index && normalized_conditions.any? && normalized_conditions.values.first.is_a?(Array)
|
|
515
548
|
index_config = resolved_model.indexes[effective_index] || {}
|
|
516
549
|
ruby_partition_key = normalized_conditions.keys.first.to_s
|
|
517
|
-
|
|
550
|
+
raw_pk = index_config[:partition_key]&.to_s || ruby_partition_key
|
|
551
|
+
dynamo_partition_key = resolved_model.to_dynamo_key(raw_pk)
|
|
518
552
|
return fanout_paginated_query(effective_index, normalized_conditions, index_config, dynamo_partition_key,
|
|
519
553
|
normalized_conditions.values.first, cursor, per_page)
|
|
520
554
|
end
|
|
@@ -526,6 +560,12 @@ module ActiveItem
|
|
|
526
560
|
else
|
|
527
561
|
paginated_scan_with_conditions(normalized_conditions, exclusive_start_key, per_page)
|
|
528
562
|
end
|
|
563
|
+
rescue Aws::DynamoDB::Errors::ValidationException => e
|
|
564
|
+
raise unless e.message.include?('specified index')
|
|
565
|
+
|
|
566
|
+
warn "[ActiveItem] WARNING: Index not found on table '#{resolved_model.table_name}'. " \
|
|
567
|
+
'Falling back to table scan. Create the index: belt setup tables && belt deploy'
|
|
568
|
+
paginated_scan_with_conditions(normalized_conditions, exclusive_start_key, per_page)
|
|
529
569
|
rescue Aws::DynamoDB::Errors::AccessDeniedException => e
|
|
530
570
|
raise ActiveItem::AccessDeniedError.new(model_name: resolved_model.name, table: resolved_model.table_name,
|
|
531
571
|
operation: 'PaginatedQuery', original_error: e)
|
|
@@ -573,7 +613,8 @@ module ActiveItem
|
|
|
573
613
|
partition_value = normalized_conditions.values.first
|
|
574
614
|
|
|
575
615
|
index_config = resolved_model.indexes[idx_name] || {}
|
|
576
|
-
|
|
616
|
+
raw_pk = index_config[:partition_key]&.to_s || ruby_partition_key
|
|
617
|
+
dynamo_partition_key = resolved_model.to_dynamo_key(raw_pk)
|
|
577
618
|
|
|
578
619
|
params = {
|
|
579
620
|
table_name: resolved_model.table_name,
|
|
@@ -755,7 +796,7 @@ module ActiveItem
|
|
|
755
796
|
effective_index = index_name || resolved_model.send(:detect_index_for_conditions, normalized_conditions)
|
|
756
797
|
|
|
757
798
|
if effective_index && normalized_conditions.any?
|
|
758
|
-
|
|
799
|
+
query_with_index_or_fallback(effective_index, normalized_conditions)
|
|
759
800
|
else
|
|
760
801
|
scan_with_conditions_normalized(normalized_conditions)
|
|
761
802
|
end
|
|
@@ -805,6 +846,13 @@ module ActiveItem
|
|
|
805
846
|
end
|
|
806
847
|
|
|
807
848
|
limit_value ? [total, limit_value].min : total
|
|
849
|
+
rescue Aws::DynamoDB::Errors::ValidationException => e
|
|
850
|
+
raise unless e.message.include?('specified index')
|
|
851
|
+
|
|
852
|
+
warn "[ActiveItem] WARNING: Index not found on table '#{resolved_model.table_name}'. " \
|
|
853
|
+
'Falling back to table scan for count. Create the index: belt setup tables && belt deploy'
|
|
854
|
+
# Retry without the index
|
|
855
|
+
count_via_scan(normalized_conditions)
|
|
808
856
|
rescue Aws::DynamoDB::Errors::AccessDeniedException => e
|
|
809
857
|
raise ActiveItem::AccessDeniedError.new(model_name: resolved_model.name, table: resolved_model.table_name,
|
|
810
858
|
operation: 'Count', original_error: e)
|
|
@@ -815,7 +863,8 @@ module ActiveItem
|
|
|
815
863
|
partition_value = normalized_conditions.values.first
|
|
816
864
|
|
|
817
865
|
index_config = resolved_model.indexes[idx_name] || {}
|
|
818
|
-
|
|
866
|
+
raw_partition_key = index_config[:partition_key]&.to_s || ruby_partition_key
|
|
867
|
+
dynamo_partition_key = resolved_model.to_dynamo_key(raw_partition_key)
|
|
819
868
|
|
|
820
869
|
params = {
|
|
821
870
|
table_name: resolved_model.table_name,
|
|
@@ -878,6 +927,26 @@ module ActiveItem
|
|
|
878
927
|
params
|
|
879
928
|
end
|
|
880
929
|
|
|
930
|
+
# Fallback count via scan when an index is missing
|
|
931
|
+
def count_via_scan(normalized_conditions)
|
|
932
|
+
total = 0
|
|
933
|
+
exclusive_start_key = nil
|
|
934
|
+
|
|
935
|
+
loop do
|
|
936
|
+
params = build_count_scan_params(normalized_conditions)
|
|
937
|
+
params[:exclusive_start_key] = exclusive_start_key if exclusive_start_key
|
|
938
|
+
params[:limit] = limit_value if limit_value
|
|
939
|
+
|
|
940
|
+
response = resolved_model.dynamodb.scan(params)
|
|
941
|
+
total += response.count
|
|
942
|
+
exclusive_start_key = response.last_evaluated_key
|
|
943
|
+
break unless exclusive_start_key
|
|
944
|
+
break if limit_value && total >= limit_value
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
limit_value ? [total, limit_value].min : total
|
|
948
|
+
end
|
|
949
|
+
|
|
881
950
|
# Build scan params for a count-only scan
|
|
882
951
|
def build_count_scan_params(normalized_conditions)
|
|
883
952
|
filter_parts = []
|
|
@@ -911,7 +980,8 @@ module ActiveItem
|
|
|
911
980
|
partition_value = normalized_conditions.values.first
|
|
912
981
|
|
|
913
982
|
index_config = resolved_model.indexes[idx_name] || {}
|
|
914
|
-
|
|
983
|
+
raw_pk = index_config[:partition_key]&.to_s || ruby_partition_key
|
|
984
|
+
dynamo_partition_key = resolved_model.to_dynamo_key(raw_pk)
|
|
915
985
|
|
|
916
986
|
params = {
|
|
917
987
|
table_name: resolved_model.table_name,
|
|
@@ -1049,6 +1119,18 @@ module ActiveItem
|
|
|
1049
1119
|
foreign_key.to_s == attr_name ? nil : foreign_key.to_s
|
|
1050
1120
|
end
|
|
1051
1121
|
|
|
1122
|
+
# Attempt a GSI query, falling back to a filtered scan if the index doesn't exist.
|
|
1123
|
+
# Logs a warning when falling back so the developer knows to create the index.
|
|
1124
|
+
def query_with_index_or_fallback(idx_name, normalized_conditions)
|
|
1125
|
+
query_with_index_normalized(idx_name, normalized_conditions)
|
|
1126
|
+
rescue Aws::DynamoDB::Errors::ValidationException => e
|
|
1127
|
+
raise unless e.message.include?('specified index')
|
|
1128
|
+
|
|
1129
|
+
warn "[ActiveItem] WARNING: Index '#{idx_name}' not found on table '#{resolved_model.table_name}'. " \
|
|
1130
|
+
'Falling back to table scan. Create the index for better performance: belt setup tables && belt deploy'
|
|
1131
|
+
scan_with_conditions_normalized(normalized_conditions)
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1052
1134
|
def query_with_index_normalized(idx_name, normalized_conditions)
|
|
1053
1135
|
# Get the partition key from conditions (Ruby snake_case)
|
|
1054
1136
|
ruby_partition_key = normalized_conditions.keys.first.to_s
|
|
@@ -1057,7 +1139,9 @@ module ActiveItem
|
|
|
1057
1139
|
# Get the actual DynamoDB partition key name from the index definition
|
|
1058
1140
|
# The index definition stores the DynamoDB key name (which may be camelCase)
|
|
1059
1141
|
index_config = resolved_model.indexes[idx_name] || {}
|
|
1060
|
-
|
|
1142
|
+
raw_partition_key = index_config[:partition_key]&.to_s || ruby_partition_key
|
|
1143
|
+
# Always convert to DynamoDB key format (camelCase) for the expression
|
|
1144
|
+
dynamo_partition_key = resolved_model.to_dynamo_key(raw_partition_key)
|
|
1061
1145
|
|
|
1062
1146
|
return fanout_query(idx_name, normalized_conditions, index_config, dynamo_partition_key, partition_value) if partition_value.is_a?(Array)
|
|
1063
1147
|
|
data/lib/active_item/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.0.16
|
|
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-
|
|
12
|
+
date: 2026-08-04 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: activemodel
|