familia 2.0.0.pre16 → 2.0.0.pre18

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.
Files changed (250) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.github/workflows/{code-smellage.yml → code-smells.yml} +3 -63
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +6 -0
  6. data/CHANGELOG.rst +82 -0
  7. data/CLAUDE.md +47 -2
  8. data/Gemfile.lock +1 -1
  9. data/README.md +13 -0
  10. data/bin/irb +1 -1
  11. data/docs/archive/FAMILIA_TECHNICAL.md +1 -1
  12. data/docs/guides/core-field-system.md +48 -26
  13. data/docs/migrating/v2.0.0-pre18.md +58 -0
  14. data/docs/overview.md +2 -2
  15. data/docs/qodo-merge-compliance.md +96 -0
  16. data/docs/reference/api-technical.md +1 -1
  17. data/examples/encrypted_fields.rb +1 -1
  18. data/examples/safe_dump.rb +1 -1
  19. data/lib/familia/base.rb +6 -6
  20. data/lib/familia/connection/middleware.rb +58 -4
  21. data/lib/familia/connection.rb +1 -1
  22. data/lib/familia/data_type/class_methods.rb +63 -0
  23. data/lib/familia/data_type/connection.rb +83 -0
  24. data/lib/familia/data_type/{commands.rb → database_commands.rb} +2 -2
  25. data/lib/familia/data_type/serialization.rb +5 -5
  26. data/lib/familia/data_type/settings.rb +96 -0
  27. data/lib/familia/data_type/types/hashkey.rb +2 -1
  28. data/lib/familia/data_type/types/sorted_set.rb +113 -10
  29. data/lib/familia/data_type/types/stringkey.rb +0 -4
  30. data/lib/familia/data_type.rb +8 -195
  31. data/lib/familia/encryption/encrypted_data.rb +12 -2
  32. data/lib/familia/encryption/manager.rb +11 -4
  33. data/lib/familia/features/autoloader.rb +3 -1
  34. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
  35. data/lib/familia/features/encrypted_fields.rb +5 -2
  36. data/lib/familia/features/external_identifier.rb +49 -8
  37. data/lib/familia/features/object_identifier.rb +84 -12
  38. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +9 -9
  39. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +45 -26
  40. data/lib/familia/features/relationships/indexing.rb +7 -1
  41. data/lib/familia/features/relationships/participation/participant_methods.rb +6 -2
  42. data/lib/familia/features/safe_dump.rb +2 -3
  43. data/lib/familia/features/transient_fields.rb +7 -2
  44. data/lib/familia/features.rb +6 -1
  45. data/lib/familia/field_type.rb +0 -18
  46. data/lib/familia/horreum/{core/connection.rb → connection.rb} +21 -0
  47. data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +1 -1
  48. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +102 -56
  49. data/lib/familia/horreum/{subclass/management.rb → management.rb} +18 -15
  50. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +73 -170
  51. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +22 -2
  52. data/lib/familia/horreum/serialization.rb +190 -0
  53. data/lib/familia/horreum.rb +39 -14
  54. data/lib/familia/identifier_extractor.rb +60 -0
  55. data/lib/familia/logging.rb +271 -112
  56. data/lib/familia/refinements.rb +0 -1
  57. data/lib/familia/version.rb +1 -1
  58. data/lib/familia.rb +2 -2
  59. data/lib/middleware/{database_middleware.rb → database_logger.rb} +47 -14
  60. data/pr_agent.toml +31 -0
  61. data/pr_compliance_checklist.yaml +45 -0
  62. data/try/edge_cases/empty_identifiers_try.rb +1 -1
  63. data/try/edge_cases/hash_symbolization_try.rb +31 -31
  64. data/try/edge_cases/json_serialization_try.rb +2 -2
  65. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
  66. data/try/edge_cases/race_conditions_try.rb +1 -1
  67. data/try/edge_cases/reserved_keywords_try.rb +1 -1
  68. data/try/edge_cases/string_coercion_try.rb +1 -1
  69. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  70. data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
  71. data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
  72. data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
  73. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  74. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
  75. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
  76. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
  77. data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
  78. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
  79. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  80. data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
  81. data/try/features/encrypted_fields/memory_security_try.rb +1 -1
  82. data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
  83. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  84. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
  85. data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
  86. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
  87. data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
  88. data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
  89. data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
  90. data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
  91. data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
  92. data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
  93. data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
  94. data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
  95. data/try/features/expiration/expiration_try.rb +1 -1
  96. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  97. data/try/features/feature_dependencies_try.rb +1 -1
  98. data/try/features/feature_improvements_try.rb +1 -1
  99. data/try/features/field_groups_try.rb +244 -0
  100. data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
  101. data/try/features/object_identifier/object_identifier_try.rb +1 -1
  102. data/try/features/quantization/quantization_try.rb +1 -1
  103. data/try/features/real_feature_integration_try.rb +17 -14
  104. data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
  105. data/try/features/relationships/indexing_try.rb +16 -1
  106. data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
  107. data/try/features/relationships/participation_commands_verification_try.rb +4 -4
  108. data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
  109. data/try/features/relationships/participation_reverse_index_try.rb +1 -1
  110. data/try/features/relationships/relationships_api_changes_try.rb +1 -1
  111. data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
  112. data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
  113. data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
  114. data/try/features/relationships/relationships_performance_try.rb +1 -1
  115. data/try/features/relationships/relationships_performance_working_try.rb +1 -1
  116. data/try/features/relationships/relationships_try.rb +1 -1
  117. data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
  118. data/try/features/safe_dump/safe_dump_try.rb +1 -1
  119. data/try/features/transient_fields/redacted_string_try.rb +1 -1
  120. data/try/features/transient_fields/refresh_reset_try.rb +3 -1
  121. data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
  122. data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
  123. data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
  124. data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +1 -1
  125. data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
  126. data/try/{core → integration/connection}/isolated_dbclient_try.rb +3 -3
  127. data/try/integration/connection/middleware_reconnect_try.rb +87 -0
  128. data/try/{connection → integration/connection}/operation_mode_guards_try.rb +1 -1
  129. data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +1 -1
  130. data/try/{core → integration/connection}/pools_try.rb +1 -1
  131. data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
  132. data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
  133. data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
  134. data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
  135. data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
  136. data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
  137. data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
  138. data/try/{core → integration}/create_method_try.rb +1 -1
  139. data/try/integration/cross_component_try.rb +1 -1
  140. data/try/{core → integration}/database_consistency_try.rb +12 -8
  141. data/try/{core → integration}/familia_extended_try.rb +1 -1
  142. data/try/{core → integration}/familia_members_methods_try.rb +1 -1
  143. data/try/{models → integration/models}/customer_safe_dump_try.rb +1 -1
  144. data/try/{models → integration/models}/customer_try.rb +6 -6
  145. data/try/{models → integration/models}/datatype_base_try.rb +1 -1
  146. data/try/{models → integration/models}/familia_object_try.rb +1 -1
  147. data/try/{core → integration}/persistence_operations_try.rb +1 -1
  148. data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
  149. data/try/{configuration → integration}/scenarios_try.rb +2 -2
  150. data/try/{core → integration}/secure_identifier_try.rb +1 -1
  151. data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
  152. data/try/performance/benchmarks_try.rb +2 -2
  153. data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
  154. data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
  155. data/try/{helpers → support/helpers}/test_helpers.rb +15 -7
  156. data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +1 -1
  157. data/try/{core → unit/core}/autoloader_try.rb +1 -1
  158. data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
  159. data/try/{core → unit/core}/connection_try.rb +5 -5
  160. data/try/{core → unit/core}/errors_try.rb +4 -4
  161. data/try/{core → unit/core}/extensions_try.rb +1 -1
  162. data/try/unit/core/familia_logger_try.rb +110 -0
  163. data/try/{core → unit/core}/familia_try.rb +2 -2
  164. data/try/{core → unit/core}/middleware_try.rb +41 -1
  165. data/try/{core → unit/core}/settings_try.rb +1 -1
  166. data/try/{core → unit/core}/time_utils_try.rb +1 -1
  167. data/try/{core → unit/core}/tools_try.rb +3 -3
  168. data/try/{core → unit/core}/utils_try.rb +17 -14
  169. data/try/{data_types → unit/data_types}/boolean_try.rb +1 -1
  170. data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
  171. data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
  172. data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
  173. data/try/{data_types → unit/data_types}/list_try.rb +1 -1
  174. data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
  175. data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
  176. data/try/unit/data_types/sorted_set_zadd_options_try.rb +625 -0
  177. data/try/{data_types → unit/data_types}/string_try.rb +1 -1
  178. data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
  179. data/try/unit/horreum/auto_indexing_on_save_try.rb +212 -0
  180. data/try/{horreum → unit/horreum}/base_try.rb +3 -3
  181. data/try/{horreum → unit/horreum}/class_methods_try.rb +1 -1
  182. data/try/{horreum → unit/horreum}/commands_try.rb +3 -1
  183. data/try/unit/horreum/defensive_initialization_try.rb +86 -0
  184. data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +3 -1
  185. data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
  186. data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
  187. data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
  188. data/try/{horreum → unit/horreum}/initialization_try.rb +2 -2
  189. data/try/unit/horreum/json_type_preservation_try.rb +248 -0
  190. data/try/{horreum → unit/horreum}/relations_try.rb +1 -1
  191. data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
  192. data/try/{horreum → unit/horreum}/serialization_try.rb +4 -4
  193. data/try/{horreum → unit/horreum}/settings_try.rb +3 -1
  194. data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
  195. data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
  196. data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
  197. data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
  198. data/try/valkey.conf +26 -0
  199. metadata +149 -132
  200. data/lib/familia/distinguisher.rb +0 -85
  201. data/lib/familia/horreum/core.rb +0 -21
  202. data/lib/familia/refinements/logger_trace.rb +0 -60
  203. data/try/refinements/logger_trace_methods_try.rb +0 -44
  204. /data/lib/familia/horreum/{shared/settings.rb → settings.rb} +0 -0
  205. /data/lib/familia/horreum/{core/utils.rb → utils.rb} +0 -0
  206. /data/try/{debugging → support/debugging}/README.md +0 -0
  207. /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
  208. /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
  209. /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
  210. /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
  211. /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
  212. /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
  213. /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
  214. /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
  215. /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
  216. /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
  217. /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
  218. /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
  219. /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
  220. /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
  221. /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
  222. /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
  223. /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
  224. /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
  225. /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
  226. /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
  227. /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
  228. /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
  229. /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
  230. /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
  231. /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
  232. /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
  233. /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
  234. /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
  235. /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
  236. /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
  237. /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
  238. /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
  239. /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
  240. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
  241. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  242. /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
  243. /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
  244. /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  245. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
  246. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
  247. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
  248. /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
  249. /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
  250. /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
