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
@@ -1,6 +1,6 @@
1
1
  # try/features/feature_dependencies_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/features/feature_improvements_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../support/helpers/test_helpers'
4
4
 
5
5
  # Test hierarchical feature registration
6
6
  class ::TestClass
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ # try/features/field_groups_try.rb
4
+
5
+ require_relative '../../lib/familia'
6
+
7
+ # Define test classes in setup section
8
+ class BasicUser < Familia::Horreum
9
+ field_group :personal_info do
10
+ field :name
11
+ field :email
12
+ end
13
+ end
14
+
15
+ class MultiGroupUser < Familia::Horreum
16
+ field_group :personal do
17
+ field :name
18
+ field :email
19
+ end
20
+
21
+ field_group :metadata do
22
+ field :created_at
23
+ field :updated_at
24
+ end
25
+ end
26
+
27
+ class EmptyGroupModel < Familia::Horreum
28
+ field_group :placeholder
29
+ end
30
+
31
+ class TransientModel < Familia::Horreum
32
+ feature :transient_fields
33
+ transient_field :api_key
34
+ transient_field :session_token
35
+ end
36
+
37
+ class EncryptedModel < Familia::Horreum
38
+ feature :encrypted_fields
39
+ encrypted_field :password
40
+ encrypted_field :credit_card
41
+ end
42
+
43
+ class MixedGroupsModel < Familia::Horreum
44
+ feature :transient_fields
45
+ transient_field :temp_data
46
+
47
+ field_group :custom do
48
+ field :custom_field
49
+ end
50
+
51
+ feature :encrypted_fields
52
+ encrypted_field :secret_key
53
+ end
54
+
55
+ class FieldsOutsideGroups < Familia::Horreum
56
+ field :standalone_field
57
+
58
+ field_group :grouped do
59
+ field :grouped_field
60
+ end
61
+ end
62
+
63
+ class NoSuchGroup < Familia::Horreum
64
+ field_group :existing do
65
+ field :name
66
+ end
67
+ end
68
+
69
+ class ParentModel < Familia::Horreum
70
+ field_group :base_fields do
71
+ field :id
72
+ end
73
+ end
74
+
75
+ class ChildModel < ParentModel
76
+ field_group :child_fields do
77
+ field :name
78
+ end
79
+ end
80
+
81
+ # Create instances for testing
82
+ @user = MultiGroupUser.new(name: 'Alice', email: 'alice@example.com', created_at: Time.now.to_i)
83
+ @user2 = BasicUser.new(name: 'Bob', email: 'bob@example.com')
84
+
85
+ ## Manual field groups - basic access via hash
86
+ BasicUser.instance_variable_get(:@field_groups)[:personal_info]
87
+ #=> [:name, :email]
88
+
89
+ ## Multiple groups - access personal group via hash
90
+ MultiGroupUser.instance_variable_get(:@field_groups)[:personal]
91
+ #=> [:name, :email]
92
+
93
+ ## Multiple groups - access metadata group via hash
94
+ MultiGroupUser.instance_variable_get(:@field_groups)[:metadata]
95
+ #=> [:created_at, :updated_at]
96
+
97
+ ## Multiple groups - list all field groups (returns hash)
98
+ MultiGroupUser.field_groups.keys.sort
99
+ #=> [:metadata, :personal]
100
+
101
+ ## Field groups - fields defined inside groups are tracked
102
+ user = MultiGroupUser.new(name: 'Alice', email: 'alice@example.com', created_at: Time.now.to_i)
103
+
104
+ ## Grouped fields - access name field
105
+ @user.name
106
+ #=> 'Alice'
107
+
108
+ ## Grouped fields - access email field
109
+ @user.email
110
+ #=> 'alice@example.com'
111
+
112
+ ## Empty group - access via hash
113
+ EmptyGroupModel.instance_variable_get(:@field_groups)[:placeholder]
114
+ #=> []
115
+
116
+ ## Empty group - list field groups (returns hash)
117
+ EmptyGroupModel.field_groups
118
+ #=> {placeholder: []}
119
+
120
+ ## Empty group - list field group keys
121
+ EmptyGroupModel.field_groups.keys
122
+ #=> [:placeholder]
123
+
124
+ ## Transient feature - access via backward compatible method
125
+ TransientModel.transient_fields
126
+ #=> [:api_key, :session_token]
127
+
128
+ ## Transient feature - access via field_groups hash
129
+ TransientModel.instance_variable_get(:@field_groups)[:transient_fields]
130
+ #=> [:api_key, :session_token]
131
+
132
+ ## Transient feature - field_groups returns hash with content
133
+ TransientModel.field_groups
134
+ #=> {transient_fields: [:api_key, :session_token]}
135
+
136
+ ## Transient feature - list field group keys
137
+ TransientModel.field_groups.keys
138
+ #=> [:transient_fields]
139
+
140
+ ## Encrypted feature - access via backward compatible method
141
+ EncryptedModel.encrypted_fields
142
+ #=> [:password, :credit_card]
143
+
144
+ ## Encrypted feature - access via field_groups hash
145
+ EncryptedModel.instance_variable_get(:@field_groups)[:encrypted_fields]
146
+ #=> [:password, :credit_card]
147
+
148
+ ## Encrypted feature - field_groups returns hash with content
149
+ EncryptedModel.field_groups
150
+ #=> {encrypted_fields: [:password, :credit_card]}
151
+
152
+ ## Encrypted feature - list field group keys
153
+ EncryptedModel.field_groups.keys
154
+ #=> [:encrypted_fields]
155
+
156
+ ## Mixed groups - list all field group keys
157
+ MixedGroupsModel.field_groups.keys.sort
158
+ #=> [:custom, :encrypted_fields, :transient_fields]
159
+
160
+ ## Mixed groups - access custom group via hash
161
+ MixedGroupsModel.instance_variable_get(:@field_groups)[:custom]
162
+ #=> [:custom_field]
163
+
164
+ ## Mixed groups - access transient_fields via backward compatible method
165
+ MixedGroupsModel.transient_fields
166
+ #=> [:temp_data]
167
+
168
+ ## Mixed groups - access encrypted_fields via backward compatible method
169
+ MixedGroupsModel.encrypted_fields
170
+ #=> [:secret_key]
171
+
172
+ ## Error: nested field groups
173
+ class NestedGroupsModel < Familia::Horreum
174
+ field_group :outer do
175
+ field_group :inner do
176
+ field :bad
177
+ end
178
+ end
179
+ end
180
+ #=!> Familia::Problem
181
+
182
+ ## Exception during field_group block resets @current_field_group
183
+ class ErrorDuringGroup < Familia::Horreum
184
+ begin
185
+ field_group :broken do
186
+ field :first_field
187
+ raise StandardError, "Simulated error"
188
+ field :unreachable_field
189
+ end
190
+ rescue StandardError
191
+ # Swallow the error for testing
192
+ end
193
+
194
+ # Field defined after the error should not be in :broken group
195
+ field :after_error
196
+ end
197
+
198
+ ErrorDuringGroup
199
+ #=> ErrorDuringGroup
200
+
201
+ ## Exception handling - broken group has only first_field
202
+ ErrorDuringGroup.instance_variable_get(:@field_groups)[:broken]
203
+ #=> [:first_field]
204
+
205
+ ## Exception handling - after_error field is not in broken group
206
+ ErrorDuringGroup.instance_variable_get(:@field_groups)[:broken].include?(:after_error)
207
+ #=> false
208
+
209
+ ## Exception handling - after_error is in fields list
210
+ ErrorDuringGroup.fields.include?(:after_error)
211
+ #=> true
212
+
213
+ ## Exception handling - current_field_group was reset to nil
214
+ ErrorDuringGroup.instance_variable_get(:@current_field_group)
215
+ #=> nil
216
+
217
+
218
+ ## Fields outside - access grouped field group via hash
219
+ FieldsOutsideGroups.instance_variable_get(:@field_groups)[:grouped]
220
+ #=> [:grouped_field]
221
+
222
+ ## Fields outside - all fields include both grouped and standalone
223
+ FieldsOutsideGroups.fields
224
+ #=> [:standalone_field, :grouped_field]
225
+
226
+ ## Accessing non-existent field group returns nil
227
+ NoSuchGroup.instance_variable_get(:@field_groups)[:nonexistent]
228
+ #=> nil
229
+
230
+ ## Inheritance - parent class has its own field groups
231
+ ParentModel.field_groups
232
+ #=> {base_fields: [:id]}
233
+
234
+ ## Inheritance - child class has its own field groups
235
+ ChildModel.field_groups
236
+ #=> {child_fields: [:name]}
237
+
238
+ ## Normal field access - get name value
239
+ @user2.name
240
+ #=> 'Bob'
241
+
242
+ ## Normal field access - get email value
243
+ @user2.email
244
+ #=> 'bob@example.com'
@@ -1,6 +1,6 @@
1
1
  # try/features/object_identifier/object_identifier_integration_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/features/object_identifier/object_identifier_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/features/quantization_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/features/real_feature_integration_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
 
