familia 1.2.1 → 2.0.0.pre2
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 +68 -0
- data/.github/workflows/docs.yml +64 -0
- data/.gitignore +4 -0
- data/.pre-commit-config.yaml +3 -1
- data/.rubocop.yml +16 -9
- data/.rubocop_todo.yml +177 -31
- data/.yardopts +9 -0
- data/CLAUDE.md +141 -0
- data/Gemfile +16 -2
- data/Gemfile.lock +97 -36
- data/README.md +39 -23
- data/bin/irb +3 -0
- data/docs/connection_pooling.md +192 -0
- data/familia.gemspec +10 -6
- data/lib/familia/base.rb +19 -9
- data/lib/familia/connection.rb +232 -65
- data/lib/familia/core_ext.rb +1 -1
- data/lib/familia/datatype/commands.rb +59 -0
- data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
- data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
- data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
- data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
- data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
- data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
- data/lib/familia/datatype.rb +243 -0
- data/lib/familia/errors.rb +5 -2
- data/lib/familia/features/expiration.rb +33 -34
- data/lib/familia/features/quantization.rb +9 -3
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/features.rb +2 -2
- data/lib/familia/horreum/class_methods.rb +97 -110
- data/lib/familia/horreum/commands.rb +46 -51
- data/lib/familia/horreum/connection.rb +82 -0
- data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
- data/lib/familia/horreum/serialization.rb +61 -198
- data/lib/familia/horreum/settings.rb +6 -17
- data/lib/familia/horreum/utils.rb +11 -10
- data/lib/familia/horreum.rb +69 -60
- data/lib/familia/logging.rb +12 -12
- data/lib/familia/multi_result.rb +72 -0
- data/lib/familia/refinements.rb +7 -44
- data/lib/familia/settings.rb +11 -11
- data/lib/familia/utils.rb +123 -90
- data/lib/familia/version.rb +4 -21
- data/lib/familia.rb +18 -13
- data/lib/middleware/database_middleware.rb +150 -0
- data/try/configuration/scenarios_try.rb +65 -0
- data/try/core/connection_try.rb +58 -0
- data/try/core/errors_try.rb +93 -0
- data/try/core/extensions_try.rb +26 -0
- data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
- data/try/{00_familia_try.rb → core/familia_try.rb} +7 -5
- data/try/core/middleware_try.rb +68 -0
- data/try/core/refinements_try.rb +39 -0
- data/try/core/settings_try.rb +76 -0
- data/try/core/tools_try.rb +54 -0
- data/try/core/utils_try.rb +189 -0
- data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
- data/try/datatypes/datatype_base_try.rb +69 -0
- data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
- data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
- data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
- data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
- data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
- data/try/edge_cases/empty_identifiers_try.rb +48 -0
- data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -7
- data/try/edge_cases/json_serialization_try.rb +85 -0
- data/try/edge_cases/race_conditions_try.rb +60 -0
- data/try/edge_cases/reserved_keywords_try.rb +59 -0
- data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +60 -59
- data/try/edge_cases/ttl_side_effects_try.rb +51 -0
- data/try/features/expiration_try.rb +86 -0
- data/try/features/quantization_try.rb +90 -0
- data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
- data/try/features/safe_dump_try.rb +137 -0
- data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
- data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
- data/try/horreum/class_methods_try.rb +41 -0
- data/try/horreum/commands_try.rb +49 -0
- data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
- data/try/horreum/relations_try.rb +146 -0
- data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
- data/try/horreum/settings_try.rb +43 -0
- data/try/integration/cross_component_try.rb +46 -0
- data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
- data/try/{40_customer_try.rb → models/customer_try.rb} +21 -18
- data/try/models/datatype_base_try.rb +100 -0
- data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
- data/try/performance/benchmarks_try.rb +55 -0
- data/try/pooling/README.md +20 -0
- data/try/pooling/configurable_stress_test_try.rb +435 -0
- data/try/pooling/connection_pool_test_try.rb +273 -0
- data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- data/try/pooling/lib/connection_pool_metrics.rb +372 -0
- data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
- data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
- data/try/pooling/lib/visualize_stress_results.rb +434 -0
- data/try/pooling/pool_siege_try.rb +509 -0
- data/try/pooling/run_stress_tests_try.rb +482 -0
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
- data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
- data/try/prototypes/atomic_saves_v4.rb +105 -0
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- metadata +143 -46
- data/.github/workflows/ruby.yml +0 -71
- data/VERSION.yml +0 -4
- data/lib/familia/redistype/commands.rb +0 -59
- data/lib/familia/redistype.rb +0 -228
- data/lib/familia/tools.rb +0 -68
- data/lib/redis_middleware.rb +0 -109
- data/try/20_redis_type_try.rb +0 -70
- data/try/91_json_bug_try.rb +0 -86
@@ -1,25 +1,25 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# lib/familia/horreum/commands.rb
|
2
|
+
|
3
3
|
module Familia
|
4
4
|
# InstanceMethods - Module containing instance-level methods for Familia
|
5
5
|
#
|
6
6
|
# This module is included in classes that include Familia, providing
|
7
|
-
# instance-level functionality for
|
7
|
+
# instance-level functionality for Database operations and object management.
|
8
8
|
#
|
9
9
|
class Horreum
|
10
10
|
|
11
|
-
# Methods that call
|
11
|
+
# Methods that call Database commands (InstanceMethods)
|
12
12
|
#
|
13
13
|
# NOTE: There is no hgetall for Horreum. This is because Horreum
|
14
|
-
# is a single hash in
|
14
|
+
# is a single hash in Database that we aren't meant to have be working
|
15
15
|
# on in memory for more than, making changes -> committing. To
|
16
16
|
# emphasize this, instead of "refreshing" the object with hgetall,
|
17
17
|
# just load the object again.
|
18
18
|
#
|
19
19
|
module Commands
|
20
20
|
|
21
|
-
def move(
|
22
|
-
|
21
|
+
def move(logical_database)
|
22
|
+
dbclient.move dbkey, logical_database
|
23
23
|
end
|
24
24
|
|
25
25
|
# Checks if the calling object's key exists in Redis.
|
@@ -39,7 +39,7 @@ module Familia
|
|
39
39
|
# @note The default behavior maintains backward compatibility by treating empty hashes
|
40
40
|
# as non-existent. Use `check_size: false` for pure key existence checking.
|
41
41
|
def exists?(check_size: true)
|
42
|
-
key_exists = self.class.
|
42
|
+
key_exists = self.class.dbclient.exists?(dbkey)
|
43
43
|
return key_exists unless check_size
|
44
44
|
key_exists && !size.zero?
|
45
45
|
end
|
@@ -47,7 +47,7 @@ module Familia
|
|
47
47
|
# Returns the number of fields in the main object hash
|
48
48
|
# @return [Integer] number of fields
|
49
49
|
def field_count
|
50
|
-
|
50
|
+
dbclient.hlen dbkey
|
51
51
|
end
|
52
52
|
alias size field_count
|
53
53
|
|
@@ -55,44 +55,39 @@ module Familia
|
|
55
55
|
# automatically be deleted. Returns 1 if the timeout was set, 0 if key
|
56
56
|
# does not exist or the timeout could not be set.
|
57
57
|
#
|
58
|
-
def expire(
|
59
|
-
|
60
|
-
Familia.trace :EXPIRE,
|
61
|
-
|
58
|
+
def expire(default_expiration = nil)
|
59
|
+
default_expiration ||= self.class.default_expiration
|
60
|
+
Familia.trace :EXPIRE, dbclient, default_expiration, caller(1..1) if Familia.debug?
|
61
|
+
dbclient.expire dbkey, default_expiration.to_i
|
62
62
|
end
|
63
63
|
|
64
|
-
# Retrieves the remaining time to live (TTL) for the object's
|
64
|
+
# Retrieves the remaining time to live (TTL) for the object's dbkey.
|
65
65
|
#
|
66
|
-
# This method accesses the ovjects
|
66
|
+
# This method accesses the ovjects Database client to obtain the TTL of `dbkey`.
|
67
67
|
# If debugging is enabled, it logs the TTL retrieval operation using `Familia.trace`.
|
68
68
|
#
|
69
69
|
# @return [Integer] The TTL of the key in seconds. Returns -1 if the key does not exist
|
70
70
|
# or has no associated expire time.
|
71
|
-
def
|
72
|
-
Familia.trace :
|
73
|
-
|
71
|
+
def current_expiration
|
72
|
+
Familia.trace :CURRENT_EXPIRATION, dbclient, uri, caller(1..1) if Familia.debug?
|
73
|
+
dbclient.ttl dbkey
|
74
74
|
end
|
75
75
|
|
76
|
-
# Removes a field from the hash stored at the
|
76
|
+
# Removes a field from the hash stored at the dbkey.
|
77
77
|
#
|
78
78
|
# @param field [String] The field to remove from the hash.
|
79
79
|
# @return [Integer] The number of fields that were removed from the hash (0 or 1).
|
80
80
|
def remove_field(field)
|
81
|
-
Familia.trace :HDEL,
|
82
|
-
|
81
|
+
Familia.trace :HDEL, dbclient, field, caller(1..1) if Familia.debug?
|
82
|
+
dbclient.hdel dbkey, field
|
83
83
|
end
|
84
84
|
alias remove remove_field # deprecated
|
85
85
|
|
86
|
-
def
|
87
|
-
Familia.trace :
|
88
|
-
|
86
|
+
def datatype
|
87
|
+
Familia.trace :DATATYPE, dbclient, uri, caller(1..1) if Familia.debug?
|
88
|
+
dbclient.type dbkey(suffix)
|
89
89
|
end
|
90
90
|
|
91
|
-
# Parity with RedisType#rename
|
92
|
-
def rename(newkey)
|
93
|
-
Familia.trace :RENAME, redis, "#{rediskey} -> #{newkey}", caller(1..1) if Familia.debug?
|
94
|
-
redis.rename rediskey, newkey
|
95
|
-
end
|
96
91
|
|
97
92
|
# Retrieves the prefix for the current instance by delegating to its class.
|
98
93
|
#
|
@@ -103,80 +98,80 @@ module Familia
|
|
103
98
|
self.class.prefix
|
104
99
|
end
|
105
100
|
|
106
|
-
# For parity with
|
101
|
+
# For parity with DataType#hgetall
|
107
102
|
def hgetall
|
108
|
-
Familia.trace :HGETALL,
|
109
|
-
|
103
|
+
Familia.trace :HGETALL, dbclient, uri, caller(1..1) if Familia.debug?
|
104
|
+
dbclient.hgetall dbkey(suffix)
|
110
105
|
end
|
111
106
|
alias all hgetall
|
112
107
|
|
113
108
|
def hget(field)
|
114
|
-
Familia.trace :HGET,
|
115
|
-
|
109
|
+
Familia.trace :HGET, dbclient, field, caller(1..1) if Familia.debug?
|
110
|
+
dbclient.hget dbkey(suffix), field
|
116
111
|
end
|
117
112
|
|
118
113
|
# @return The number of fields that were added to the hash. If the
|
119
114
|
# field already exists, this will return 0.
|
120
115
|
def hset(field, value)
|
121
|
-
Familia.trace :HSET,
|
122
|
-
|
116
|
+
Familia.trace :HSET, dbclient, field, caller(1..1) if Familia.debug?
|
117
|
+
dbclient.hset dbkey, field, value
|
123
118
|
end
|
124
119
|
|
125
120
|
def hmset(hsh={})
|
126
121
|
hsh ||= self.to_h
|
127
|
-
Familia.trace :HMSET,
|
128
|
-
|
122
|
+
Familia.trace :HMSET, dbclient, hsh, caller(1..1) if Familia.debug?
|
123
|
+
dbclient.hmset dbkey(suffix), hsh
|
129
124
|
end
|
130
125
|
|
131
126
|
def hkeys
|
132
|
-
Familia.trace :HKEYS,
|
133
|
-
|
127
|
+
Familia.trace :HKEYS, dbclient, 'uri', caller(1..1) if Familia.debug?
|
128
|
+
dbclient.hkeys dbkey(suffix)
|
134
129
|
end
|
135
130
|
|
136
131
|
def hvals
|
137
|
-
|
132
|
+
dbclient.hvals dbkey(suffix)
|
138
133
|
end
|
139
134
|
|
140
135
|
def incr(field)
|
141
|
-
|
136
|
+
dbclient.hincrby dbkey(suffix), field, 1
|
142
137
|
end
|
143
138
|
alias increment incr
|
144
139
|
|
145
140
|
def incrby(field, increment)
|
146
|
-
|
141
|
+
dbclient.hincrby dbkey(suffix), field, increment
|
147
142
|
end
|
148
143
|
alias incrementby incrby
|
149
144
|
|
150
145
|
def incrbyfloat(field, increment)
|
151
|
-
|
146
|
+
dbclient.hincrbyfloat dbkey(suffix), field, increment
|
152
147
|
end
|
153
148
|
alias incrementbyfloat incrbyfloat
|
154
149
|
|
155
150
|
def decrby(field, decrement)
|
156
|
-
|
151
|
+
dbclient.decrby dbkey(suffix), field, decrement
|
157
152
|
end
|
158
153
|
alias decrementby decrby
|
159
154
|
|
160
155
|
def decr(field)
|
161
|
-
|
156
|
+
dbclient.hdecr field
|
162
157
|
end
|
163
158
|
alias decrement decr
|
164
159
|
|
165
160
|
def hstrlen(field)
|
166
|
-
|
161
|
+
dbclient.hstrlen dbkey(suffix), field
|
167
162
|
end
|
168
163
|
alias hstrlength hstrlen
|
169
164
|
|
170
165
|
def key?(field)
|
171
|
-
|
166
|
+
dbclient.hexists dbkey(suffix), field
|
172
167
|
end
|
173
168
|
alias has_key? key?
|
174
169
|
|
175
|
-
# Deletes the entire
|
170
|
+
# Deletes the entire dbkey
|
176
171
|
# @return [Boolean] true if the key was deleted, false otherwise
|
177
172
|
def delete!
|
178
|
-
Familia.trace :DELETE!,
|
179
|
-
ret =
|
173
|
+
Familia.trace :DELETE!, dbclient, uri, caller(1..1) if Familia.debug?
|
174
|
+
ret = dbclient.del dbkey
|
180
175
|
ret.positive?
|
181
176
|
end
|
182
177
|
alias clear delete!
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# lib/familia/horreum/connection.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
class Horreum
|
5
|
+
|
6
|
+
# Familia::Horreum::Connection
|
7
|
+
#
|
8
|
+
module Connection
|
9
|
+
attr_reader :uri
|
10
|
+
|
11
|
+
# Returns the Database connection for the class.
|
12
|
+
#
|
13
|
+
# This method retrieves the Database connection instance for the class. If no
|
14
|
+
# connection is set, it initializes a new connection using the provided URI
|
15
|
+
# or database configuration.
|
16
|
+
#
|
17
|
+
# @return [Redis] the Database connection instance.
|
18
|
+
#
|
19
|
+
def dbclient
|
20
|
+
Fiber[:familia_transaction] || @dbclient || Familia.dbclient(uri || logical_database)
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect(*)
|
24
|
+
Familia.connect(*)
|
25
|
+
end
|
26
|
+
|
27
|
+
def uri=(uri)
|
28
|
+
@uri = normalize_uri(uri)
|
29
|
+
end
|
30
|
+
alias url uri
|
31
|
+
alias url= uri=
|
32
|
+
|
33
|
+
# Perform a sacred Database transaction ritual.
|
34
|
+
#
|
35
|
+
# This method creates a protective circle around your Database operations,
|
36
|
+
# ensuring they all succeed or fail together. It's like a group hug for your
|
37
|
+
# data operations, but with more ACID properties.
|
38
|
+
#
|
39
|
+
# @yield [conn] A block where you can perform your Database incantations.
|
40
|
+
# @yieldparam conn [Redis] A Database connection in multi mode.
|
41
|
+
#
|
42
|
+
# @example Performing a Database rain dance
|
43
|
+
# transaction do |conn|
|
44
|
+
# conn.set("weather", "rainy")
|
45
|
+
# conn.set("mood", "melancholic")
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# @note This method works with the global Familia.transaction context when available
|
49
|
+
#
|
50
|
+
def transaction
|
51
|
+
# If we're already in a Familia.transaction context, just yield the multi connection
|
52
|
+
if Fiber[:familia_transaction]
|
53
|
+
yield(Fiber[:familia_transaction])
|
54
|
+
else
|
55
|
+
# Otherwise, create a local transaction
|
56
|
+
block_result = dbclient.multi do |conn|
|
57
|
+
yield(conn)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
block_result
|
61
|
+
end
|
62
|
+
alias multi transaction
|
63
|
+
|
64
|
+
def pipeline
|
65
|
+
# If we're already in a Familia.pipeline context, just yield the pipeline connection
|
66
|
+
if Fiber[:familia_pipeline]
|
67
|
+
yield(Fiber[:familia_pipeline])
|
68
|
+
else
|
69
|
+
# Otherwise, create a local transaction
|
70
|
+
block_result = dbclient.pipeline do |conn|
|
71
|
+
yield(conn)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
block_result
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
# Include Connection module for instance methods after it's loaded
|
80
|
+
include Familia::Horreum::Connection
|
81
|
+
end
|
82
|
+
end
|
@@ -1,21 +1,23 @@
|
|
1
|
+
# lib/familia/horreum/related_fields_management.rb
|
2
|
+
|
1
3
|
module Familia
|
2
4
|
class Horreum
|
3
5
|
#
|
4
|
-
#
|
6
|
+
# RelatedFieldsManagement: Manages DataType fields and relations
|
5
7
|
#
|
6
8
|
# This module uses metaprogramming to dynamically create methods
|
7
|
-
# for managing different types of
|
9
|
+
# for managing different types of Database objects (e.g., sets, lists, hashes).
|
8
10
|
#
|
9
11
|
# Key metaprogramming features:
|
10
|
-
# * Dynamically defines methods for each
|
12
|
+
# * Dynamically defines methods for each Database type (e.g., set, list, hashkey)
|
11
13
|
# * Creates both instance-level and class-level relation methods
|
12
14
|
# * Provides query methods for checking relation types
|
13
15
|
#
|
14
16
|
# Usage:
|
15
|
-
# Include this module in classes that need
|
17
|
+
# Include this module in classes that need DataType management
|
16
18
|
# Call setup_relations_accessors to initialize the feature
|
17
19
|
#
|
18
|
-
module
|
20
|
+
module RelatedFieldsManagement
|
19
21
|
# A practical flag to indicate that a Horreum member has relations,
|
20
22
|
# not just theoretically but actually at least one list/haskey/etc.
|
21
23
|
@has_relations = nil
|
@@ -26,18 +28,18 @@ module Familia
|
|
26
28
|
end
|
27
29
|
|
28
30
|
module ClassMethods
|
29
|
-
# Sets up all
|
31
|
+
# Sets up all DataType related methods
|
30
32
|
# This method is the core of the metaprogramming logic
|
31
33
|
#
|
32
34
|
def setup_relations_accessors
|
33
|
-
Familia::
|
35
|
+
Familia::DataType.registered_types.each_pair do |kind, klass|
|
34
36
|
Familia.ld "[registered_types] #{kind} => #{klass}"
|
35
37
|
|
36
38
|
# Dynamically define instance-level relation methods
|
37
39
|
#
|
38
40
|
# Once defined, these methods can be used at the instance-level of a
|
39
41
|
# Familia member to define *instance-level* relations to any of the
|
40
|
-
#
|
42
|
+
# DataType types (e.g. set, list, hash, etc).
|
41
43
|
#
|
42
44
|
define_method :"#{kind}" do |*args|
|
43
45
|
name, opts = *args
|
@@ -45,15 +47,15 @@ module Familia
|
|
45
47
|
# As log as we have at least one relation, we can set this flag.
|
46
48
|
@has_relations = true
|
47
49
|
|
48
|
-
|
50
|
+
attach_instance_related_field name, klass, opts
|
49
51
|
end
|
50
52
|
define_method :"#{kind}?" do |name|
|
51
|
-
obj =
|
53
|
+
obj = related_fields[name.to_s.to_sym]
|
52
54
|
!obj.nil? && klass == obj.klass
|
53
55
|
end
|
54
56
|
define_method :"#{kind}s" do
|
55
|
-
names =
|
56
|
-
names.collect! { |name|
|
57
|
+
names = related_fields.keys.select { |name| send(:"#{kind}?", name) }
|
58
|
+
names.collect! { |name| related_fields[name] }
|
57
59
|
names
|
58
60
|
end
|
59
61
|
|
@@ -61,24 +63,24 @@ module Familia
|
|
61
63
|
#
|
62
64
|
# Once defined, these methods can be used at the class-level of a
|
63
65
|
# Familia member to define *class-level relations* to any of the
|
64
|
-
#
|
66
|
+
# DataType types (e.g. class_set, class_list, class_hash, etc).
|
65
67
|
#
|
66
68
|
define_method :"class_#{kind}" do |*args|
|
67
69
|
name, opts = *args
|
68
|
-
|
70
|
+
attach_class_related_field name, klass, opts
|
69
71
|
end
|
70
72
|
define_method :"class_#{kind}?" do |name|
|
71
|
-
obj =
|
73
|
+
obj = class_related_fields[name.to_s.to_sym]
|
72
74
|
!obj.nil? && klass == obj.klass
|
73
75
|
end
|
74
76
|
define_method :"class_#{kind}s" do
|
75
|
-
names =
|
76
|
-
# TODO: This returns instances of the
|
77
|
+
names = class_related_fields.keys.select { |name| send(:"class_#{kind}?", name) }
|
78
|
+
# TODO: This returns instances of the DataType class which
|
77
79
|
# also contain the options. This is different from the instance
|
78
|
-
#
|
80
|
+
# DataTypes defined above which returns the Struct of name, klass, and opts.
|
79
81
|
# names.collect! { |name| self.send name }
|
80
82
|
# OR NOT:
|
81
|
-
names.collect! { |name|
|
83
|
+
names.collect! { |name| class_related_fields[name] }
|
82
84
|
names
|
83
85
|
end
|
84
86
|
end
|
@@ -87,17 +89,17 @@ module Familia
|
|
87
89
|
# End of ClassMethods module
|
88
90
|
|
89
91
|
# Creates an instance-level relation
|
90
|
-
def
|
92
|
+
def attach_instance_related_field(name, klass, opts)
|
91
93
|
Familia.ld "[#{self}##{name}] Attaching instance-level #{klass} #{opts}"
|
92
94
|
raise ArgumentError, "Name is blank (#{klass})" if name.to_s.empty?
|
93
95
|
|
94
96
|
name = name.to_s.to_sym
|
95
97
|
opts ||= {}
|
96
98
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
99
|
+
related_fields[name] = Struct.new(:name, :klass, :opts).new
|
100
|
+
related_fields[name].name = name
|
101
|
+
related_fields[name].klass = klass
|
102
|
+
related_fields[name].opts = opts
|
101
103
|
|
102
104
|
attr_reader name
|
103
105
|
|
@@ -108,11 +110,11 @@ module Familia
|
|
108
110
|
!send(name).empty?
|
109
111
|
end
|
110
112
|
|
111
|
-
|
113
|
+
related_fields[name]
|
112
114
|
end
|
113
115
|
|
114
116
|
# Creates a class-level relation
|
115
|
-
def
|
117
|
+
def attach_class_related_field(name, klass, opts)
|
116
118
|
Familia.ld "[#{self}.#{name}] Attaching class-level #{klass} #{opts}"
|
117
119
|
raise ArgumentError, 'Name is blank (klass)' if name.to_s.empty?
|
118
120
|
|
@@ -120,10 +122,10 @@ module Familia
|
|
120
122
|
opts = opts.nil? ? {} : opts.clone
|
121
123
|
opts[:parent] = self unless opts.key?(:parent)
|
122
124
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
125
|
+
class_related_fields[name] = Struct.new(:name, :klass, :opts).new
|
126
|
+
class_related_fields[name].name = name
|
127
|
+
class_related_fields[name].klass = klass
|
128
|
+
class_related_fields[name].opts = opts
|
127
129
|
|
128
130
|
# An accessor method created in the metaclass will
|
129
131
|
# access the instance variables for this class.
|
@@ -136,13 +138,13 @@ module Familia
|
|
136
138
|
!send(name).empty?
|
137
139
|
end
|
138
140
|
|
139
|
-
|
140
|
-
|
141
|
-
instance_variable_set(:"@#{name}",
|
141
|
+
related_field = klass.new name, opts
|
142
|
+
related_field.freeze
|
143
|
+
instance_variable_set(:"@#{name}", related_field)
|
142
144
|
|
143
|
-
|
145
|
+
class_related_fields[name]
|
144
146
|
end
|
145
147
|
end
|
146
|
-
# End of
|
148
|
+
# End of RelatedFieldsManagement module
|
147
149
|
end
|
148
150
|
end
|