@@ -0,0 +1,212 @@
1
+ # try/horreum/auto_indexing_on_save_try.rb
2
+
3
+ #
4
+ # Auto-indexing on save functionality tests
5
+ # Tests automatic index population when Familia::Horreum objects are saved
6
+ #
7
+
8
+ require_relative '../../support/helpers/test_helpers'
9
+
10
+ # Test classes for auto-indexing functionality
11
+ class ::AutoIndexUser < Familia::Horreum
12
+ feature :relationships
13
+
14
+ identifier_field :user_id
15
+ field :user_id
16
+ field :email
17
+ field :username
18
+ field :department
19
+
20
+ # Class-level unique indexes (should auto-populate on save)
21
+ unique_index :email, :email_index
22
+ unique_index :username, :username_index
23
+ end
24
+
25
+ class ::AutoIndexCompany < Familia::Horreum
26
+ feature :relationships
27
+
28
+ identifier_field :company_id
29
+ field :company_id
30
+ field :name
31
+ end
32
+
33
+ class ::AutoIndexEmployee < Familia::Horreum
34
+ feature :relationships
35
+
36
+ identifier_field :emp_id
37
+ field :emp_id
38
+ field :badge_number
39
+ field :department
40
+
41
+ # Instance-scoped indexes (should NOT auto-populate - require parent context)
42
+ unique_index :badge_number, :badge_index, within: AutoIndexCompany
43
+ multi_index :department, :dept_index, within: AutoIndexCompany
44
+ end
45
+
46
+ # Setup
47
+ @user_id = "user_#{rand(1000000)}"
48
+ @user = AutoIndexUser.new(user_id: @user_id, email: 'test@example.com', username: 'testuser', department: 'engineering')
49
+
50
+ @company_id = "comp_#{rand(1000000)}"
51
+ @company = AutoIndexCompany.new(company_id: @company_id, name: 'Test Corp')
52
+
53
+ @emp_id = "emp_#{rand(1000000)}"
54
+ @employee = AutoIndexEmployee.new(emp_id: @emp_id, badge_number: 'BADGE123', department: 'sales')
55
+
56
+ # =============================================
57
+ # 1. Class-Level Unique Index Auto-Population
58
+ # =============================================
59
+
60
+ ## Unique index is empty before save
61
+ AutoIndexUser.email_index.has_key?('test@example.com')
62
+ #=> false
63
+
64
+ ## Save automatically populates unique index
65
+ @user.save
66
+ AutoIndexUser.email_index.has_key?('test@example.com')
67
+ #=> true
68
+
69
+ ## Auto-populated index maps to correct identifier
70
+ AutoIndexUser.email_index.get('test@example.com')
71
+ #=> @user_id
72
+
73
+ ## Finder method works after auto-indexing
74
+ found = AutoIndexUser.find_by_email('test@example.com')
75
+ found&.user_id
76
+ #=> @user_id
77
+
78
+ ## Multiple unique indexes auto-populate on same save
79
+ AutoIndexUser.username_index.get('testuser')
80
+ #=> @user_id
81
+
82
+ ## Subsequent saves maintain index (idempotent)
83
+ @user.save
84
+ AutoIndexUser.email_index.get('test@example.com')
85
+ #=> @user_id
86
+
87
+ ## Changing indexed field and saving adds new entry (old entry remains unless manually removed)
88
+ # Note: Auto-indexing is idempotent addition only - updates require manual update_in_class_* calls
89
+ @user.email = 'newemail@example.com'
90
+ @user.save
91
+ # New email is indexed, but old email remains (expected behavior - use update_in_class_* for proper updates)
92
+ [AutoIndexUser.email_index.has_key?('test@example.com'), AutoIndexUser.email_index.get('newemail@example.com') == @user_id]
93
+ #=> [true, true]
94
+
95
+ # =============================================
96
+ # 2. Instance-Scoped Indexes (Manual Only)
97
+ # =============================================
98
+
99
+ ## Instance-scoped indexes do NOT auto-populate on save
100
+ @employee.save
101
+ @company.badge_index.has_key?('BADGE123')
102
+ #=> false
103
+
104
+ ## Instance-scoped indexes remain manual (require parent context)
105
+ @employee.add_to_auto_index_company_badge_index(@company)
106
+ @company.badge_index.has_key?('BADGE123')
107
+ #=> true
108
+
109
+ # =============================================
110
+ # 3. Edge Cases and Error Handling
111
+ # =============================================
112
+
113
+ ## Nil field values handled gracefully
114
+ @user_nil_id = "user_nil_#{rand(1000000)}"
115
+ @user_nil = AutoIndexUser.new(user_id: @user_nil_id, email: nil, username: nil, department: nil)
116
+ @user_nil.save
117
+ AutoIndexUser.email_index.has_key?('')
118
+ #=> false
119
+
120
+ ## Empty string field values handled gracefully
121
+ @user_empty_id = "user_empty_#{rand(1000000)}"
122
+ @user_empty = AutoIndexUser.new(user_id: @user_empty_id, email: '', username: '', department: '')
123
+ @user_empty.save
124
+ # Empty strings are indexed (they're valid string values, just empty)
125
+ AutoIndexUser.email_index.has_key?('')
126
+ #=> true
127
+
128
+ ## Auto-indexing works with create method
129
+ @user2_id = "user_#{rand(1000000)}"
130
+ @user2 = AutoIndexUser.create(user_id: @user2_id, email: 'create@example.com', username: 'createuser', department: 'marketing')
131
+ AutoIndexUser.find_by_email('create@example.com')&.user_id
132
+ #=> @user2_id
133
+
134
+ ## Auto-indexing idempotent with multiple saves
135
+ @user2.save
136
+ @user2.save
137
+ @user2.save
138
+ AutoIndexUser.email_index.get('create@example.com')
139
+ #=> @user2_id
140
+
141
+ ## Field update followed by save adds new entry (use update_in_class_* for proper updates)
142
+ old_email = @user2.email
143
+ @user2.email = 'updated@example.com'
144
+ @user2.save
145
+ # Both old and new emails are indexed (auto-indexing doesn't remove old values)
146
+ # For proper updates that remove old values, use: @user2.update_in_class_email_index(old_email)
147
+ [AutoIndexUser.email_index.has_key?(old_email), AutoIndexUser.email_index.get('updated@example.com') == @user2_id]
148
+ #=> [true, true]
149
+
150
+ # =============================================
151
+ # 4. Integration with Other Features
152
+ # =============================================
153
+
154
+ ## Auto-indexing works with transient fields
155
+ class ::AutoIndexWithTransient < Familia::Horreum
156
+ feature :transient_fields
157
+ feature :relationships
158
+
159
+ identifier_field :id
160
+ field :id
161
+ field :email
162
+ transient_field :temp_value
163
+
164
+ unique_index :email, :email_index
165
+ end
166
+
167
+ @transient_id = "trans_#{rand(1000000)}"
168
+ @transient_obj = AutoIndexWithTransient.new(id: @transient_id, email: 'transient@example.com', temp_value: 'ignored')
169
+ @transient_obj.save
170
+ AutoIndexWithTransient.find_by_email('transient@example.com')&.id
171
+ #=> @transient_id
172
+
173
+ ## Auto-indexing works regardless of other features
174
+ # Just verify that the feature system doesn't interfere
175
+ @transient_obj.class.respond_to?(:indexing_relationships)
176
+ #=> true
177
+
178
+ # =============================================
179
+ # 5. Performance and Behavior Verification
180
+ # =============================================
181
+
182
+ ## Auto-indexing has negligible overhead (no existence checks)
183
+ # This test verifies the design: we use idempotent commands (HSET, SADD)
184
+ # rather than checking if the index exists before updating
185
+ @user4_id = "user_#{rand(1000000)}"
186
+ @user4 = AutoIndexUser.new(user_id: @user4_id, email: 'perf@example.com', username: 'perfuser', department: 'ops')
187
+
188
+ # Save multiple times - all should succeed with same result
189
+ @user4.save
190
+ @user4.save
191
+ @user4.save
192
+
193
+ AutoIndexUser.email_index.get('perf@example.com')
194
+ #=> @user4_id
195
+
196
+ ## Auto-indexing only processes class-level indexes
197
+ # Verify no errors when instance-scoped indexes present
198
+ @employee2_id = "emp_#{rand(1000000)}"
199
+ @employee2 = AutoIndexEmployee.new(emp_id: @employee2_id, badge_number: 'BADGE456', department: 'engineering')
200
+ @employee2.save # Should not error, just skip instance-scoped indexes
201
+ @employee2.emp_id
202
+ #=> @employee2_id
203
+
204
+ # Teardown - clean up test objects
205
+ [@user, @user2, @user4, @user_nil, @user_empty, @company, @employee, @employee2, @transient_obj].each do |obj|
206
+ obj.destroy! if obj.respond_to?(:destroy!) && obj.respond_to?(:exists?) && obj.exists?
207
+ end
208
+
209
+ # Clean up class-level indexes
210
+ [AutoIndexUser.email_index, AutoIndexUser.username_index].each do |index|
211
+ index.delete! if index.respond_to?(:delete!) && index.respond_to?(:exists?) && index.exists?
212
+ end
@@ -1,6 +1,6 @@
1
1
  # try/horreum/base_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  Familia.debug = false
