familia 2.0.0.pre8 → 2.0.0.pre10

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +13 -0
  3. data/.github/workflows/docs.yml +1 -1
  4. data/.gitignore +9 -9
  5. data/.rubocop.yml +19 -0
  6. data/.yardopts +22 -1
  7. data/CHANGELOG.md +184 -0
  8. data/CLAUDE.md +8 -5
  9. data/Gemfile.lock +1 -1
  10. data/README.md +62 -2
  11. data/changelog.d/README.md +66 -0
  12. data/changelog.d/fragments/.keep +0 -0
  13. data/changelog.d/template.md.j2 +29 -0
  14. data/docs/archive/.gitignore +2 -0
  15. data/docs/archive/FAMILIA_RELATIONSHIPS.md +210 -0
  16. data/docs/archive/FAMILIA_TECHNICAL.md +823 -0
  17. data/docs/archive/FAMILIA_UPDATE.md +226 -0
  18. data/docs/archive/README.md +67 -0
  19. data/docs/guides/.gitignore +2 -0
  20. data/docs/{wiki → guides}/Relationships-Guide.md +103 -50
  21. data/docs/guides/relationships-methods.md +266 -0
  22. data/examples/relationships_basic.rb +90 -157
  23. data/familia.gemspec +4 -4
  24. data/lib/familia/connection.rb +4 -21
  25. data/lib/familia/features/relationships/indexing.rb +160 -175
  26. data/lib/familia/features/relationships/membership.rb +16 -21
  27. data/lib/familia/features/relationships/tracking.rb +61 -21
  28. data/lib/familia/features/relationships.rb +15 -8
  29. data/lib/familia/horreum/subclass/definition.rb +2 -0
  30. data/lib/familia/horreum.rb +15 -24
  31. data/lib/familia/version.rb +1 -1
  32. data/setup.cfg +12 -0
  33. data/try/features/relationships/relationships_api_changes_try.rb +339 -0
  34. data/try/features/relationships/relationships_try.rb +6 -5
  35. metadata +43 -30
  36. /data/docs/{wiki → guides}/API-Reference.md +0 -0
  37. /data/docs/{wiki → guides}/Connection-Pooling-Guide.md +0 -0
  38. /data/docs/{wiki → guides}/Encrypted-Fields-Overview.md +0 -0
  39. /data/docs/{wiki → guides}/Expiration-Feature-Guide.md +0 -0
  40. /data/docs/{wiki → guides}/Feature-System-Guide.md +0 -0
  41. /data/docs/{wiki → guides}/Features-System-Developer-Guide.md +0 -0
  42. /data/docs/{wiki → guides}/Field-System-Guide.md +0 -0
  43. /data/docs/{wiki → guides}/Home.md +0 -0
  44. /data/docs/{wiki → guides}/Implementation-Guide.md +0 -0
  45. /data/docs/{wiki → guides}/Quantization-Feature-Guide.md +0 -0
  46. /data/docs/{wiki → guides}/Security-Model.md +0 -0
  47. /data/docs/{wiki → guides}/Transient-Fields-Guide.md +0 -0
