familia 2.0.0.pre16 → 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 (63) 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 +22 -0
  7. data/CLAUDE.md +38 -0
  8. data/Gemfile.lock +1 -1
  9. data/docs/archive/FAMILIA_TECHNICAL.md +1 -1
  10. data/docs/overview.md +2 -2
  11. data/docs/reference/api-technical.md +1 -1
  12. data/examples/encrypted_fields.rb +1 -1
  13. data/examples/safe_dump.rb +1 -1
  14. data/lib/familia/base.rb +6 -4
  15. data/lib/familia/data_type/class_methods.rb +63 -0
  16. data/lib/familia/data_type/connection.rb +83 -0
  17. data/lib/familia/data_type/settings.rb +96 -0
  18. data/lib/familia/data_type/types/hashkey.rb +2 -1
  19. data/lib/familia/data_type/types/sorted_set.rb +113 -10
  20. data/lib/familia/data_type/types/stringkey.rb +0 -4
  21. data/lib/familia/data_type.rb +6 -193
  22. data/lib/familia/features/encrypted_fields.rb +5 -2
  23. data/lib/familia/features/external_identifier.rb +49 -8
  24. data/lib/familia/features/object_identifier.rb +84 -12
  25. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +6 -1
  26. data/lib/familia/features/relationships/indexing.rb +7 -1
  27. data/lib/familia/features/relationships/participation/participant_methods.rb +6 -2
  28. data/lib/familia/features/transient_fields.rb +7 -2
  29. data/lib/familia/features.rb +6 -1
  30. data/lib/familia/field_type.rb +0 -18
  31. data/lib/familia/horreum/{core/connection.rb → connection.rb} +21 -0
  32. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +109 -32
  33. data/lib/familia/horreum/{subclass/management.rb → management.rb} +1 -3
  34. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +72 -169
  35. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +22 -2
  36. data/lib/familia/horreum/serialization.rb +172 -0
  37. data/lib/familia/horreum.rb +29 -8
  38. data/lib/familia/version.rb +1 -1
  39. data/try/configuration/scenarios_try.rb +1 -1
  40. data/try/core/connection_try.rb +4 -4
  41. data/try/core/database_consistency_try.rb +1 -0
  42. data/try/core/errors_try.rb +3 -3
  43. data/try/core/familia_try.rb +1 -1
  44. data/try/core/isolated_dbclient_try.rb +2 -2
  45. data/try/core/tools_try.rb +2 -2
  46. data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
  47. data/try/features/field_groups_try.rb +244 -0
  48. data/try/features/relationships/indexing_try.rb +10 -0
  49. data/try/features/transient_fields/refresh_reset_try.rb +2 -0
  50. data/try/helpers/test_helpers.rb +3 -4
  51. data/try/horreum/auto_indexing_on_save_try.rb +212 -0
  52. data/try/horreum/commands_try.rb +2 -0
  53. data/try/horreum/defensive_initialization_try.rb +86 -0
  54. data/try/horreum/destroy_related_fields_cleanup_try.rb +2 -0
  55. data/try/horreum/settings_try.rb +2 -0
  56. data/try/memory/memory_docker_ruby_dump.sh +1 -1
  57. data/try/models/customer_try.rb +5 -5
  58. data/try/valkey.conf +26 -0
  59. metadata +19 -11
  60. data/lib/familia/horreum/core.rb +0 -21
  61. /data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +0 -0
  62. /data/lib/familia/horreum/{shared/settings.rb → settings.rb} +0 -0
  63. /data/lib/familia/horreum/{core/utils.rb → utils.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebaf394ebbccf9d096e87da84c693040c71d963860002a9d970d9b04edc11b6c
4
- data.tar.gz: 4862991d1b2a1538876089030657986eb1b56f5551c7e5e9978a1009c77e86f9
3
+ metadata.gz: e760a3ff094446126c56a0c2b76fd5bed0f6ff64d8eed89580e19d98a5a9ba66
4
+ data.tar.gz: 74545b0bbb8c89b06ffd385c1448a95a7808b168686a955155a004b91160dafa
5
5
  SHA512:
6
- metadata.gz: 533af9a33a115e8a59c87ae35e5b82401e1d72edbe43fd4a2963a3ca25a2c1ac259e5aaf02e68945d9d2f576393c6f77e9843ec87c4ed92696ca41d0b06e4d4c
7
- data.tar.gz: b74dca7e415cbd0d6933beb77185e7738d33b3c10a704569120892fa9062fe92874fe2b879439f8d9508b8d02ba0e8154c79cb1546b029ac9e6bbdde442fd5e1
6
+ metadata.gz: cd9cd6c374f24859279125ef05199b0dfdac51f7cccdf2bcdf0489c7cca5126ac03d7cab1d54aa8021ce38b286203212514b2b412322ea151b8957fa16c9b445
7
+ data.tar.gz: 3e44e06249e6daf715ce09b02643b8faf153c85fc2d8451d4cf56d77c5115e5e5fdb1b752acef3f1fdd46f7b7eb7f019213f22850ffe728ec51a05d98ecc5528
@@ -35,8 +35,8 @@ jobs:
35
35
  --health-retries 5
36
36
  ports:
37
37
  # https://docs.github.com/en/actions/using-containerized-services/creating-redis-service-containers#running-jobs-in-containers
38
- # Maps port 6379 on service container to the host
39
- - 6379:6379
38
+ # Maps port 6379 on service container to 2525 on the host
39
+ - 2525:6379
40
40
 
41
41
  steps:
42
42
  - uses: actions/checkout@v4
@@ -1,4 +1,6 @@
1
- name: Any Code Smellage?
1
+ # ..github/workflows/code-smells.yml
2
+ ---
3
+ name: Code Smells
2
4
 
3
5
  on:
4
6
  pull_request:
@@ -81,65 +83,3 @@ jobs:
81
83
  reek-report.html
82
84
  if-no-files-found: ignore
83
85
  retention-days: 30
84
-
85
- # Add other code quality checks here
86
- additional-quality-checks:
87
- name: Additional Quality Checks
88
- runs-on: ubuntu-24.04
89
- timeout-minutes: 5
90
-
91
- steps:
92
- - name: Checkout code
93
- uses: actions/checkout@v4
94
-
95
- - name: Set up Ruby
96
- uses: ruby/setup-ruby@v1
97
- with:
98
- ruby-version: 3.4
99
- bundler-cache: true
100
-
101
- - name: Configure Bundler for secure gem installation
102
- run: |
103
- bundle config set --local path 'vendor/bundle'
104
- bundle config set --local deployment 'false'
105
-
106
- - name: Install dependencies
107
- run: bundle install --jobs 4 --retry 3
108
-
109
- - name: Check for TODO/FIXME comments
110
- run: |
111
- echo "=== Scanning for TODO/FIXME comments ==="
112
- echo "This helps track technical debt and action items."
113
- echo ""
114
-
115
- # Find TODO/FIXME comments (excluding this workflow file)
116
- find . -name "*.rb" -not -path "./vendor/*" -not -path "./tmp/*" | \
117
- xargs grep -Hn -i -E "(TODO|FIXME|HACK|XXX|NOTE):" 2>/dev/null | \
118
- head -20 || echo "No TODO/FIXME comments found"
119
- continue-on-error: true
120
-
121
- - name: Check Ruby file syntax
122
- run: |
123
- echo "=== Checking Ruby syntax ==="
124
- echo "Validates that all Ruby files have correct syntax."
125
- echo ""
126
-
127
- find . -name "*.rb" -not -path "./vendor/*" -not -path "./tmp/*" | \
128
- while read -r file; do
129
- if ! ruby -c "$file" > /dev/null 2>&1; then
130
- echo "Syntax error in: $file"
131
- ruby -c "$file"
132
- fi
133
- done
134
- continue-on-error: true
135
-
136
- - name: Check for long lines
137
- run: |
138
- echo "=== Checking for long lines (>120 characters) ==="
139
- echo "Identifies potentially hard-to-read code lines."
140
- echo ""
141
-
142
- find . -name "*.rb" -not -path "./vendor/*" -not -path "./tmp/*" | \
143
- xargs grep -Hn "^.\{121,\}$" | \
144
- head -10 || echo "No overly long lines found"
145
- continue-on-error: true
data/.gitignore CHANGED
@@ -14,7 +14,9 @@
14
14
  *.log
15
15
  *.txt
16
16
  .ruby-version
17
+ dump.rdb
17
18
  appendonlydir
19
+ data
18
20
  log
19
21
  tmp
20
22
  vendor
data/.rubocop.yml CHANGED
@@ -124,6 +124,9 @@ Style/NegatedIfElseCondition:
124
124
  Naming/AsciiIdentifiers:
125
125
  Enabled: false
126
126
 
127
+ Naming/PredicateMethod:
128
+ Enabled: false
129
+
127
130
  Style/StringLiterals:
128
131
  Enabled: true
129
132
  EnforcedStyle: single_quotes
@@ -135,6 +138,9 @@ Style/FrozenStringLiteralComment:
135
138
  Naming/MemoizedInstanceVariableName:
136
139
  Enabled: false
137
140
 
141
+ ThreadSafety/ClassAndModuleAttributes:
142
+ Enabled: false
143
+
138
144
  ThreadSafety/ClassInstanceVariable:
139
145
  Enabled: false
140
146
 
data/CHANGELOG.rst CHANGED
@@ -12,6 +12,28 @@ Versioning <https://semver.org/spec/v2.0.0.html>`__.
12
12
 
13
13
  <!--scriv-insert-here-->
14
14
 
15
+ .. _changelog-2.0.0.pre17:
16
+
17
+ 2.0.0.pre17 — 2025-10-03
18
+ ========================
19
+
20
+ Added
21
+ -----
22
+
23
+ - **SortedSet#add**: Full ZADD option support (NX, XX, GT, LT, CH) for atomic conditional operations and accurate change tracking. This enables proper index management with timestamp preservation, update-only operations, conditional score updates, and analytics tracking. Closes issue #135.
24
+
25
+ Fixed
26
+ -----
27
+
28
+ - Restored objid provenance tracking when loading objects from Redis. The ``ObjectIdentifier`` feature now infers the generator type (:uuid_v7, :uuid_v4, :hex) from the objid format, enabling dependent features like ``ExternalIdentifier`` to derive external identifiers from loaded objects. PR #131
29
+
30
+ AI Assistance
31
+ -------------
32
+
33
+ - Claude Code assisted with implementing the ``infer_objid_generator`` method and updating the setter logic in ``lib/familia/features/object_identifier.rb``.
34
+
35
+ - Claude Code assisted with Redis ZADD option semantics research, mutual exclusivity validation design, comprehensive test case matrix creation (50+ test cases), and YAML documentation examples.
36
+
15
37
  .. _changelog-2.0.0.pre16:
16
38
 
17
39
  2.0.0.pre16 — 2025-09-30
data/CLAUDE.md CHANGED
@@ -92,6 +92,44 @@ Familia uses a modular feature system where features are mixed into classes:
92
92
 
93
93
  **Inheritance Chain**: `MyClass < Familia::Horreum` automatically extends `ClassMethods` and `Features`
94
94
 
95
+ **Common Pitfall: Overriding initialize**
96
+
97
+ ⚠️ **Do NOT override `initialize` without calling `super`** - this breaks related field initialization.
98
+
99
+ **Bad - will cause crashes:**
100
+ ```ruby
101
+ class User < Familia::Horreum
102
+ def initialize(email)
103
+ @email = email # Missing super! Related fields won't work
104
+ end
105
+ end
106
+ ```
107
+
108
+ **Good - use the `init` hook instead:**
109
+ ```ruby
110
+ class User < Familia::Horreum
111
+ def init(email = nil)
112
+ @email = email # Called after super, related fields work
113
+ end
114
+ end
115
+ ```
116
+
117
+ **Good - call super explicitly:**
118
+ ```ruby
119
+ class User < Familia::Horreum
120
+ def initialize(email = nil, **kwargs)
121
+ super(**kwargs) # ✓ Related fields initialized
122
+ @email = email
123
+ end
124
+ end
125
+ ```
126
+
127
+ **Why this matters**: Familia's `initialize` method calls `initialize_relatives` to set up DataType objects (lists, sets, etc.). Without calling `super`, these objects remain nil and you'll get helpful errors pointing to the missing super call.
128
+
129
+ **When to use each approach:**
130
+ - **Use `init` hook** (preferred): For simple initialization logic that doesn't need to intercept constructor arguments. The `init` method is called automatically after `super` with the same arguments passed to `new`.
131
+ - **Use explicit `super`**: When you need full control over initialization order or need to transform arguments before passing to parent. Remember to pass `**kwargs` to preserve keyword argument handling.
132
+
95
133
  **DataType Definition**: Use class methods to define keystore database-backed attributes:
96
134
  ```ruby
97
135
  class User < Familia::Horreum
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- familia (2.0.0.pre16)
4
+ familia (2.0.0.pre17)
5
5
  benchmark (~> 0.4)
6
6
  connection_pool (~> 2.5)
7
7
  csv (~> 3.3)
@@ -722,7 +722,7 @@ require 'familia'
722
722
 
723
723
  # Use separate Valkey/Redis database for tests
724
724
  Familia.configure do |config|
725
- config.redis_uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:6379/15')
725
+ config.redis_uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:2525/3')
726
726
  end
727
727
 
728
728
  module TestHelpers
data/docs/overview.md CHANGED
@@ -708,7 +708,7 @@ when 'production'
708
708
  when 'development'
709
709
  Familia.uri = 'redis://localhost:6379/0'
710
710
  when 'test'
711
- Familia.uri = 'redis://localhost:6379/15'
711
+ Familia.uri = 'redis://localhost:2525/3'
712
712
  end
713
713
  ```
714
714
 
@@ -868,7 +868,7 @@ Familia.redis.keys('*') # List all keys (use carefully in production)
868
868
  require 'familia'
869
869
 
870
870
  # Use separate test database
871
- Familia.uri = 'redis://localhost:6379/15'
871
+ Familia.uri = 'redis://localhost:2525/3'
872
872
 
873
873
  # Setup encryption for tests
874
874
  test_keys = {
@@ -1167,7 +1167,7 @@ require 'familia'
1167
1167
 
1168
1168
  # Use separate Valkey/Redis database for tests
1169
1169
  Familia.configure do |config|
1170
- config.redis_uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:6379/15')
1170
+ config.redis_uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:2525/3')
1171
1171
  end
1172
1172
 
1173
1173
  module TestHelpers
@@ -10,7 +10,7 @@ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
10
10
  require 'familia'
11
11
 
12
12
  # Configure connection
13
- Familia.uri = 'redis://localhost:6379/15'
13
+ Familia.uri = 'redis://localhost:2525/3'
14
14
 
15
15
  puts '=== Encrypted Fields Feature Examples ==='
16
16
  puts
@@ -10,7 +10,7 @@ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
10
10
  require 'familia'
11
11
 
12
12
  # Configure connection
13
- Familia.uri = 'redis://localhost:6379/15'
13
+ Familia.uri = 'redis://localhost:2525/3'
14
14
 
15
15
  puts '=== SafeDump Feature Examples ==='
16
16
  puts
data/lib/familia/base.rb CHANGED
@@ -31,14 +31,15 @@ module Familia
31
31
  attr_reader :features_available, :feature_definitions
32
32
  attr_accessor :dump_method, :load_method
33
33
 
34
- def add_feature(klass, feature_name, depends_on: [])
34
+ def add_feature(klass, feature_name, depends_on: [], field_group: nil)
35
35
  @features_available ||= {}
36
36
  Familia.trace :ADD_FEATURE, klass, feature_name if Familia.debug?
37
37
 
38
38
  # Create field definition object
39
39
  feature_def = FeatureDefinition.new(
40
40
  name: feature_name,
41
- depends_on: depends_on
41
+ depends_on: depends_on,
42
+ field_group: field_group
42
43
  )
43
44
 
44
45
  # Track field definitions after defining field methods
@@ -112,14 +113,15 @@ module Familia
112
113
  attr_reader :features_available, :feature_definitions
113
114
  attr_accessor :dump_method, :load_method
114
115
 
115
- def add_feature(klass, feature_name, depends_on: [])
116
+ def add_feature(klass, feature_name, depends_on: [], field_group: nil)
116
117
  @features_available ||= {}
117
118
  Familia.trace :ADD_FEATURE, klass, feature_name if Familia.debug?
118
119
 
119
120
  # Create field definition object
120
121
  feature_def = FeatureDefinition.new(
121
122
  name: feature_name,
122
- depends_on: depends_on
123
+ depends_on: depends_on,
124
+ field_group: field_group
123
125
  )
124
126
 
125
127
  # Track field definitions after defining field methods
@@ -0,0 +1,63 @@
1
+ # lib/familia/data_type/definition.rb
2
+
3
+ module Familia
4
+ class DataType
5
+ # ClassMethods - Class-level DSL methods for defining DataType behavior
6
+ #
7
+ # This module is extended into classes that inherit from Familia::DataType,
8
+ # providing class methods for type registration, configuration, and inheritance.
9
+ #
10
+ # Key features:
11
+ # * Type registration system for creating DataType subclasses
12
+ # * Database and connection configuration
13
+ # * Inheritance hooks for propagating settings
14
+ # * Option validation and filtering
15
+ #
16
+ module ClassMethods
17
+ attr_accessor :parent, :suffix, :prefix, :uri
18
+ attr_writer :logical_database
19
+
20
+ # To be called inside every class that inherits DataType
21
+ # +methname+ is the term used for the class and instance methods
22
+ # that are created for the given +klass+ (e.g. set, list, etc)
23
+ def register(klass, methname)
24
+ Familia.trace :REGISTER, nil, "[#{self}] Registering #{klass} as #{methname.inspect}" if Familia.debug?
25
+
26
+ @registered_types[methname] = klass
27
+ end
28
+
29
+ # Get the registered type class from a given method name
30
+ # +methname+ is the method name used to register the class (e.g. :set, :list, etc)
31
+ # Returns the registered class or nil if not found
32
+ def registered_type(methname)
33
+ @registered_types[methname]
34
+ end
35
+
36
+ def logical_database(val = nil)
37
+ @logical_database = val unless val.nil?
38
+ @logical_database || parent&.logical_database
39
+ end
40
+
41
+ def uri(val = nil)
42
+ @uri = val unless val.nil?
43
+ @uri || (parent ? parent.uri : Familia.uri)
44
+ end
45
+
46
+ def inherited(obj)
47
+ Familia.trace :DATATYPE, nil, "#{obj} is my kinda type" if Familia.debug?
48
+ obj.logical_database = logical_database
49
+ obj.default_expiration = default_expiration # method added via Features::Expiration
50
+ obj.uri = uri
51
+ super
52
+ end
53
+
54
+ def valid_keys_only(opts)
55
+ opts.slice(*DataType.valid_options)
56
+ end
57
+
58
+ def relations?
59
+ @has_related_fields ||= false
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,83 @@
1
+ # lib/familia/data_type/connection.rb
2
+
3
+ module Familia
4
+ class DataType
5
+ # Connection - Instance-level connection and key generation methods
6
+ #
7
+ # This module provides instance methods for database connection resolution
8
+ # and Redis key generation for DataType objects.
9
+ #
10
+ # Key features:
11
+ # * Database connection resolution with Chain of Responsibility pattern
12
+ # * Redis key generation based on parent context
13
+ # * Direct database access for advanced operations
14
+ #
15
+ module Connection
16
+ # TODO: Replace with Chain of Responsibility pattern
17
+ def dbclient
18
+ return Fiber[:familia_transaction] if Fiber[:familia_transaction]
19
+ return @dbclient if @dbclient
20
+
21
+ # Delegate to parent if present, otherwise fall back to Familia
22
+ parent ? parent.dbclient : Familia.dbclient(opts[:logical_database])
23
+ end
24
+
25
+ # Produces the full dbkey for this object.
26
+ #
27
+ # @return [String] The full dbkey.
28
+ #
29
+ # This method determines the appropriate dbkey based on the context of the DataType object:
30
+ #
31
+ # 1. If a hardcoded key is set in the options, it returns that key.
32
+ # 2. For instance-level DataType objects, it uses the parent instance's dbkey method.
33
+ # 3. For class-level DataType objects, it uses the parent class's dbkey method.
34
+ # 4. For standalone DataType objects, it uses the keystring as the full dbkey.
35
+ #
36
+ # For class-level DataType objects (parent_class? == true):
37
+ # - The suffix is optional and used to differentiate between different types of objects.
38
+ # - If no suffix is provided, the class's default suffix is used (via the self.suffix method).
39
+ # - If a nil suffix is explicitly passed, it won't appear in the resulting dbkey.
40
+ # - Passing nil as the suffix is how class-level DataType objects are created without
41
+ # the global default 'object' suffix.
42
+ #
43
+ # @example Instance-level DataType
44
+ # user_instance.some_datatype.dbkey # => "user:123:some_datatype"
45
+ #
46
+ # @example Class-level DataType
47
+ # User.some_datatype.dbkey # => "user:some_datatype"
48
+ #
49
+ # @example Standalone DataType
50
+ # DataType.new("mykey").dbkey # => "mykey"
51
+ #
52
+ # @example Class-level DataType with explicit nil suffix
53
+ # User.dbkey("123", nil) # => "user:123"
54
+ #
55
+ def dbkey
56
+ # Return the hardcoded key if it's set. This is useful for
57
+ # support legacy keys that aren't derived in the same way.
58
+ return opts[:dbkey] if opts[:dbkey]
59
+
60
+ if parent_instance?
61
+ # This is an instance-level datatype object so the parent instance's
62
+ # dbkey method is defined in Familia::Horreum::InstanceMethods.
63
+ parent.dbkey(keystring)
64
+ elsif parent_class?
65
+ # This is a class-level datatype object so the parent class' dbkey
66
+ # method is defined in Familia::Horreum::DefinitionMethods.
67
+ parent.dbkey(keystring, nil)
68
+ else
69
+ # This is a standalone DataType object where it's keystring
70
+ # is the full database key (dbkey).
71
+ keystring
72
+ end
73
+ end
74
+
75
+ # Provides a structured way to "gear down" to run db commands that are
76
+ # not implemented in our DataType classes since we intentionally don't
77
+ # have a method_missing method.
78
+ def direct_access
79
+ yield(dbclient, dbkey)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,96 @@
1
+ # lib/familia/data_type/settings.rb
2
+
3
+ module Familia
4
+ class DataType
5
+ # Settings - Instance-level configuration and introspection methods
6
+ #
7
+ # This module provides instance methods for accessing and managing
8
+ # DataType object configuration, parent relationships, and serialization.
9
+ #
10
+ # Key features:
11
+ # * Parent object relationship management
12
+ # * URI and database configuration
13
+ # * Serialization method delegation
14
+ # * Type introspection
15
+ #
16
+ module Settings
17
+ attr_reader :keystring, :opts, :logical_database
18
+ attr_reader :uri
19
+
20
+ alias url uri
21
+
22
+ def class?
23
+ !@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
24
+ end
25
+
26
+ def parent_instance?
27
+ parent&.is_a?(Horreum::ParentDefinition)
28
+ end
29
+
30
+ def parent_class?
31
+ parent.is_a?(Class) && parent.ancestors.include?(Familia::Horreum)
32
+ end
33
+
34
+ def parent?
35
+ parent_class? || parent_instance?
36
+ end
37
+
38
+ def parent
39
+ # Return cached ParentDefinition if available
40
+ return @parent if @parent
41
+
42
+ # Return class-level parent if no instance parent
43
+ return self.class.parent unless @parent_ref
44
+
45
+ # Create ParentDefinition dynamically from stored reference.
46
+ # This ensures we get the current identifier value (available after initialization)
47
+ # rather than a stale nil value from initialization time. Cannot cache due to frozen object.
48
+ Horreum::ParentDefinition.from_parent(@parent_ref)
49
+ end
50
+
51
+ def parent=(value)
52
+ case value
53
+ when Horreum::ParentDefinition
54
+ @parent = value
55
+ when nil
56
+ @parent = nil
57
+ @parent_ref = nil
58
+ else
59
+ # Store parent instance reference for lazy ParentDefinition creation.
60
+ # During initialization, the parent's identifier may not be available yet,
61
+ # so we defer ParentDefinition creation until first access for memory efficiency.
62
+ # Note: @parent_ref is not cleared after use because DataType objects are frozen.
63
+ @parent_ref = value
64
+ @parent = nil # Will be created dynamically in parent method
65
+ end
66
+ end
67
+
68
+ def uri
69
+ # Return explicit instance URI if set
70
+ return @uri if @uri
71
+
72
+ # If we have a parent with logical_database, build URI with that database
73
+ if parent && parent.respond_to?(:logical_database) && parent.logical_database
74
+ new_uri = (self.class.uri || Familia.uri).dup
75
+ new_uri.db = parent.logical_database
76
+ new_uri
77
+ else
78
+ # Fall back to class-level URI or global Familia.uri
79
+ self.class.uri || Familia.uri
80
+ end
81
+ end
82
+
83
+ def uri=(value)
84
+ @uri = value
85
+ end
86
+
87
+ def dump_method
88
+ self.class.dump_method
89
+ end
90
+
91
+ def load_method
92
+ self.class.load_method
93
+ end
94
+ end
95
+ end
96
+ end
@@ -95,7 +95,8 @@ module Familia
95
95
  def remove_field(field)
96
96
  dbclient.hdel dbkey, field.to_s
97
97
  end
98
- alias remove remove_field # deprecated
98
+ alias remove remove_field
99
+ alias remove_element remove_field
99
100
 
100
101
  def increment(field, by = 1)
101
102
  dbclient.hincrby(dbkey, field.to_s, by).to_i