Class: Familia::DataType Abstract
- Inherits:
-
Object
- Object
- Familia::DataType
- Extended by:
- ClassMethods, Features
- Includes:
- Base, Connection, DatabaseCommands, Serialization, Settings
- Defined in:
- lib/familia/data_type.rb,
lib/familia/data_type/settings.rb,
lib/familia/data_type/connection.rb,
lib/familia/data_type/class_methods.rb,
lib/familia/data_type/serialization.rb,
lib/familia/data_type/database_commands.rb
Overview
Subclass and implement Database data type specific methods
DataType - Base class for Database data type wrappers
This class provides common functionality for various Database data types such as String, JsonStringKey, List, UnsortedSet, SortedSet, and HashKey.
== Write Method Transaction Safety Audit (2026-02-25)
All write methods use dbclient which is transaction-aware: inside a Horreum#transaction block, Fiber[:familia_transaction] routes commands through the transaction connection. Outside a transaction, each write is a standalone command followed by a separate EXPIRE (2 round trips, no atomicity guarantee between them).
Methods marked read-then-write are NOT atomic outside of transactions.
| Type | Method | Redis Cmd | update_exp | Read-then-write |
|---|---|---|---|---|
| UnsortedSet | add | SADD | yes | no |
| UnsortedSet | remove_element | SREM | yes | no |
| UnsortedSet | pop | SPOP | yes | no |
| UnsortedSet | move | SMOVE | yes | no |
| SortedSet | add | ZADD | yes | no |
| SortedSet | remove_element | ZREM | yes | no |
| SortedSet | increment | ZINCRBY | yes | no |
| SortedSet | decrement | ZINCRBY (neg) | yes (via increment) | no |
| SortedSet | remrangebyrank | ZREMRANGEBYRANK | yes | no |
| SortedSet | remrangebyscore | ZREMRANGEBYSCORE | yes | no |
| HashKey | []= | HSET | yes | no |
| HashKey | hsetnx | HSETNX | conditional | no |
| HashKey | remove_field | HDEL | yes | no |
| HashKey | increment | HINCRBY | yes | no |
| HashKey | decrement | HINCRBY (neg) | yes (via increment) | no |
| HashKey | update | HMSET | yes | no |
| ListKey | push | RPUSH(+LTRIM) | yes | no |
| ListKey | unshift | LPUSH(+LTRIM) | yes | no |
| ListKey | pop | RPOP | yes | no |
| ListKey | shift | LPOP | yes | no |
| ListKey | remove_element | LREM | yes | no |
| StringKey | value= | SET | yes | no |
| StringKey | setnx | SETNX | yes | no |
| StringKey | increment | INCR | yes | no |
| StringKey | incrementby | INCRBY | yes | no |
| StringKey | decrement | DECR | yes | no |
| StringKey | decrementby | DECRBY | yes | no |
| StringKey | append | APPEND | yes | no |
| StringKey | setbit | SETBIT | yes | no |
| StringKey | setrange | SETRANGE | yes | no |
| StringKey | getset | GETSET | yes | no |
| StringKey | del | DEL | no | no |
| Counter | reset | SET (via set) | yes (via value=) | no |
| Counter | incr_if_lt | EVAL (Lua) | yes | no (atomic Lua) |
| Lock | acquire | SETNX(+EXPIRE) | yes (via setnx) | no |
| Lock | release | EVAL (Lua) | no (deletes key) | no (atomic Lua) |
| Lock | force_unlock! | DEL | no (deletes key) | no |
Notes:
- Counter#increment_if_less_than uses a Lua script (EVAL) for atomic threshold check + increment. Previously used GET then conditional INCRBY which was not atomic outside of a transaction.
- Lock#release uses a Lua script (EVAL) which IS atomic on the server.
- StringKey#del and Lock methods that delete the key do not call update_expiration because the key no longer exists.
- HashKey#hsetnx only calls update_expiration when the field was actually set (ret == 1), which is correct conditional behavior.
Direct Known Subclasses
HashKey, JsonStringKey, ListKey, SortedSet, StringKey, UnsortedSet
Defined Under Namespace
Modules: ClassMethods, Connection, DatabaseCommands, Serialization, Settings
Class Attribute Summary collapse
-
.has_related_fields ⇒ Object
readonly
Returns the value of attribute has_related_fields.
-
.registered_types ⇒ Object
readonly
Returns the value of attribute registered_types.
-
.valid_options ⇒ Object
readonly
Returns the value of attribute valid_options.
Instance Attribute Summary collapse
-
#features_enabled ⇒ Object
included
from Features
readonly
Returns the value of attribute features_enabled.
- #logical_database(val = nil) ⇒ Object included from ClassMethods
-
#parent ⇒ Object
included
from ClassMethods
Returns the value of attribute parent.
-
#prefix ⇒ Object
included
from ClassMethods
Returns the value of attribute prefix.
-
#suffix ⇒ Object
included
from ClassMethods
Returns the value of attribute suffix.
-
#uri(val = nil) ⇒ Object
included
from ClassMethods
Returns the value of attribute uri.
Attributes included from Settings
#current_key_version, #delim, #encryption_keys, #encryption_personalization, #logical_database, #prefix, #schema_path, #schema_validator, #schemas, #strict_write_order, #suffix, #transaction_mode
Class Method Summary collapse
-
.feature(feature_name = nil, **options) ⇒ Array?
extended
from Features
Enables a feature for the current class with optional configuration.
- .inherited(obj) ⇒ Object extended from ClassMethods
-
.register(klass, methname) ⇒ Object
extended
from ClassMethods
To be called inside every class that inherits DataType +methname+ is the term used for the class and instance methods that are created for the given +klass+ (e.g. set, list, etc).
-
.registered_type(methname) ⇒ Object
extended
from ClassMethods
Get the registered type class from a given method name +methname+ is the method name used to register the class (e.g. :set, :list, etc) Returns the registered class or nil if not found.
- .relations? ⇒ Boolean extended from ClassMethods
- .valid_keys_only(opts) ⇒ Object extended from ClassMethods
Instance Method Summary collapse
-
#default_expiration ⇒ Numeric
Override the default_expiration instance method to inherit from the parent Horreum when this DataType doesn't have its own explicit default_expiration option.
-
#initialize(keystring, opts = {}) ⇒ DataType
constructor
+keystring+: If parent is set, this will be used as the suffix for dbkey.
-
#warn_if_dirty! ⇒ void
Checks if the parent Horreum object has unsaved scalar field changes and emits a warning (or raises) before a collection write.
Methods included from Features::Autoloader
autoload_files, included, normalize_to_config_name
Methods included from Serialization
#deserialize_value, #deserialize_values, #deserialize_values_with_nil, #serialize_value
Methods included from DatabaseCommands
#current_expiration, #delete!, #echo, #exists?, #expire, #expireat, #move, #persist, #rename, #renamenx, #type
Methods included from Connection
#dbclient, #dbkey, #direct_access, #uri
Methods included from Connection::Behavior
#connect, #create_dbclient, #multi, #normalize_uri, #pipeline, #pipelined, #transaction, #uri=, #url, #url=
Methods included from Settings
#configure, #default_suffix, #pipelined_mode, #pipelined_mode=
Methods included from Base
add_feature, #as_json, #expired?, #expires?, find_feature, #generate_id, #to_json, #to_s, #ttl, #update_expiration, #uuid
Constructor Details
#initialize(keystring, opts = {}) ⇒ DataType
+keystring+: If parent is set, this will be used as the suffix for dbkey. Otherwise this becomes the value of the key. If this is an Array, the elements will be joined.
Options:
:class => A class that responds to from_json. This will be used when loading data from the database to unmarshal the class. JSON serialization is used for all data storage.
:parent => The Familia object that this datatype object belongs to. This can be a class that includes Familia or an instance.
:default_expiration => the time to live in seconds. When not nil, this will set the default expiration for this dbkey whenever #save is called. You can also call it explicitly via #update_expiration.
:default => the default value (String-only)
:dbkey => a hardcoded key to use instead of the deriving the from the name and parent (e.g. a derived key: customer:custid:secret_counter).
:suffix => the suffix to use for the key (e.g. 'scores' in customer:custid:scores). :prefix => the prefix to use for the key (e.g. 'customer' in customer:custid:scores).
Connection precendence: uses the database connection of the parent or the value of opts[:dbclient] or Familia.dbclient (in that order).
125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/familia/data_type.rb', line 125 def initialize(keystring, opts = {}) @keystring = keystring @keystring = @keystring.join(Familia.delim) if @keystring.is_a?(Array) # Remove all keys from the opts that are not in the allowed list @opts = DataType.valid_keys_only(opts || {}) # Apply the options to instance method setters of the same name @opts.each do |k, v| send(:"#{k}=", v) if respond_to? :"#{k}=" end init if respond_to? :init end |
Class Attribute Details
.has_related_fields ⇒ Object (readonly)
Returns the value of attribute has_related_fields.
95 96 97 |
# File 'lib/familia/data_type.rb', line 95 def @has_related_fields end |
.registered_types ⇒ Object (readonly)
Returns the value of attribute registered_types.
95 96 97 |
# File 'lib/familia/data_type.rb', line 95 def registered_types @registered_types end |
.valid_options ⇒ Object (readonly)
Returns the value of attribute valid_options.
95 96 97 |
# File 'lib/familia/data_type.rb', line 95 def @valid_options end |
Instance Attribute Details
#features_enabled ⇒ Object (readonly) Originally defined in module Features
Returns the value of attribute features_enabled.
#logical_database(val = nil) ⇒ Object Originally defined in module ClassMethods
#parent ⇒ Object Originally defined in module ClassMethods
Returns the value of attribute parent.
#prefix ⇒ Object Originally defined in module ClassMethods
Returns the value of attribute prefix.
#suffix ⇒ Object Originally defined in module ClassMethods
Returns the value of attribute suffix.
#uri(val = nil) ⇒ Object Originally defined in module ClassMethods
Returns the value of attribute uri.
Class Method Details
.feature(feature_name = nil, **options) ⇒ Array? Originally defined in module Features
Enables a feature for the current class with optional configuration.
Features are modular capabilities that can be mixed into Familia::Horreum classes. Each feature can be configured with options that are stored per-class, ensuring complete isolation between different models.
.inherited(obj) ⇒ Object Originally defined in module ClassMethods
.register(klass, methname) ⇒ Object Originally defined in module ClassMethods
To be called inside every class that inherits DataType +methname+ is the term used for the class and instance methods that are created for the given +klass+ (e.g. set, list, etc)
.registered_type(methname) ⇒ Object Originally defined in module ClassMethods
Get the registered type class from a given method name +methname+ is the method name used to register the class (e.g. :set, :list, etc) Returns the registered class or nil if not found
.relations? ⇒ Boolean Originally defined in module ClassMethods
.valid_keys_only(opts) ⇒ Object Originally defined in module ClassMethods
Instance Method Details
#default_expiration ⇒ Numeric
Override the default_expiration instance method to inherit from the
parent Horreum when this DataType doesn't have its own explicit
default_expiration option. This enables TTL cascade: when a Horreum
class has default_expiration 1.hour and a relation like set :tags
doesn't specify its own, the tags set will use the parent's TTL.
Precedence:
- Instance-level @default_expiration (set directly)
- Explicit opts:default_expiration
- Parent Horreum's default_expiration (cascade)
- Class-level default (Familia.default_expiration, typically 0)
Relations with no_expiration: true are excluded from cascade and
always return 0 (no TTL).
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/familia/data_type.rb', line 183 def default_expiration return 0 if @opts && @opts[:no_expiration] # Check instance-level override first return @default_expiration if @default_expiration # Check explicit opts from relation declaration return @opts[:default_expiration] if @opts && @opts[:default_expiration] # Inherit from parent Horreum if available if @parent_ref.respond_to?(:default_expiration) parent_exp = @parent_ref.default_expiration return parent_exp if parent_exp && parent_exp > 0 end # Fall back to class-level default self.class.default_expiration end |
#warn_if_dirty! ⇒ void
This method returns an undefined value.
Checks if the parent Horreum object has unsaved scalar field changes and emits a warning (or raises) before a collection write.
This guards against a subtle issue where collection operations (SADD, RPUSH, ZADD, HSET) write to Redis immediately while scalar field changes remain only in memory. If the process crashes before the scalar fields are saved, the collection data is persisted but the scalar data is lost, creating an inconsistent state.
152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/familia/data_type.rb', line 152 def warn_if_dirty! return unless @parent_ref.respond_to?(:dirty?) && @parent_ref.dirty? dirty = @parent_ref.dirty_fields = "Writing to #{self.class.name} #{dbkey} while parent " \ "#{@parent_ref.class.name} has unsaved scalar fields: #{dirty.join(', ')}" if Familia.strict_write_order raise Familia::Problem, else Familia.warn end end |