familia 2.0.0 → 2.1.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rst +45 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +11 -1
  5. data/docs/guides/writing-migrations.md +345 -0
  6. data/examples/migrations/v1_to_v2_serialization_migration.rb +374 -0
  7. data/examples/schemas/customer.json +33 -0
  8. data/examples/schemas/session.json +27 -0
  9. data/familia.gemspec +2 -0
  10. data/lib/familia/data_type/types/hashkey.rb +0 -238
  11. data/lib/familia/data_type/types/listkey.rb +4 -110
  12. data/lib/familia/data_type/types/sorted_set.rb +0 -365
  13. data/lib/familia/data_type/types/stringkey.rb +0 -139
  14. data/lib/familia/data_type/types/unsorted_set.rb +2 -122
  15. data/lib/familia/features/schema_validation.rb +139 -0
  16. data/lib/familia/migration/base.rb +447 -0
  17. data/lib/familia/migration/errors.rb +31 -0
  18. data/lib/familia/migration/model.rb +418 -0
  19. data/lib/familia/migration/pipeline.rb +226 -0
  20. data/lib/familia/migration/rake_tasks.rake +3 -0
  21. data/lib/familia/migration/rake_tasks.rb +160 -0
  22. data/lib/familia/migration/registry.rb +364 -0
  23. data/lib/familia/migration/runner.rb +311 -0
  24. data/lib/familia/migration/script.rb +234 -0
  25. data/lib/familia/migration.rb +43 -0
  26. data/lib/familia/schema_registry.rb +173 -0
  27. data/lib/familia/settings.rb +63 -1
  28. data/lib/familia/version.rb +1 -1
  29. data/lib/familia.rb +1 -0
  30. data/try/features/schema_registry_try.rb +193 -0
  31. data/try/features/schema_validation_feature_try.rb +218 -0
  32. data/try/migration/base_try.rb +226 -0
  33. data/try/migration/errors_try.rb +67 -0
  34. data/try/migration/integration_try.rb +451 -0
  35. data/try/migration/model_try.rb +431 -0
  36. data/try/migration/pipeline_try.rb +460 -0
  37. data/try/migration/rake_tasks_try.rb +61 -0
  38. data/try/migration/registry_try.rb +199 -0
  39. data/try/migration/runner_try.rb +311 -0
  40. data/try/migration/schema_validation_try.rb +201 -0
  41. data/try/migration/script_try.rb +192 -0
  42. data/try/migration/v1_to_v2_serialization_try.rb +513 -0
  43. data/try/performance/benchmarks_try.rb +11 -12
  44. metadata +44 -1
@@ -87,113 +87,9 @@ module Familia
87
87
  end
88
88
  alias remove remove_element # deprecated
89
89
 