@@ -0,0 +1,210 @@
1
+ # Relationship methods
2
+
3
+ here are the methods automatically generated for each relationship type:
4
+
5
+ member_of Relationships
6
+
7
+ When you declare:
8
+ class Domain < Familia::Horreum
9
+ member_of Customer, :domains, type: :set
10
+ end
11
+
12
+ Generated methods on Domain instances:
13
+ - add_to_customer_domains(customer_id) - Add this domain to customer's domains collection
14
+ - remove_from_customer_domains(customer_id) - Remove this domain from customer's domains collection
15
+ - in_customer_domains?(customer_id) - Check if this domain is in customer's domains collection
16
+
17
+ The method names follow the pattern: {action}_to_{lowercase_class_name}_{collection_name}
18
+
19
+ tracked_in Relationships
20
+
21
+ When you declare:
22
+ class Customer < Familia::Horreum
23
+ tracked_in :all_customers, type: :sorted_set, score: :created_at
24
+ end
25
+
26
+ Generated class methods:
27
+ - Customer.add_to_all_customers(customer) - Add customer to global tracking
28
+ - Customer.remove_from_all_customers(customer) - Remove customer from global tracking
29
+ - Customer.all_customers - Access the sorted set collection directly
30
+
31
+ For scoped tracking (with class prefix):
32
+ tracked_in :global, :active_users, score: :last_seen
33
+ Generates: Customer.add_to_active_users(customer) and Customer.active_users
34
+
35
+ indexed_by Relationships
36
+
37
+ The `indexed_by` method creates Redis hash-based indexes for O(1) field lookups. The `context` parameter determines index ownership and scope.
38
+
39
+ **Global Context (Shared Index)**
40
+ When you declare:
41
+ ```ruby
42
+ class Customer < Familia::Horreum
43
+ indexed_by :email, :email_lookup, context: :global
44
+ end
45
+ ```
46
+
47
+ Generated class methods:
48
+ - Customer.add_to_email_lookup(customer) - Add customer to global email index
49
+ - Customer.remove_from_email_lookup(customer) - Remove customer from global email index
50
+ - Customer.email_lookup - Access the global hash index directly (supports .get(email))
51
+
52
+ Redis key pattern: `global:email_lookup`
53
+
54
+ **Class Context (Per-Instance Index)**
55
+ When you declare:
56
+ ```ruby
57
+ class Domain < Familia::Horreum
58
+ indexed_by :name, :domain_index, context: Customer
59
+ end
60
+ ```
61
+
62
+ Generated class methods on Customer:
63
+ - Customer.find_by_name(domain_name) - Find domain by name within this customer
64
+ - Customer.find_all_by_name(domain_names) - Find multiple domains by names
65
+
66
+ Redis key pattern: `customer:123:domain_index` (per customer instance)
67
+
68
+ **When to Use Each Context**
69
+ - **Global context (`:global`)**: Use for system-wide lookups where the field value should be unique across all instances
70
+ - Examples: email addresses, usernames, API keys
71
+ - **Class context (e.g., `Customer`)**: Use for scoped lookups where the field value is unique within a specific parent object
72
+ - Examples: domain names per customer, project names per team
73
+
74
+ Example from the Codebase
75
+
76
+ From the relationships example file, you can see this in action:
77
+
78
+ # Domain declares membership in Customer collections
79
+ class Domain < Familia::Horreum
80
+ member_of Customer, :domains, type: :set
81
+ end
82
+
83
+ # This generates these methods on Domain instances:
84
+ domain.add_to_customer_domains(customer.custid) # Add to relationship
85
+ domain.remove_from_customer_domains(customer.custid) # Remove from relationship
86
+ domain.in_customer_domains?(customer.custid) # Query membership
87
+
88
+ # For tracked_in relationships:
89
+ Customer.add_to_all_customers(customer) # Class method
90
+ Customer.all_customers.range(0, -1) # Direct collection access
91
+
92
+ # For indexed_by relationships:
93
+ Customer.add_to_email_lookup(customer) # Class method
94
+ Customer.email_lookup.get("user@example.com") # O(1) lookup
95
+
96
+ Method Naming Conventions
97
+
98
+ The relationship system uses consistent naming patterns:
99
+ - member_of: {add_to|remove_from|in}_#{parent_class.downcase}_#{collection_name}
100
+ - tracked_in: {add_to|remove_from}_#{collection_name} (class methods)
101
+ - indexed_by: {add_to|remove_from}_#{index_name} (class methods)
102
+
103
+ This automatic method generation creates a clean, predictable API that handles both the Redis operations and maintains referential consistency
104
+ across related objects.
105
+
106
+
107
+ ## Context Parameter Usage Patterns
108
+
109
+ The `context` parameter in `indexed_by` is a fundamental architectural decision that determines index scope and ownership. Here are practical patterns for when to use each approach:
110
+
111
+ ### Global Context Pattern
112
+ Use `context: :global` when field values should be unique system-wide:
113
+
114
+ ```ruby
115
+ class User < Familia::Horreum
116
+ feature :relationships
117
+
118
+ identifier_field :user_id
119
+ field :user_id, :email, :username
120
+
121
+ # System-wide unique email lookup
122
+ indexed_by :email, :email_lookup, context: :global
123
+ indexed_by :username, :username_lookup, context: :global
124
+ end
125
+
126
+ # Usage:
127
+ User.add_to_email_lookup(user)
128
+ found_user_id = User.email_lookup.get("john@example.com")
129
+ ```
130
+
131
+ **Redis keys generated**: `global:email_lookup`, `global:username_lookup`
132
+
133
+ ### Class Context Pattern
134
+ Use `context: SomeClass` when field values are unique within a specific parent context:
135
+
136
+ ```ruby
137
+ class Customer < Familia::Horreum
138
+ feature :relationships
139
+
140
+ identifier_field :custid
141
+ field :custid, :name
142
+ sorted_set :domains
143
+ end
144
+
145
+ class Domain < Familia::Horreum
146
+ feature :relationships
147
+
148
+ identifier_field :domain_id
149
+ field :domain_id, :name, :subdomain
150
+
151
+ # Domains are unique per customer (customer can't have duplicate domain names)
152
+ indexed_by :name, :domain_index, context: Customer
153
+ indexed_by :subdomain, :subdomain_index, context: Customer
154
+ end
155
+
156
+ # Usage:
157
+ customer = Customer.new(custid: "cust_123")
158
+ customer.find_by_name("example.com") # Find domain within this customer
159
+ customer.find_all_by_subdomain(["www", "api"]) # Find multiple subdomains
160
+ ```
161
+
162
+ **Redis keys generated**: `customer:cust_123:domain_index`, `customer:cust_123:subdomain_index`
163
+
164
+ ### Mixed Pattern Example
165
+ A real-world example showing both patterns:
166
+
167
+ ```ruby
168
+ class ApiKey < Familia::Horreum
169
+ feature :relationships
170
+
171
+ identifier_field :key_id
172
+ field :key_id, :key_hash, :name, :scope
173
+
174
+ # API key hashes must be globally unique
175
+ indexed_by :key_hash, :global_key_lookup, context: :global
176
+
177
+ # But key names can be reused across different customers
178
+ indexed_by :name, :customer_key_lookup, context: Customer
179
+ indexed_by :scope, :scope_lookup, context: Customer
180
+ end
181
+
182
+ # Usage examples:
183
+ # Global lookup (system-wide unique)
184
+ ApiKey.key_lookup.get("sha256:abc123...")
185
+
186
+ # Scoped lookup (unique per customer)
187
+ customer = Customer.new(custid: "cust_456")
188
+ customer.find_by_name("production-api-key")
189
+ customer.find_all_by_scope(["read", "write"])
190
+ ```
191
+
192
+ ### Migration Guide
193
+ If you have existing code with incorrect syntax, here's how to fix it:
194
+
195
+ ```ruby
196
+ # ❌ Old incorrect syntax
197
+ indexed_by :email_lookup, field: :email
198
+
199
+ # ✅ New correct syntax - Global scope
200
+ indexed_by :email, :email_lookup, context: :global
201
+
202
+ # ✅ New correct syntax - Class scope
203
+ indexed_by :email, :customer_email_lookup, context: Customer
204
+ ```
205
+
206
+ **Key Differences**:
207
+ 1. Parameter order: `indexed_by(field, index_name, context:)`
208
+ 2. The `field:` named parameter is now positional
209
+ 3. The `context:` parameter is required and determines scope
210
+ 4. Index name comes second (allows same field to have multiple indexes)