6
6
 
@@ -203,11 +203,11 @@ end
203
203
  @aliased.display_size! 100
204
204
  #=> true
205
205
 
206
- ## Aliased field refresh works correctly
206
+ ## Aliased field refresh works correctly (type preserved)
207
207
  @aliased.width = 50 # unsaved change
208
208
  @aliased.refresh!
209
209
  @aliased.width
210
- #=> "100"
210
+ #=> 100
211
211
 
212
212
  ## Fast method with custom name
213
213
  class CustomFastMethodTest < Familia::Horreum
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Test Horreum class methods
4
4
 
5
- require_relative '../helpers/test_helpers'
5
+ require_relative '../../support/helpers/test_helpers'
6
6
 
7
7
  TestUser = Class.new(Familia::Horreum) do
8
8
  identifier_field :email
@@ -1,6 +1,8 @@
1
+ # try/horreum/commands_try.rb
2
+
1
3
  # Test Horreum Valkey/Redis commands
2
4
 
3
- require_relative '../helpers/test_helpers'
5
+ require_relative '../../support/helpers/test_helpers'
4
6
 
5
7
  ## hget/hset operations
6
8
  begin
@@ -0,0 +1,86 @@
1
+ # try/horreum/defensive_initialization_try.rb
2
+
3
+ require_relative '../../support/helpers/test_helpers'
4
+
5
+ # Test defensive initialization behavior
6
+ class User < Familia::Horreum
7
+ field :email
8
+ list :sessions
9
+ zset :metrics
10
+
11
+ def initialize(email = nil)
12
+ # This is the common mistake - overriding initialize without calling super
13
+ @email = email
14
+ # Missing: super() or initialize_relatives
15
+ end
16
+ end
17
+
18
+ class SafeUser < Familia::Horreum
19
+ field :email
20
+ list :sessions
21
+ zset :metrics
22
+
23
+ def init
24
+ # This is the correct way - using the init hook
25
+ # Fields are already set by initialize, no need to override
26
+ end
27
+ end
28
+
29
+ # Setup instances for testing
30
+ @user = User.new("test@example.com")
31
+ @safe_user = SafeUser.new
32
+ @safe_user.email = "safe@example.com"
33
+
34
+ ## Test that accessing relationships after bad initialize triggers lazy initialization
35
+ @user.email
36
+ #=> "test@example.com"
37
+
38
+ ## Test that sessions works with lazy initialization
39
+ @user.sessions.class
40
+ #=> Familia::ListKey
41
+
42
+ ## Test that metrics also works with lazy initialization
43
+ @user.metrics.class
44
+ #=> Familia::SortedSet
45
+
46
+ ## Test that safe user works normally
47
+ @safe_user.email
48
+ #=> "safe@example.com"
49
+
50
+ ## Test that safe user sessions work
51
+ @safe_user.sessions.class
52
+ #=> Familia::ListKey
53
+
54
+ ## Test that relatives_initialized flag prevents double initialization
55
+ @user.singleton_class.instance_variable_get(:@relatives_initialized)
56
+ #=> true
57
+
58
+ ## Test that manual initialize_relatives call is no-op
59
+ @user.initialize_relatives
60
+ @user.sessions.class
61
+ #=> Familia::ListKey
62
+
63
+ ## Test that the original problem is now fixed - bad override still works
64
+ class BadUser < Familia::Horreum
65
+ field :email
66
+ list :sessions
67
+
68
+ def initialize(email)
69
+ # Bad: overriding initialize without calling super
70
+ @email = email
71
+ # Missing: super() or initialize_relatives
72
+ end
73
+ end
74
+
75
+ @bad_user = BadUser.new("bad@example.com")
76
+ @bad_user.email
77
+ #=> "bad@example.com"
78
+
79
+ ## Test that relationships work despite bad initialize (lazy initialization kicks in)
80
+ @bad_user.sessions.class
81
+ #=> Familia::ListKey
82
+
83
+ ## Test that the bad user can actually use the relationships
84
+ @bad_user.sessions.add("session_123")
85
+ @bad_user.sessions.size > 0
86
+ #=> true
@@ -1,3 +1,5 @@
1
+ # try/horreum/destroy_related_fields_cleanup_try.rb
2
+
1
3
  # Horreum destroy! Related Fields Cleanup Tryouts