90
- # Returns the intersection of this set with one or more other sets.
91
- # @param other_sets [Array<UnsortedSet, String>] Other sets (as UnsortedSet instances or raw keys)
92
- # @return [Array] Deserialized members present in all sets
93
- def intersection(*other_sets)
94
- keys = extract_keys(other_sets)
95
- elements = dbclient.sinter(dbkey, *keys)
96
- deserialize_values(*elements)
97
- end
98
- alias inter intersection
99
-
100
- # Returns the union of this set with one or more other sets.
101
- # @param other_sets [Array<UnsortedSet, String>] Other sets (as UnsortedSet instances or raw keys)
102
- # @return [Array] Deserialized members present in any of the sets
103
- def union(*other_sets)
104
- keys = extract_keys(other_sets)
105
- elements = dbclient.sunion(dbkey, *keys)
106
- deserialize_values(*elements)
107
- end
108
-
109
- # Returns the difference of this set minus one or more other sets.
110
- # @param other_sets [Array<UnsortedSet, String>] Other sets (as UnsortedSet instances or raw keys)
111
- # @return [Array] Deserialized members present in this set but not in any other sets
112
- def difference(*other_sets)
113
- keys = extract_keys(other_sets)
114
- elements = dbclient.sdiff(dbkey, *keys)
115
- deserialize_values(*elements)
116
- end
117
- alias diff difference
118
-
119
- # Checks membership for multiple values at once.
120
- # @param values [Array] Values to check for membership
121
- # @return [Array<Boolean>] Array of booleans indicating membership for each value
122
- def member_any?(*values)
123
- values = values.flatten
124
- serialized = values.map { |v| serialize_value(v) }
125
- dbclient.smismember(dbkey, *serialized)
126
- end
127
- alias members? member_any?
128
-
129
- # Iterates over set members using cursor-based iteration.
130
- # @param cursor [Integer] Starting cursor position (default: 0)
131
- # @param match [String, nil] Optional pattern to filter members
132
- # @param count [Integer, nil] Optional hint for number of elements to return per call
133
- # @return [Array<Integer, Array>] Two-element array: [new_cursor, deserialized_members]
134
- def scan(cursor = 0, match: nil, count: nil)
135
- opts = {}
136
- opts[:match] = match if match
137
- opts[:count] = count if count
138
-
139
- new_cursor, elements = dbclient.sscan(dbkey, cursor, **opts)
140
- [new_cursor.to_i, deserialize_values(*elements)]
141
- end
142
-
143
- # Returns the cardinality of the intersection without retrieving members.
144
- # More memory-efficient than intersection when only the count is needed.
145
- # @param other_sets [Array<UnsortedSet, String>] Other sets (as UnsortedSet instances or raw keys)
146
- # @param limit [Integer] Stop counting after reaching this limit (0 = no limit)
147
- # @return [Integer] Number of elements in the intersection
148
- def intercard(*other_sets, limit: 0)
149
- keys = extract_keys(other_sets)
150
- all_keys = [dbkey, *keys]
151
- if limit.positive?
152
- dbclient.sintercard(all_keys.size, *all_keys, limit: limit)
153
- else
154
- dbclient.sintercard(all_keys.size, *all_keys)
155
- end
156
- end
157
- alias intersection_cardinality intercard
158
-
159
- # Stores the intersection of this set with other sets into a destination key.
160
- # @param destination [UnsortedSet, String] Destination set (as UnsortedSet instance or raw key)
161
- # @param other_sets [Array<UnsortedSet, String>] Other sets to intersect with
162
- # @return [Integer] Number of elements in the resulting set
163
- def interstore(destination, *other_sets)
164
- dest_key = extract_key(destination)
165
- keys = extract_keys(other_sets)
166
- result = dbclient.sinterstore(dest_key, dbkey, *keys)
167
- update_expiration
168
- result
169
- end
170
- alias intersection_store interstore
171
-
172
- # Stores the union of this set with other sets into a destination key.
173
- # @param destination [UnsortedSet, String] Destination set (as UnsortedSet instance or raw key)
174
- # @param other_sets [Array<UnsortedSet, String>] Other sets to union with
175
- # @return [Integer] Number of elements in the resulting set
176
- def unionstore(destination, *other_sets)
177
- dest_key = extract_key(destination)
178
- keys = extract_keys(other_sets)
179
- result = dbclient.sunionstore(dest_key, dbkey, *keys)
180
- update_expiration
181
- result
182
- end
183
- alias union_store unionstore
184
-
185
- # Stores the difference of this set minus other sets into a destination key.
186
- # @param destination [UnsortedSet, String] Destination set (as UnsortedSet instance or raw key)
187
- # @param other_sets [Array<UnsortedSet, String>] Other sets to subtract
188
- # @return [Integer] Number of elements in the resulting set
189
- def diffstore(destination, *other_sets)
190
- dest_key = extract_key(destination)
191
- keys = extract_keys(other_sets)
192
- result = dbclient.sdiffstore(dest_key, dbkey, *keys)
193
- update_expiration
194
- result
90
+ def intersection *setkeys
91
+ # TODO
195
92
  end
196
- alias difference_store diffstore
197
93
 
198
94
  def pop
199
95
  dbclient.spop dbkey
@@ -219,22 +115,6 @@ module Familia
219
115
  end
220
116
  alias random sampleraw
221
117
 
222
- private
223
-
224
- # Extracts the database key from a set reference.
225
- # @param set_ref [UnsortedSet, String] An UnsortedSet instance or raw key string
226
- # @return [String] The database key
227
- def extract_key(set_ref)
228
- set_ref.respond_to?(:dbkey) ? set_ref.dbkey : set_ref.to_s
229
- end
230
-
231
- # Extracts database keys from an array of set references.
232
- # @param set_refs [Array<UnsortedSet, String>] Array of UnsortedSet instances or raw keys
233
- # @return [Array<String>] Array of database keys
234
- def extract_keys(set_refs)
235
- set_refs.flatten.map { |s| extract_key(s) }
236
- end
237
-
238
118
  Familia::DataType.register self, :set
239
119
  Familia::DataType.register self, :unsorted_set
240
120
  end