@@ -12,15 +12,17 @@ class ExpirationIntegrationTest < Familia::Horreum
12
12
  feature :expiration
13
13
  end
14
14
 
15
- # Safe dump feature integration with field categories
15
+ # Safe dump feature integration with field types
16
16
  class SafeDumpCategoryTest < Familia::Horreum
17
+ feature :encrypted_fields
18
+ feature :transient_fields
19
+ feature :safe_dump
20
+
17
21
  identifier_field :id
18
22
  field :id
19
- field :public_name, category: :persistent
20
- field :email, category: :encrypted
21
- field :tryouts_cache_data, category: :transient
22
-
23
- feature :safe_dump
23
+ field :public_name
24
+ encrypted_field :email
25
+ transient_field :tryouts_cache_data
24
26
 
25
27
  # Use new SafeDump DSL
26
28
  safe_dump_field :id
@@ -30,14 +32,15 @@ end
30
32
 
31
33
  # Combined features work together
32
34
  class CombinedFeaturesTest < Familia::Horreum
33
- identifier_field :id
34
- field :id
35
- field :name, category: :persistent
36
- field :temp_data, category: :transient
37
-
35
+ feature :transient_fields
38
36
  feature :expiration
39
37
  feature :safe_dump
40
38
 
39
+ identifier_field :id
40
+ field :id
41
+ field :name
42
+ transient_field :temp_data
43
+
41
44
  # Use new SafeDump DSL
