familia 2.0.0.pre15 → 2.0.0.pre17

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 (288) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.github/workflows/code-quality.yml +138 -0
  4. data/.github/workflows/code-smells.yml +85 -0
  5. data/.github/workflows/docs.yml +31 -8
  6. data/.gitignore +3 -1
  7. data/.pre-commit-config.yaml +7 -1
  8. data/.reek.yml +98 -0
  9. data/.rubocop.yml +54 -10
  10. data/.talismanrc +9 -0
  11. data/.yardopts +18 -13
  12. data/CHANGELOG.rst +86 -4
  13. data/CLAUDE.md +39 -1
  14. data/Gemfile +6 -5
  15. data/Gemfile.lock +99 -23
  16. data/LICENSE.txt +1 -1
  17. data/README.md +285 -85
  18. data/changelog.d/README.md +2 -2
  19. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  20. data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
  21. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  22. data/docs/archive/README.md +3 -2
  23. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  24. data/docs/conf.py +29 -0
  25. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  26. data/docs/guides/feature-encrypted-fields.md +785 -0
  27. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  28. data/docs/guides/feature-external-identifiers.md +637 -0
  29. data/docs/guides/feature-object-identifiers.md +435 -0
  30. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  31. data/docs/guides/feature-relationships-methods.md +684 -0
  32. data/docs/guides/feature-relationships.md +200 -0
  33. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  34. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  35. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  36. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  37. data/docs/guides/index.md +176 -0
  38. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  39. data/docs/migrating/v2.0.0-pre.md +1 -1
  40. data/docs/migrating/v2.0.0-pre11.md +2 -2
  41. data/docs/migrating/v2.0.0-pre12.md +2 -2
  42. data/docs/migrating/v2.0.0-pre5.md +33 -12
  43. data/docs/migrating/v2.0.0-pre6.md +2 -2
  44. data/docs/migrating/v2.0.0-pre7.md +8 -8
  45. data/docs/overview.md +624 -20
  46. data/docs/reference/api-technical.md +1365 -0
  47. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  48. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  49. data/examples/autoloader/mega_customer.rb +3 -1
  50. data/examples/encrypted_fields.rb +378 -0
  51. data/examples/json_usage_patterns.rb +144 -0
  52. data/examples/relationships.rb +13 -13
  53. data/examples/safe_dump.rb +7 -7
  54. data/examples/single_connection_transaction_confusions.rb +379 -0
  55. data/lib/familia/base.rb +51 -10
  56. data/lib/familia/connection/handlers.rb +223 -0
  57. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  58. data/lib/familia/connection/middleware.rb +75 -0
  59. data/lib/familia/connection/operation_core.rb +93 -0
  60. data/lib/familia/connection/operations.rb +277 -0
  61. data/lib/familia/connection/pipeline_core.rb +87 -0
  62. data/lib/familia/connection/transaction_core.rb +100 -0
  63. data/lib/familia/connection.rb +60 -186
  64. data/lib/familia/data_type/class_methods.rb +63 -0
  65. data/lib/familia/data_type/commands.rb +53 -51
  66. data/lib/familia/data_type/connection.rb +83 -0
  67. data/lib/familia/data_type/serialization.rb +108 -107
  68. data/lib/familia/data_type/settings.rb +96 -0
  69. data/lib/familia/data_type/types/counter.rb +1 -1
  70. data/lib/familia/data_type/types/hashkey.rb +15 -11
  71. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  72. data/lib/familia/data_type/types/lock.rb +3 -2
  73. data/lib/familia/data_type/types/sorted_set.rb +128 -14
  74. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
  75. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  76. data/lib/familia/data_type.rb +12 -171
  77. data/lib/familia/distinguisher.rb +85 -0
  78. data/lib/familia/encryption/encrypted_data.rb +15 -24
  79. data/lib/familia/encryption/manager.rb +6 -4
  80. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  81. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  82. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  83. data/lib/familia/encryption/request_cache.rb +7 -7
  84. data/lib/familia/encryption.rb +2 -3
  85. data/lib/familia/errors.rb +9 -3
  86. data/lib/familia/features/autoloader.rb +30 -12
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  89. data/lib/familia/features/encrypted_fields.rb +71 -66
  90. data/lib/familia/features/expiration/extensions.rb +1 -1
  91. data/lib/familia/features/expiration.rb +31 -26
  92. data/lib/familia/features/external_identifier.rb +57 -19
  93. data/lib/familia/features/object_identifier.rb +134 -25
  94. data/lib/familia/features/quantization.rb +16 -21
  95. data/lib/familia/features/relationships/README.md +97 -0
  96. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  98. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
  99. data/lib/familia/features/relationships/indexing.rb +182 -256
  100. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  101. data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
  102. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  103. data/lib/familia/features/relationships/participation.rb +656 -0
  104. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  105. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  106. data/lib/familia/features/relationships.rb +65 -266
  107. data/lib/familia/features/safe_dump.rb +127 -130
  108. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  109. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  110. data/lib/familia/features/transient_fields.rb +10 -7
  111. data/lib/familia/features.rb +10 -14
  112. data/lib/familia/field_type.rb +6 -4
  113. data/lib/familia/horreum/connection.rb +297 -0
  114. data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
  115. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
  116. data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
  117. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
  118. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
  119. data/lib/familia/horreum/serialization.rb +172 -0
  120. data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
  121. data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
  122. data/lib/familia/horreum.rb +222 -119
  123. data/lib/familia/json_serializer.rb +0 -1
  124. data/lib/familia/logging.rb +11 -114
  125. data/lib/familia/refinements/dear_json.rb +122 -0
  126. data/lib/familia/refinements/logger_trace.rb +20 -17
  127. data/lib/familia/refinements/stylize_words.rb +65 -0
  128. data/lib/familia/refinements/time_literals.rb +60 -52
  129. data/lib/familia/refinements.rb +2 -1
  130. data/lib/familia/secure_identifier.rb +60 -28
  131. data/lib/familia/settings.rb +83 -7
  132. data/lib/familia/utils.rb +5 -87
  133. data/lib/familia/verifiable_identifier.rb +4 -4
  134. data/lib/familia/version.rb +1 -1
  135. data/lib/familia.rb +72 -14
  136. data/lib/middleware/database_middleware.rb +56 -14
  137. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  138. data/try/configuration/scenarios_try.rb +2 -2
  139. data/try/connection/fiber_context_preservation_try.rb +250 -0
  140. data/try/connection/handler_constraints_try.rb +59 -0
  141. data/try/connection/operation_mode_guards_try.rb +208 -0
  142. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  143. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  144. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  145. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  146. data/try/connection/transaction_mode_strict_try.rb +98 -0
  147. data/try/connection/transaction_mode_warn_try.rb +131 -0
  148. data/try/connection/transaction_modes_try.rb +249 -0
  149. data/try/core/autoloader_try.rb +120 -2
  150. data/try/core/connection_try.rb +10 -10
  151. data/try/core/conventional_inheritance_try.rb +130 -0
  152. data/try/core/create_method_try.rb +15 -23
  153. data/try/core/database_consistency_try.rb +11 -10
  154. data/try/core/errors_try.rb +11 -14
  155. data/try/core/familia_extended_try.rb +2 -2
  156. data/try/core/familia_members_methods_try.rb +76 -0
  157. data/try/core/familia_try.rb +1 -1
  158. data/try/core/isolated_dbclient_try.rb +165 -0
  159. data/try/core/middleware_try.rb +16 -16
  160. data/try/core/persistence_operations_try.rb +4 -4
  161. data/try/core/pools_try.rb +42 -26
  162. data/try/core/secure_identifier_try.rb +28 -24
  163. data/try/core/time_utils_try.rb +10 -10
  164. data/try/core/tools_try.rb +3 -3
  165. data/try/core/utils_try.rb +2 -2
  166. data/try/data_types/boolean_try.rb +4 -4
  167. data/try/data_types/datatype_base_try.rb +0 -2
  168. data/try/data_types/list_try.rb +10 -10
  169. data/try/data_types/sorted_set_try.rb +5 -5
  170. data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
  171. data/try/data_types/string_try.rb +12 -12
  172. data/try/data_types/unsortedset_try.rb +33 -0
  173. data/try/debugging/cache_behavior_tracer.rb +7 -7
  174. data/try/debugging/debug_aad_process.rb +1 -1
  175. data/try/debugging/debug_concealed_internal.rb +1 -1
  176. data/try/debugging/debug_cross_context.rb +1 -1
  177. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  178. data/try/debugging/encryption_method_tracer.rb +10 -10
  179. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  180. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  181. data/try/encryption/config_persistence_try.rb +2 -2
  182. data/try/encryption/encryption_core_try.rb +19 -19
  183. data/try/encryption/instance_variable_scope_try.rb +1 -1
  184. data/try/encryption/module_loading_try.rb +2 -2
  185. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  186. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  187. data/try/encryption/secure_memory_handling_try.rb +1 -1
  188. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  189. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  190. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  191. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  192. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  193. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  194. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  195. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  196. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  197. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  198. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  199. data/try/features/feature_dependencies_try.rb +3 -3
  200. data/try/features/field_groups_try.rb +244 -0
  201. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  202. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  203. data/try/features/quantization/quantization_try.rb +1 -1
  204. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  205. data/try/features/relationships/indexing_try.rb +443 -0
  206. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  207. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  208. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  209. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  210. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  211. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  212. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  213. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  214. data/try/features/relationships/relationships_performance_try.rb +20 -20
  215. data/try/features/relationships/relationships_try.rb +27 -38
  216. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  217. data/try/features/transient_fields/refresh_reset_try.rb +3 -1
  218. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  219. data/try/helpers/test_cleanup.rb +86 -0
  220. data/try/helpers/test_helpers.rb +6 -7
  221. data/try/horreum/auto_indexing_on_save_try.rb +212 -0
  222. data/try/horreum/base_try.rb +3 -2
  223. data/try/horreum/commands_try.rb +3 -1
  224. data/try/horreum/defensive_initialization_try.rb +86 -0
  225. data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
  226. data/try/horreum/initialization_try.rb +11 -7
  227. data/try/horreum/relations_try.rb +21 -13
  228. data/try/horreum/serialization_try.rb +12 -11
  229. data/try/horreum/settings_try.rb +2 -0
  230. data/try/integration/cross_component_try.rb +3 -3
  231. data/try/memory/memory_basic_test.rb +1 -1
  232. data/try/memory/memory_docker_ruby_dump.sh +2 -2
  233. data/try/models/customer_safe_dump_try.rb +1 -1
  234. data/try/models/customer_try.rb +13 -15
  235. data/try/models/datatype_base_try.rb +3 -3
  236. data/try/models/familia_object_try.rb +9 -8
  237. data/try/performance/benchmarks_try.rb +2 -2
  238. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  239. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  240. data/try/prototypes/atomic_saves_v4.rb +1 -1
  241. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  242. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  243. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  244. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  245. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  246. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  247. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  248. data/try/prototypes/pooling/pool_siege.rb +11 -11
  249. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  250. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  251. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  252. data/try/refinements/logger_trace_methods_try.rb +44 -0
  253. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  254. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  255. data/try/valkey.conf +26 -0
  256. metadata +92 -52
  257. data/.rubocop_todo.yml +0 -208
  258. data/docs/connection_pooling.md +0 -192
  259. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  260. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  261. data/docs/guides/Feature-System-Autoloading.md +0 -198
  262. data/docs/guides/Home.md +0 -116
  263. data/docs/guides/Relationships-Guide.md +0 -737
  264. data/docs/guides/relationships-methods.md +0 -266
  265. data/docs/reference/auditing_database_commands.rb +0 -228
  266. data/examples/permissions.rb +0 -240
  267. data/lib/familia/features/relationships/cascading.rb +0 -437
  268. data/lib/familia/features/relationships/membership.rb +0 -497
  269. data/lib/familia/features/relationships/permission_management.rb +0 -264
  270. data/lib/familia/features/relationships/querying.rb +0 -615
  271. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  272. data/lib/familia/features/relationships/tracking.rb +0 -418
  273. data/lib/familia/horreum/core/connection.rb +0 -73
  274. data/lib/familia/horreum/core.rb +0 -21
  275. data/lib/familia/refinements/snake_case.rb +0 -40
  276. data/lib/familia/validation/command_recorder.rb +0 -336
  277. data/lib/familia/validation/expectations.rb +0 -519
  278. data/lib/familia/validation/validation_helpers.rb +0 -443
  279. data/lib/familia/validation/validator.rb +0 -412
  280. data/lib/familia/validation.rb +0 -140
  281. data/try/data_types/set_try.rb +0 -33
  282. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  283. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  284. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  285. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  286. data/try/validation/command_validation_try.rb.disabled +0 -207
  287. data/try/validation/performance_validation_try.rb.disabled +0 -324
  288. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -1,240 +0,0 @@
