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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -2
- data/.github/workflows/{code-smellage.yml → code-smells.yml} +3 -63
- data/.gitignore +2 -0
- data/.rubocop.yml +6 -0
- data/CHANGELOG.rst +22 -0
- data/CLAUDE.md +38 -0
- data/Gemfile.lock +1 -1
- data/docs/archive/FAMILIA_TECHNICAL.md +1 -1
- data/docs/overview.md +2 -2
- data/docs/reference/api-technical.md +1 -1
- data/examples/encrypted_fields.rb +1 -1
- data/examples/safe_dump.rb +1 -1
- data/lib/familia/base.rb +6 -4
- data/lib/familia/data_type/class_methods.rb +63 -0
- data/lib/familia/data_type/connection.rb +83 -0
- data/lib/familia/data_type/settings.rb +96 -0
- data/lib/familia/data_type/types/hashkey.rb +2 -1
- data/lib/familia/data_type/types/sorted_set.rb +113 -10
- data/lib/familia/data_type/types/stringkey.rb +0 -4
- data/lib/familia/data_type.rb +6 -193
- data/lib/familia/features/encrypted_fields.rb +5 -2
- data/lib/familia/features/external_identifier.rb +49 -8
- data/lib/familia/features/object_identifier.rb +84 -12
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +6 -1
- data/lib/familia/features/relationships/indexing.rb +7 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +6 -2
- data/lib/familia/features/transient_fields.rb +7 -2
- data/lib/familia/features.rb +6 -1
- data/lib/familia/field_type.rb +0 -18
- data/lib/familia/horreum/{core/connection.rb → connection.rb} +21 -0
- data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +109 -32
- data/lib/familia/horreum/{subclass/management.rb → management.rb} +1 -3
- data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +72 -169
- data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +22 -2
- data/lib/familia/horreum/serialization.rb +172 -0
- data/lib/familia/horreum.rb +29 -8
- data/lib/familia/version.rb +1 -1
- data/try/configuration/scenarios_try.rb +1 -1
- data/try/core/connection_try.rb +4 -4
- data/try/core/database_consistency_try.rb +1 -0
- data/try/core/errors_try.rb +3 -3
- data/try/core/familia_try.rb +1 -1
- data/try/core/isolated_dbclient_try.rb +2 -2
- data/try/core/tools_try.rb +2 -2
- data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
- data/try/features/field_groups_try.rb +244 -0
- data/try/features/relationships/indexing_try.rb +10 -0
- data/try/features/transient_fields/refresh_reset_try.rb +2 -0
- data/try/helpers/test_helpers.rb +3 -4
- data/try/horreum/auto_indexing_on_save_try.rb +212 -0
- data/try/horreum/commands_try.rb +2 -0
- data/try/horreum/defensive_initialization_try.rb +86 -0
- data/try/horreum/destroy_related_fields_cleanup_try.rb +2 -0
- data/try/horreum/settings_try.rb +2 -0
- data/try/memory/memory_docker_ruby_dump.sh +1 -1
- data/try/models/customer_try.rb +5 -5
- data/try/valkey.conf +26 -0
- metadata +19 -11
- data/lib/familia/horreum/core.rb +0 -21
- /data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +0 -0
- /data/lib/familia/horreum/{shared/settings.rb → settings.rb} +0 -0
- /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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e760a3ff094446126c56a0c2b76fd5bed0f6ff64d8eed89580e19d98a5a9ba66
|
4
|
+
data.tar.gz: 74545b0bbb8c89b06ffd385c1448a95a7808b168686a955155a004b91160dafa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd9cd6c374f24859279125ef05199b0dfdac51f7cccdf2bcdf0489c7cca5126ac03d7cab1d54aa8021ce38b286203212514b2b412322ea151b8957fa16c9b445
|
7
|
+
data.tar.gz: 3e44e06249e6daf715ce09b02643b8faf153c85fc2d8451d4cf56d77c5115e5e5fdb1b752acef3f1fdd46f7b7eb7f019213f22850ffe728ec51a05d98ecc5528
|
data/.github/workflows/ci.yml
CHANGED
@@ -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
|
-
-
|
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
|
-
|
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
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
@@ -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:
|
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:
|
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:
|
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:
|
1170
|
+
config.redis_uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:2525/3')
|
1171
1171
|
end
|
1172
1172
|
|
1173
1173
|
module TestHelpers
|
data/examples/safe_dump.rb
CHANGED
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
|
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
|