2
4
  #
3
5
  # Tests that when a Horreum instance is destroyed, all its related fields
@@ -7,7 +9,7 @@
7
9
  # This addresses the bug where destroy! only deleted the main object key
8
10
  # but left related field keys in the database.
9
11
 
10
- require_relative '../helpers/test_helpers'
12
+ require_relative '../../support/helpers/test_helpers'
11
13
 
12
14
  MANY_FIELD_MULTIPLIER = 10
13
15
 
@@ -1,6 +1,6 @@
1
1
  # try/horreum/enhanced_conflict_handling_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  Familia.debug = false
6
6
 
@@ -1,44 +1,53 @@
1
1
  # try/horreum/field_categories_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  Familia.debug = false
6
6
 
7
- # Define test class with various field categories
7
+ # Define test class with various field types
8
8
  class FieldCategoryTest < Familia::Horreum
9
+ feature :encrypted_fields
10
+ feature :transient_fields
11
+
9
12
  identifier_field :id
10
13
  field :id
11
- field :name # default category (:field)
12
- field :email, category: :encrypted # encrypted category
13
- field :tryouts_cache_data, category: :transient # transient category
14
- field :description, category: :persistent # explicit persistent category
15
- field :settings, category: nil # nil category (defaults to :field)
14
+ field :name # regular field
15
+ encrypted_field :email # encrypted field
16
+ transient_field :tryouts_cache_data # transient field
17
+ field :description # regular persistent field
18
+ field :settings # regular field
16
19
  end