1
- # examples/bit_encoding_integration.rb
2
- #
3
- # Production Integration Example: Document Management System with Fine-Grained Permissions
4
- #
5
- # This example demonstrates how to use Familia's bit encoding permission system
6
- # in a real-world document management scenario with sophisticated access control.
7
-
8
- require_relative '../lib/familia'
9
- require_relative '../lib/familia/features/relationships/score_encoding'
10
- require_relative '../lib/familia/features/relationships/permission_management'
11
-
12
- # Document Management System Classes
13
- class User < Familia::Horreum
14
- logical_database 14
15
-
16
- identifier_field :user_id
17
- field :user_id
18
- field :email
19
- field :name
20
- field :role # admin, editor, viewer, guest
21
- field :created_at
22
-
23
- sorted_set :documents # Documents this user can access
24
- sorted_set :recent_activity # Recent document access
25
- end
26
-
27
- class Document < Familia::Horreum
28
- include Familia::Features::Relationships::PermissionManagement
29
-
30
- logical_database 14
31
-
32
- # Enable fine-grained permission tracking
33
- permission_tracking :user_permissions
34
-
35
- identifier_field :doc_id
36
- field :doc_id
37
- field :title
38
- field :owner_id
39
- field :content
40
- field :created_at
41
- field :updated_at
42
- field :document_type # public, private, confidential
43
-
44
- sorted_set :collaborators # Users with access to this document
45
- list :audit_log # Track permission changes and access
46
-
47
- # Add document to user's collection with specific permissions
48
- def share_with_user(user, *permissions)
49
- permissions = [:read] if permissions.empty?
50
-
51
- # Create time-based score with permissions encoded
52
- timestamp = updated_at || Time.now
53
- score = Familia::Features::Relationships::ScoreEncoding.encode_score(timestamp, permissions)
54
-
55
- # Add to user's document list
56
- user.documents.add(score, doc_id)
57
-
58
- # Add user to document's collaborator list
59
- collaborators.add(score, user.user_id)
60
-
61
- # Grant permissions via permission management
62
- grant(user, *permissions)
63
-
64
- # Log the permission grant
65
- log_entry = "#{Time.now.iso8601}: Granted #{permissions.join(', ')} to #{user.email}"
66
- audit_log.push(log_entry)
67
- end
68
-
69
- # Remove user access
70
- def revoke_access(user)
71
- user.documents.zrem(doc_id)
72
- collaborators.zrem(user.user_id)
73
- revoke(user, :read, :write, :edit, :delete, :configure, :transfer, :admin)
74
-
75
- log_entry = "#{Time.now.iso8601}: Revoked all access from #{user.email}"
76
- audit_log.push(log_entry)
77
- end
78
-
79
- # Get users with specific permission level or higher
80
- def users_with_permission(*required_permissions)
81
- all_permissions.select do |_user_id, user_perms|
82
- required_permissions.all? { |perm| user_perms.include?(perm) }
83
- end.keys
84
- end
85
-
86
- # Advanced: Get document access history for analytics
87
- def access_analytics(days_back = 30)
88
- start_time = Time.now - (days_back * 24 * 60 * 60)
89
- end_time = Time.now
90
-
91
- # Use score range to get recent access
92
- range = Familia::Features::Relationships::ScoreEncoding.score_range(
93
- start_time,
94
- end_time,
95
- min_permissions: [:read]
96
- )
97
-
98
- # Get collaborators active in time range
99
- active_users = collaborators.rangebyscore(*range)
100
-
101
- {
102
- active_users: active_users,
103
- total_collaborators: collaborators.size,
104
- permission_breakdown: all_permissions,
105
- audit_entries: audit_log.range(0, 50),
106
- }
107
- end
108
- end
109
-
110
- # Document Management Service - Business Logic Layer
111
- class DocumentService
112
- # Permission role definitions matching business needs
113
- ROLE_PERMISSIONS = {
114
- guest: [:read],
115
- viewer: [:read],
116
- commenter: %i[read append],
117
- editor: %i[read write edit],
118
- reviewer: %i[read write edit delete],
119
- admin: %i[read write edit delete configure transfer admin],
120
- }.freeze
121
-
122
- def self.create_document(owner, title, content, doc_type = 'private')
123
- doc = Document.new(
124
- doc_id: "doc_#{Time.now.to_i}_#{rand(1000)}",
125
- title: title,
126
- content: content,
127
- owner_id: owner.user_id,
128
- document_type: doc_type,
129
- created_at: Time.now,
130
- updated_at: Time.now
131
- )
132
-
133
- # Owner gets full admin access
134
- doc.share_with_user(owner, *ROLE_PERMISSIONS[:admin])
135
- doc
136
- end
137
-
138
- def self.share_document(document, user, role)
139
- permissions = ROLE_PERMISSIONS[role] || ROLE_PERMISSIONS[:viewer]
140
- document.share_with_user(user, *permissions)
141
- end
142
-
143
- def self.can_user_perform?(user, document, action)
144
- case action
145
- when :view, :read
146
- document.can?(user, :read)
147
- when :comment, :append
148
- document.can?(user, :read, :append)
149
- when :edit, :modify
150
- document.can?(user, :read, :write, :edit)
151
- when :delete, :remove
152
- document.can?(user, :delete)
153
- when :share, :configure
154
- document.can?(user, :configure)
155
- when :transfer_ownership
156
- document.can?(user, :admin)
157
- else
158
- false
159
- end
160
- end
161
-
162
- def self.bulk_permission_update(documents, users, role)
163
- permissions = ROLE_PERMISSIONS[role]
164
-
165
- documents.each do |doc|
166
- users.each do |user|
167
- doc.revoke_access(user) # Clear existing
168
- doc.share_with_user(user, *permissions) if permissions
169
- end
170
- end
171
- end
172
- end
173
-
174
- # Example Usage and Demonstration
175
- if __FILE__ == $0
176
- puts '🚀 Familia Bit Encoding Integration Example'
177
- puts '=' * 50
178
-
179
- # Create users
180
- alice = User.new(user_id: 'alice', email: 'alice@company.com', name: 'Alice Smith', role: 'admin')
181
- bob = User.new(user_id: 'bob', email: 'bob@company.com', name: 'Bob Jones', role: 'editor')
182
- charlie = User.new(user_id: 'charlie', email: 'charlie@company.com', name: 'Charlie Brown', role: 'viewer')
183
-
184
- # Create documents
185
- doc1 = DocumentService.create_document(alice, 'Q4 Financial Report', 'Confidential financial data...', 'confidential')
186
- doc2 = DocumentService.create_document(alice, 'Team Meeting Notes', 'Weekly standup notes...', 'private')
187
- doc3 = DocumentService.create_document(bob, 'Project Proposal', 'New feature proposal...', 'public')
188
-
189
- # Share documents with different permission levels
190
- puts "\n📄 Document Sharing:"
191
- DocumentService.share_document(doc1, bob, :reviewer) # Bob can review financial report
192
- DocumentService.share_document(doc1, charlie, :viewer) # Charlie can only view
193
-
194
- DocumentService.share_document(doc2, bob, :editor) # Bob can edit meeting notes
195
- DocumentService.share_document(doc2, charlie, :commenter) # Charlie can comment
196
-
197
- DocumentService.share_document(doc3, alice, :admin) # Alice gets admin on Bob's doc
198
- DocumentService.share_document(doc3, charlie, :editor) # Charlie can edit proposal
199
-
200
- # Test permission checks
201
- puts "\n🔐 Permission Testing:"
202
- puts "Can Bob edit financial report? #{DocumentService.can_user_perform?(bob, doc1, :edit)}"
203
- puts "Can Bob delete financial report? #{DocumentService.can_user_perform?(bob, doc1, :delete)}"
204
- puts "Can Charlie comment on meeting notes? #{DocumentService.can_user_perform?(charlie, doc2, :comment)}"
205
- puts "Can Charlie edit project proposal? #{DocumentService.can_user_perform?(charlie, doc3, :edit)}"
206
-
207
- # Advanced analytics
208
- puts "\n📊 Document Analytics:"
209
- analytics = doc1.access_analytics
210
- puts "Financial Report - Active Users: #{analytics[:active_users].size}"
211
- puts "Total Collaborators: #{analytics[:total_collaborators]}"
212
- puts 'Permission Breakdown:'
213
- analytics[:permission_breakdown].each do |user_id, perms|
214
- puts " #{user_id}: #{perms.join(', ')}"
215
- end
216
-
217
- # Demonstrate bit encoding efficiency
218
- puts "\n⚡ Bit Encoding Efficiency:"
219
- score = Familia::Features::Relationships::ScoreEncoding.encode_score(Time.now, %i[read write edit delete])
220
- decoded = Familia::Features::Relationships::ScoreEncoding.decode_score(score)
221
- puts "Encoded score: #{score}"
222
- puts "Decoded permissions: #{decoded[:permission_list].join(', ')}"
223
- puts "Permission bits: #{decoded[:permissions]} (#{decoded[:permissions].to_s(2).rjust(8, '0')})"
224
-
225
- # Cleanup
226
- puts "\n🧹 Cleanup:"
227
- [alice, bob, charlie].each { |user| user.documents.clear }
228
- [doc1, doc2, doc3].each do |doc|
229
- doc.clear_all_permissions
230
- doc.collaborators.clear
231
- end
232
-
233
- puts '✅ Integration example completed successfully!'
234
- puts "\nThis demonstrates:"
235
- puts '• Fine-grained permission management with 8-bit encoding'
236
- puts '• Role-based access control with business logic'
237
- puts '• Time-based analytics and audit trails'
238
- puts '• Efficient Redis storage with sorted sets'
239
- puts '• Production-ready error handling and validation'
240
- end
@@ -1,437 +0,0 @@
1
- # lib/familia/features/relationships/cascading.rb
2
-
3
- module Familia
4
- module Features
5
- module Relationships
6
- # Cascading module for handling cascade operations during object lifecycle
7
- # Supports multi-presence scenarios where objects exist in multiple collections
8
- module Cascading
9
- # Cascade strategies
10
- STRATEGIES = {
11
- remove: :remove_from_collections,
12
- ignore: :ignore_collections,
13
- cascade: :cascade_destroy_dependents
14
- }.freeze
15
-
16
- # Class-level cascade configurations
17
- def self.included(base)
18
- base.extend ClassMethods
19
- base.include InstanceMethods
20
- super
21
- end
22
-
23
- module ClassMethods
24
- # Get cascade strategies for all relationships
25
- def cascade_strategies
26
- strategies = {}
27
-
28
- # Collect strategies from tracking relationships
29
- if respond_to?(:tracking_relationships)
30
- tracking_relationships.each do |config|
31
- key = "#{config[:context_class_name]}.#{config[:collection_name]}"
32
- strategies[key] = {
33
- type: :tracking,
34
- strategy: config[:on_destroy] || :remove,
35
- config: config
36
- }
37
- end
38
- end
39
-
40
- # Collect strategies from membership relationships
41
- if respond_to?(:membership_relationships)
42
- membership_relationships.each do |config|
43
- key = "#{config[:owner_class_name]}.#{config[:collection_name]}"
44
- strategies[key] = {
45
- type: :membership,
46
- strategy: config[:on_destroy] || :remove,
47
- config: config
48
- }
49
- end
50
- end
51
-
52
- # Collect strategies from indexing relationships
53
- if respond_to?(:indexing_relationships)
54
- indexing_relationships.each do |config|
55
- key = if config[:context_class_name] == 'global'
56
- "global.#{config[:index_name]}"
57
- else
58
- "#{config[:context_class_name]}.#{config[:index_name]}"
59
- end
60
- strategies[key] = {
61
- type: :indexing,
62
- strategy: :remove, # Indexes should always be cleaned up
63
- config: config
64
- }
65
- end
66
- end
67
-
68
- strategies
69
- end
70
- end
71
-
72
- # Instance methods for cascade operations
73
- module InstanceMethods
74
- # Execute cascade operations during destroy
75
- def execute_cascade_operations
76
- strategies = self.class.cascade_strategies
77
-
78
- # Group operations by strategy for efficient execution
79
- remove_operations = []
80
- cascade_operations = []
81
-
82
- strategies.each_value do |strategy_info|
83
- case strategy_info[:strategy]
84
- when :remove
85
- remove_operations << strategy_info
86
- when :cascade
87
- cascade_operations << strategy_info
88
- when :ignore
89
- # Do nothing
90
- end
91
- end
92
-
93
- # Execute remove operations first (cleanup this object's presence)
94
- execute_remove_operations(remove_operations) if remove_operations.any?
95
-
96
- # Then execute cascade operations (may trigger other destroys)
97
- execute_cascade_operations_recursive(cascade_operations) if cascade_operations.any?
98
- end
99
-
100
- # Remove this object from all collections without cascading
101
- def remove_from_all_collections
102
- strategies = self.class.cascade_strategies
103
- remove_operations = strategies.values.reject { |s| s[:strategy] == :ignore }
104
- execute_remove_operations(remove_operations)
105
- end
106
-
107
- # Check if destroying this object would trigger cascades
108
- def cascade_impact
109
- strategies = self.class.cascade_strategies
110
- impact = {
111
- removals: 0,
112
- cascades: 0,
113
- affected_collections: [],
114
- cascade_targets: []
115
- }
116
-
117
- strategies.each do |key, strategy_info|
118
- case strategy_info[:strategy]
119
- when :remove
120
- impact[:removals] += 1
121
- impact[:affected_collections] << key
122
- when :cascade
123
- impact[:cascades] += 1
124
- impact[:affected_collections] << key
125
-
126
- # Estimate cascade targets (this is expensive, use carefully)
127
- targets = estimate_cascade_targets(strategy_info)
128
- impact[:cascade_targets].concat(targets)
129
- end
130
- end
131
-
132
- impact
133
- end
134
-
135
- private
136
-
137
- # Execute removal operations atomically
138
- def execute_remove_operations(remove_operations)
139
- return if remove_operations.empty?
140
-
141
- dbclient.pipelined do |pipeline|
142
- remove_operations.each do |operation|
143
- case operation[:type]
144
- when :tracking
145
- remove_from_tracking_collections(pipeline, operation[:config])
146
- when :membership
147
- remove_from_membership_collections(pipeline, operation[:config])
148
- when :indexing
149
- remove_from_indexing_collections(pipeline, operation[:config])
150
- end
151
- end
152
- end
153
- end
154
-
155
- # Remove from tracking collections
156
- def remove_from_tracking_collections(pipeline, config)
157
- context_class_name = config[:context_class_name]
158
- collection_name = config[:collection_name]
159
-
160
- # Find all collections this object is tracked in
161
- pattern = "#{context_class_name.downcase}:*:#{collection_name}"
162
-
163
- dbclient.scan_each(match: pattern) do |key|
164
- pipeline.zrem(key, identifier)
165
- end
166
- end
167
-
168
- # Remove from membership collections
169
- def remove_from_membership_collections(pipeline, config)
170
- owner_class_name = config[:owner_class_name]
171
- collection_name = config[:collection_name]
172
- type = config[:type]
173
-
174
- # Find all collections this object is a member of
175
- pattern = "#{owner_class_name.downcase}:*:#{collection_name}"
176
-
177
- dbclient.scan_each(match: pattern) do |key|
178
- case type
179
- when :sorted_set
180
- pipeline.zrem(key, identifier)
181
- when :set
182
- pipeline.srem(key, identifier)
183
- when :list
184
- pipeline.lrem(key, 0, identifier)
185
- end
186
- end
187
- end
188
-
189
- # Remove from indexing collections
190
- def remove_from_indexing_collections(pipeline, config)
191
- context_class_name = config[:context_class_name]
192
- index_name = config[:index_name]
193
- field = config[:field]
194
-
195
- field_value = send(field) if respond_to?(field)
196
- return unless field_value
197
-
198
- if context_class_name == 'global'
199
- index_key = "global:#{index_name}"
200
- pipeline.hdel(index_key, field_value.to_s)
201
- else
202
- # Find all indexes this object appears in
203
- pattern = "#{context_class_name.downcase}:*:#{index_name}"
204
-
205
- dbclient.scan_each(match: pattern) do |key|
206
- pipeline.hdel(key, field_value.to_s)
207
- end
208
- end
209
- end
210
-
211
- # Execute cascade operations that may trigger dependent destroys
212
- def execute_cascade_operations_recursive(cascade_operations)
213
- cascade_operations.each do |operation|
214
- case operation[:type]
215
- when :tracking
216
- cascade_tracking_dependents(operation[:config])
217
- when :membership
218
- cascade_membership_dependents(operation[:config])
219
- end
220
- end
221
- end
222
-
223
- # Cascade destroy for tracking relationships
224
- def cascade_tracking_dependents(config)
225
- # This is a complex operation that depends on the specific business logic
226
- # For now, we'll provide a framework that can be customized
227
-
228
- context_class_name = config[:context_class_name]
229
- collection_name = config[:collection_name]
230
-
231
- # Find all contexts that track this object
232
- pattern = "#{context_class_name.downcase}:*:#{collection_name}"
233
-
234
- dbclient.scan_each(match: pattern) do |key|
235
- # Check if this object is the only member
236
- if dbclient.zcard(key) == 1 && dbclient.zscore(key, identifier)
237
- context_id = key.split(':')[1]
238
-
239
- # Optionally destroy the context if it becomes empty
240
- # This is application-specific logic
241
- trigger_cascade_callback(:tracking, context_class_name, context_id, collection_name)
242
- end
243
- end
244
- end
245
-
246
- # Cascade destroy for membership relationships
247
- def cascade_membership_dependents(config)
248
- # Similar to tracking, this depends on business logic
249
-
250
- owner_class_name = config[:owner_class_name]
251
- collection_name = config[:collection_name]
252
- type = config[:type]
253
-
254
- # Find all owners that contain this object
255
- pattern = "#{owner_class_name.downcase}:*:#{collection_name}"
256
-
257
- dbclient.scan_each(match: pattern) do |key|
258
- # Check if this object exists in the collection
259
- is_member = case type
260
- when :sorted_set
261
- dbclient.zscore(key, identifier) != nil
262
- when :set
263
- dbclient.sismember(key, identifier)
264
- when :list
265
- dbclient.lpos(key, identifier) != nil
266
- end
267
-
268
- if is_member
269
- owner_id = key.split(':')[1]
270
- trigger_cascade_callback(:membership, owner_class_name, owner_id, collection_name)
271
- end
272
- end
273
- end
274
-
275
- # Trigger application-specific cascade callbacks
276
- def trigger_cascade_callback(relationship_type, class_name, object_id, collection_name)
277
- # This method can be overridden by applications to implement
278
- # custom cascade logic
279
-
280
- callback_method = "on_cascade_#{relationship_type}_#{collection_name}"
281
-
282
- return unless respond_to?(callback_method, true)
283
-
284
- send(callback_method, class_name, object_id)
285
- end
286
-
287
- # Estimate objects that would be affected by cascading (expensive operation)
288
- def estimate_cascade_targets(strategy_info)
289
- targets = []
290
-
291
- case strategy_info[:type]
292
- when :tracking
293
- config = strategy_info[:config]
294
- context_class_name = config[:context_class_name]
295
- collection_name = config[:collection_name]
296
-
297
- pattern = "#{context_class_name.downcase}:*:#{collection_name}"
298
- dbclient.scan_each(match: pattern) do |key|
299
- if dbclient.zscore(key, identifier)
300
- context_id = key.split(':')[1]
301
- targets << {
302
- type: :context,
303
- class: context_class_name,
304
- id: context_id,
305
- collection: collection_name
306
- }
307
- end
308
- end
309
-
310
- when :membership
311
- config = strategy_info[:config]
312
- owner_class_name = config[:owner_class_name]
313
- collection_name = config[:collection_name]
314
- type = config[:type]
315
-
316
- pattern = "#{owner_class_name.downcase}:*:#{collection_name}"
317
- dbclient.scan_each(match: pattern) do |key|
318
- is_member = case type
319
- when :sorted_set
320
- dbclient.zscore(key, identifier) != nil
321
- when :set
322
- dbclient.sismember(key, identifier)
323
- when :list
324
- dbclient.lpos(key, identifier) != nil
325
- end
326
-
327
- if is_member
328
- owner_id = key.split(':')[1]
329
- targets << {
330
- type: :owner,
331
- class: owner_class_name,
332
- id: owner_id,
333
- collection: collection_name
334
- }
335
- end
336
- end
337
- end
338
-
339
- targets
340
- end
341
-
342
- # Dry run cascade operations (for testing/preview)
343
- def cascade_dry_run
344
- strategies = self.class.cascade_strategies
345
-
346
- preview = {
347
- removals: [],
348
- cascades: [],
349
- affected_keys: []
350
- }
351
-
352
- strategies.each do |key, strategy_info|
353
- case strategy_info[:strategy]
354
- when :remove
355
- affected_keys = find_affected_keys(strategy_info)
356
- preview[:removals] << {
357
- relationship: key,
358
- keys: affected_keys,
359
- count: affected_keys.length
360
- }
361
- preview[:affected_keys].concat(affected_keys)
362
-
363
- when :cascade
364
- cascade_targets = estimate_cascade_targets(strategy_info)
365
- preview[:cascades] << {
366
- relationship: key,
367
- targets: cascade_targets,
368
- count: cascade_targets.length
369
- }
370
- end
371
- end
372
-
373
- preview[:affected_keys].uniq!
374
- preview
375
- end
376
-
377
- # Find all Redis keys that would be affected by removing this object
378
- def find_affected_keys(strategy_info)
379
- affected_keys = []
380
-
381
- case strategy_info[:type]
382
- when :tracking
383
- config = strategy_info[:config]
384
- context_class_name = config[:context_class_name]
385
- collection_name = config[:collection_name]
386
- pattern = "#{context_class_name.downcase}:*:#{collection_name}"
387
-
388
- dbclient.scan_each(match: pattern) do |key|
389
- affected_keys << key if dbclient.zscore(key, identifier)
390
- end
391
-
392
- when :membership
393
- config = strategy_info[:config]
394
- owner_class_name = config[:owner_class_name]
395
- collection_name = config[:collection_name]
396
- type = config[:type]
397
- pattern = "#{owner_class_name.downcase}:*:#{collection_name}"
398
-
399
- dbclient.scan_each(match: pattern) do |key|
400
- is_member = case type
401
- when :sorted_set
402
- dbclient.zscore(key, identifier) != nil
403
- when :set
404
- dbclient.sismember(key, identifier)
405
- when :list
406
- dbclient.lpos(key, identifier) != nil
407
- end
408
- affected_keys << key if is_member
409
- end
410
-
411
- when :indexing
412
- config = strategy_info[:config]
413
- context_class_name = config[:context_class_name]
414
- index_name = config[:index_name]
415
- field = config[:field]
416
-
417
- field_value = send(field) if respond_to?(field)
418
- if field_value
419
- if context_class_name == 'global'
420
- index_key = "global:#{index_name}"
421
- affected_keys << index_key if dbclient.hexists(index_key, field_value.to_s)
422
- else
423
- pattern = "#{context_class_name.downcase}:*:#{index_name}"
424
- dbclient.scan_each(match: pattern) do |key|
425
- affected_keys << key if dbclient.hexists(key, field_value.to_s)
426
- end
427
- end
428
- end
429
- end
430
-
431
- affected_keys
432
- end
433
- end
434
- end
435
- end
436
- end
437
- end