42
45
  safe_dump_field :id
43
46
  safe_dump_field :name
@@ -123,11 +126,11 @@ CombinedFeaturesTest.features_enabled.include?(:safe_dump)
123
126
 
124
127
  ## Test that feature() method returns current features when called with no args
125
128
  CombinedFeaturesTest.feature
126
- #=> [:expiration, :safe_dump]
129
+ #=> [:transient_fields, :expiration, :safe_dump]
127
130
 
128
131
  ## Test that features_enabled() method returns the same results as feature() method
129
132
  CombinedFeaturesTest.feature
130
- #=> [:expiration, :safe_dump]
133
+ #=> [:transient_fields, :expiration, :safe_dump]
131
134
 
132
135
  ## Features list is accessible
133
136
  QueryFeaturesTest.feature
@@ -4,7 +4,7 @@
4
4
  # This test ensures the indexing system uses proper DataType methods instead of direct Redis calls
5
5
  #
6
6
 
7
- require_relative '../../helpers/test_helpers'
7
+ require_relative '../../support/helpers/test_helpers'
8
8
 
9
9
  # Enable database command logging for command verification tests
10
10
  Familia.enable_database_logging = true
@@ -44,8 +44,11 @@ end
44
44
 
45
45
  # Test data
46
46
  @user = TestIndexedUser.new(user_id: 'test_user_123', email: 'test@example.com', department: 'engineering')
47
+ @user.save
47
48
  @company = TestIndexedCompany.new(company_id: 'test_company_456', name: 'Test Corp')
49
+ @company.save
48
50
  @employee = TestIndexedEmployee.new(emp_id: 'test_emp_789', email: 'emp@example.com', department: 'sales')
51
+ @employee.save
49
52
 
50
53
  ## Class-level indexing creates proper DataType field