17
20
 
18
21
  # Test class with multiple transient fields
19
22
  class MultiTransientTest < Familia::Horreum
23
+ feature :transient_fields
24
+
20
25
  identifier_field :id
21
26
  field :id
22
27
  field :permanent_data
23
- field :temp1, category: :transient
24
- field :temp2, category: :transient
25
- field :temp3, category: :transient
28
+ transient_field :temp1
29
+ transient_field :temp2
30
+ transient_field :temp3
26
31
  end
27
32
 
28
- # Field categories work with field aliasing
33
+ # Field types work with field aliasing
29
34
  class AliasedCategoryTest < Familia::Horreum
35
+ feature :transient_fields
36
+
30
37
  identifier_field :id
31
38
  field :id
32
- field :internal_temp, as: :temp, category: :transient
33
- field :internal_perm, as: :perm, category: :persistent
39
+ transient_field :internal_temp, as: :temp
40
+ field :internal_perm, as: :perm
34
41
  end
35
42
 
36
43
  # Test edge case with all transient fields
37
44
  class AllTransientTest < Familia::Horreum
45
+ feature :transient_fields
46
+
38
47
  identifier_field :id
39
48
  field :id
40
- field :temp1, category: :transient
41
- field :temp2, category: :transient
49
+ transient_field :temp1
50
+ transient_field :temp2
42
51
  end