@@ -0,0 +1,139 @@
1
+ # lib/familia/features/schema_validation.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ module Familia
6
+ module Features
7
+ # Adds JSON schema validation methods to Horreum models.
8
+ # Schemas are loaded from external files via SchemaRegistry.
9
+ #
10
+ # This feature provides instance-level validation against JSON schemas,
11
+ # enabling data integrity checks before persistence or during API operations.
12
+ #
13
+ # Example:
14
+ #
15
+ # class Customer < Familia::Horreum
16
+ # feature :schema_validation
17
+ # identifier_field :custid
18
+ # field :custid
19
+ # field :email
20
+ # end
21
+ #
22
+ # # With schema at schemas/customer.json
23
+ # customer = Customer.new(custid: 'c1', email: 'invalid')
24
+ # customer.valid_against_schema? # => false
25
+ # customer.schema_validation_errors # => [...]
26
+ # customer.validate_against_schema! # raises SchemaValidationError
27
+ #
28
+ # Schema Loading:
29
+ #
30
+ # Schemas are loaded by SchemaRegistry based on class names. Configure
31
+ # schema loading before enabling this feature:
32
+ #
33
+ # # Convention-based loading
34
+ # Familia.schema_path = 'schemas/models'
35
+ # # Loads schemas/models/customer.json for Customer class
36
+ #
37
+ # # Explicit mapping
38
+ # Familia.schemas = { 'Customer' => 'schemas/customer.json' }
39
+ #
40
+ # Validation Behavior:
41
+ #
42
+ # - Returns true/valid if no schema is defined for the class
43
+ # - Uses json_schemer gem for validation when available
44
+ # - Falls back to null validation (always passes) if gem not installed
45
+ #
46
+ # Integration Patterns:
47
+ #
48
+ # # Validate before save
49
+ # class Order < Familia::Horreum
50
+ # feature :schema_validation
51
+ #
52
+ # def save
53
+ # validate_against_schema!
54
+ # super
55
+ # end
56
+ # end
57
+ #
58
+ # # Conditional validation
59
+ # class User < Familia::Horreum
60
+ # feature :schema_validation
61
+ #
62
+ # def save
63
+ # if self.class.schema_defined?
64
+ # return false unless valid_against_schema?
65
+ # end
66
+ # super
67
+ # end
68
+ # end
69
+ #
70
+ # Error Handling:
71
+ #
72
+ # The validate_against_schema! method raises SchemaValidationError with
73
+ # detailed error information:
74
+ #
75
+ # begin
76
+ # customer.validate_against_schema!
77
+ # rescue Familia::SchemaValidationError => e
78
+ # e.errors # => [{ 'data_pointer' => '/email', 'type' => 'format', ... }]
79
+ # end
80
+ #
81
+ # @see Familia::SchemaRegistry for schema loading and configuration
82
+ # @see Familia::SchemaValidationError for error details
83
+ #
84
+ module SchemaValidation
85
+ Familia::Base.add_feature self, :schema_validation
86
+
87
+ def self.included(base)
88
+ Familia.trace :LOADED, self, base if Familia.debug?
89
+ base.extend(ClassMethods)
90
+ end
91
+
92
+ # Class-level schema access methods
93
+ module ClassMethods
94
+ # Get the JSON schema for this class
95
+ # @return [Hash, nil] the parsed schema or nil if not defined
96
+ def schema
97
+ Familia::SchemaRegistry.schema_for(name)
98
+ end
99
+
100
+ # Check if a schema is defined for this class
101
+ # @return [Boolean]
102
+ def schema_defined?
103
+ Familia::SchemaRegistry.schema_defined?(name)
104
+ end
105
+ end
106
+
107
+ # Get the schema for this instance's class
108
+ # @return [Hash, nil]
109
+ def schema
110
+ self.class.schema
111
+ end
112
+
113
+ # Check if the current state validates against the schema
114
+ # @return [Boolean] true if valid or no schema defined
115
+ def valid_against_schema?
116
+ return true unless self.class.schema_defined?
117
+
118
+ Familia::SchemaRegistry.validate(self.class.name, to_h)[:valid]
119
+ end
120
+
121
+ # Get validation errors for the current state
122
+ # @return [Array<Hash>] array of error objects (empty if valid)
123
+ def schema_validation_errors
124
+ return [] unless self.class.schema_defined?
125
+
126
+ Familia::SchemaRegistry.validate(self.class.name, to_h)[:errors]
127
+ end
128
+
129
+ # Validate current state or raise SchemaValidationError
130
+ # @return [true] if valid
131
+ # @raise [SchemaValidationError] if validation fails
132
+ def validate_against_schema!
133
+ return true unless self.class.schema_defined?
134
+
135
+ Familia::SchemaRegistry.validate!(self.class.name, to_h)
136
+ end
137
+ end
138
+ end
139
+ end