51
54
  TestIndexedUser.respond_to?(:email_index)
@@ -91,6 +94,7 @@ end
91
94
  #=> false
92
95
 
93
96
  ## Instance-level indexing works with parent context
97
+ @employee.save
94
98
  @employee.add_to_test_indexed_company_dept_index(@company)
95
99
  sample = @company.sample_from_department('sales')
96
100
  sample.first&.emp_id == @employee.emp_id
@@ -103,15 +107,16 @@ dept_index.class.name
103
107
 
104
108
  ## Multiple employees in same department
105
109
  @employee2 = TestIndexedEmployee.new(emp_id: 'test_emp_999', email: 'emp2@example.com', department: 'sales')
110
+ @employee2.save
106
111
  @employee2.add_to_test_indexed_company_dept_index(@company)
107
112
  employees_in_sales = @company.find_all_by_department('sales')
108
- employees_in_sales.map(&:emp_id).sort
113
+ employees_in_sales&.map(&:emp_id).sort
109
114
  #=> ["test_emp_789", "test_emp_999"]
110
115
 
111
116
  ## Removing from instance-level index works
112
117
  @employee.remove_from_test_indexed_company_dept_index(@company)
113
118
  remaining_employees = @company.find_all_by_department('sales')
114
- remaining_employees.map(&:emp_id)
119
+ remaining_employees&.map(&:emp_id)
115
120
  #=> ["test_emp_999"]
116
121
 
117
122
  ## Index update methods work correctly
@@ -4,7 +4,7 @@
4
4
  # Tests both multi_index (parent-context) and unique_index (class-level) indexing
5
5
  #
6
6
 
7
- require_relative '../../helpers/test_helpers'
7
+ require_relative '../../support/helpers/test_helpers'
8
8
 
9
9
  # Test classes for indexing functionality
10
10
  class ::TestUser < Familia::Horreum
@@ -55,13 +55,18 @@ end
55
55
 
56
56
  # Setup
57
57
  @user1 = TestUser.new(user_id: 'user_001', email: 'alice@example.com', username: 'alice', department: 'engineering', role: 'developer')
58
+ @user1.save
58
59
  @user2 = TestUser.new(user_id: 'user_002', email: 'bob@example.com', username: 'bob', department: 'marketing', role: 'manager')
60
+ @user2.save
59
61
  @user3 = TestUser.new(user_id: 'user_003', email: 'charlie@example.com', username: 'charlie', department: 'engineering', role: 'lead')
62
+ @user3.save
60
63
 
61
64
  @company_id = "comp_#{rand(10000000)}"
62
65
  @company = TestCompany.create(company_id: @company_id, name: 'Acme Corp')
63
66
  @emp1 = TestEmployee.new(emp_id: 'emp_001', email: 'alice@acme.com', department: 'engineering', manager_id: 'mgr_001', badge_number: 'BADGE001')
67
+ @emp1.save
64
68
  @emp2 = TestEmployee.new(emp_id: 'emp_002', email: 'bob@acme.com', department: 'sales', manager_id: 'mgr_002', badge_number: 'BADGE002')
69
+ @emp2.save
65
70
 
66
71
 
67
72
  ## Context-scoped methods require context parameter
@@ -152,6 +157,11 @@ found_users.map(&:user_id).sort
152
157
  TestUser.find_all_by_email([]).length
153
158
  #=> 0
154
159
 
160
+ ## Single value (non-array) is accepted by find_all_by method
161
+ found_users = TestUser.find_all_by_email('bob@example.com')
162
+ found_users.map(&:user_id)
163
+ #=> ["user_002"]
164
+
155
165
  ## Update index entry with old value removal
156
166
  old_email = @user1.email
157
167
  @user1.email = 'alice.new@example.com'
@@ -236,6 +246,11 @@ found_emps = @company.find_all_by_badge_number(badges)
236
246
  found_emps.map(&:emp_id).sort
237
247
  #=> ["emp_001", "emp_002"]
238
248
 