43
52
 
44
53
  ## Field types are stored correctly
@@ -58,11 +67,11 @@ FieldCategoryTest.field_types[:email].category
58
67
  FieldCategoryTest.field_types[:tryouts_cache_data].category
59
68
  #=> :transient
60
69
 
61
- ## Explicit persistent category field has correct category
70
+ ## Regular fields have :field category
62
71
  FieldCategoryTest.field_types[:description].category
63
- #=> :persistent
72
+ #=> :field
64
73
 
65
- ## Nil category field defaults to :field
74
+ ## Regular fields default to :field category
66
75
  FieldCategoryTest.field_types[:settings].category
67
76
  #=> :field
68
77
 
@@ -1,6 +1,6 @@
1
1
  # try/horreum/field_definition_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  Familia.debug = false
6
6
 
@@ -1,6 +1,6 @@
1
1
  # try/horreum/initialization_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  Familia.debug = false
6
6
 
@@ -97,7 +97,7 @@ Familia.debug = false
97
97
  @complex.save
98
98
  @complex.refresh!
99
99
  [@complex.custid, @complex.name, @complex.role, @complex.verified]
100
- #=> ["complex@test.com", "Complex User", "admin", "true"]
100
+ #=> ["complex@test.com", "Complex User", "admin", true]
101
101
 
102
102
  ## Clean up saved test objects
103
103
  [@customer6, @complex].map(&:delete!)