249
+ ## Single value (non-array) accepted for instance-scoped find_all_by
250
+ found_emps = @company.find_all_by_badge_number('BADGE002')
251
+ found_emps.map(&:emp_id)
252
+ #=> ["emp_002"]
253
+
239
254
  ## Update badge index entry
240
255
  old_badge = @emp1.badge_number
241
256
  @emp1.badge_number = 'BADGE001_NEW'
@@ -7,7 +7,7 @@ RSpec.describe 'participation_commands_verification_try' do
7
7
  Timecop.freeze(Time.parse("2024-01-15 10:30:00"))
8
8
  puts Time.now # Always returns 2024-01-15 10:30:00
9
9
  puts Date.today # Always returns 2024-01-15
10
- require_relative '../../../lib/middleware/database_middleware'
10
+ require_relative '../../../lib/middleware/database_logger'
11
11
  require_relative '../../../lib/familia'
12
12
  Familia.enable_database_logging = true
13
13
  Familia.enable_database_counter = true
@@ -17,7 +17,7 @@ puts Date.today # Always returns 2024-01-15
17
17
 
18
18
 
19
19
  # Load middleware first
20
- require_relative '../../../lib/middleware/database_middleware'
20
+ require_relative '../../../lib/middleware/database_logger'
21
21
 
22
22
  # Load Familia
23
23
  require_relative '../../../lib/familia'
@@ -83,7 +83,7 @@ instantiation_commands.empty?
83
83
  database_commands = DatabaseLogger.capture_commands do
84
84
  @customer.save
85
85
  end
86
- database_commands.map { |cmd| cmd[:command] } if database_commands
86
+ database_commands.map { |cmd| cmd.command } if database_commands
87
87
  ##=> [["hmset", "reverse_index_customer:ri_cust_123:object", "customer_id", "ri_cust_123", "name", "Reverse Index Test Customer"], ["zadd", "reverse_index_customer:instances", "1705343400.0", "ri_cust_123"]]
88
88
 
89
89
 
@@ -91,14 +91,14 @@ database_commands.map { |cmd| cmd[:command] } if database_commands
91
91
  database_commands = DatabaseLogger.capture_commands do
92
92
  @domain1.save
93
93
  end
94
- database_commands[0][:command] if database_commands && database_commands[0]
94
+ database_commands[0].command if database_commands && database_commands[0]
95
95
  ##=> ["hmset", "reverse_index_domain:ri_dom_1:object", "domain_id", "ri_dom_1", "display_domain", "example1.com", "created_at", "1705343400.0"]
96
96
 
97
97
  ## Domain2 save functionality
98
98
  database_commands = DatabaseLogger.capture_commands do
99
99
  @domain2.save
100
100
  end
101
- database_commands[0][:command]if database_commands && database_commands[0]
101
+ database_commands[0].command if database_commands && database_commands[0]
102
102
  ##=> ["hmset", "reverse_index_domain:ri_dom_2:object", "domain_id", "ri_dom_2", "display_domain", "example2.com", "created_at", "1705343401.0"]
103
103
 
104
104
 
@@ -3,7 +3,7 @@
3
3
  # Tests for performance improvements in participation functionality
4
4
  # Verifies reverse index functionality and robust type comparison
5
5
 
6
- require_relative '../../helpers/test_helpers'
6
+ require_relative '../../support/helpers/test_helpers'
7
7
 
8
8
  # Test classes for performance improvements
9
9
  class PerfTestCustomer < Familia::Horreum
@@ -3,7 +3,7 @@
3
3
  # Tests for participation reverse index functionality
4
4
  # Verifies performance improvements and correct behavior
5
5
 
6
- require_relative '../../helpers/test_helpers'
6
+ require_relative '../../support/helpers/test_helpers'
7
7
 
8
8
  # Test classes for reverse index functionality
9
9
  class ReverseIndexCustomer < Familia::Horreum
@@ -4,7 +4,7 @@
4
4
  # Testing new class_participates_in and unique_index methods
5
5
  # Testing breaking changes and argument validation
6
6
 
7
- require_relative '../../helpers/test_helpers'
7
+ require_relative '../../support/helpers/test_helpers'
8
8
 
9
9
  # Test classes for new API
10
10
  class ::ApiTestUser < Familia::Horreum
@@ -1,6 +1,6 @@
1
1
  # Simplified edge case testing for Relationships v2 - focusing on core functionality
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  # Test classes for edge case testing
6
6
  class EdgeTestCustomer < Familia::Horreum
@@ -41,14 +41,14 @@ end
41
41
  @domain1 = EdgeTestDomain.new(
42
42
  domain_id: 'edge_dom_1',
43
43
  display_domain: 'edge1.example.com',
44
- created_at: Time.new(2025, 6, 15, 12, 0, 0),
44
+ created_at: Time.new(2025, 6, 15, 12, 0, 0).to_i, # Convert Time to Integer for JSON serialization
45
45
  score_value: 10
46
46
  )
47
47
 
48
48
  @domain2 = EdgeTestDomain.new(
49
49
  domain_id: 'edge_dom_2',
50
50
  display_domain: 'edge2.example.com',
51
- created_at: Time.new(2025, 7, 20, 15, 30, 0),
51
+ created_at: Time.new(2025, 7, 20, 15, 30, 0).to_i, # Convert Time to Integer for JSON serialization
52
52
  score_value: 25
53
53
  )
54
54
 
@@ -1,6 +1,6 @@
1
1
  # Minimal performance testing focusing on core Familia functionality
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
  require 'benchmark'
5
5
 
6
6
  # Simple test classes without relationships feature
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Simplified performance testing for the Relationships feature
4
4
 
5
- require_relative '../../helpers/test_helpers'
5
+ require_relative '../../support/helpers/test_helpers'
6
6
  require 'benchmark'
7
7
 
8
8
  # Test classes for performance testing
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Performance and integration testing for the Relationships feature
4
4
 
5
- require_relative '../../helpers/test_helpers'
5
+ require_relative '../../support/helpers/test_helpers'
6
6
  require 'benchmark'
7
7
 
8
8
  # Test classes for performance testing
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Working performance test focusing on basic functionality
4
4
 
5
- require_relative '../../helpers/test_helpers'
5
+ require_relative '../../support/helpers/test_helpers'
6
6
  require 'benchmark'
7
7
 
8
8
  # Simple test class using only basic Familia features
@@ -3,7 +3,7 @@
3
3
  # Simplified Familia v2 relationship functionality tests - focusing on core working features
4
4
  #
5
5
 
6
- require_relative '../../helpers/test_helpers'
6
+ require_relative '../../support/helpers/test_helpers'
7
7
 
8
8
  # Test classes for Familia v2 relationship functionality
9
9
  class TestCustomer < Familia::Horreum
@@ -2,7 +2,7 @@
2
2
 
3
3
  # These tryouts test the safe dumping functionality.
4
4
 
5
- require_relative '../../helpers/test_helpers'
5
+ require_relative '../../support/helpers/test_helpers'
6
6
 
7
7
  ## By default Familia::Base has no safe_dump_fields method
8
8
  Familia::Base.respond_to?(:safe_dump_fields)
@@ -1,6 +1,6 @@
1
1
  # try/features/safe_dump_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/features/transient_fields/redacted_string_try.rb
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
 
6
6
  # Create sample sensitive values for testing
@@ -1,13 +1,15 @@
1
1
  # try/features/transient_fields/refresh_reset_try.rb
2
2
  # Test that refresh! properly resets transient fields to nil
3
3
 
4
- require_relative '../../helpers/test_helpers'
4
+ require_relative '../../support/helpers/test_helpers'
5
5
 
6
6
  Familia.debug = false
7
7
 
8
8
  Familia.dbclient.flushdb
9
9
 
10
10
  class SecretService < Familia::Horreum
11
+ feature :transient_fields
12
+
11
13
  identifier_field :name
12
14
 
13
15
  field :name
@@ -1,6 +1,6 @@
1
1
  # try/features/transient_fields/single_use_redacted_string_try.rb
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  @otp_code = "123456"
6
6
  @auth_token = "temp-auth-token-xyz"
@@ -1,6 +1,6 @@
1
1
  # try/features/transient_fields_core_try.rb
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  class SecretService < Familia::Horreum
6
6
  feature :transient_fields