Class: Familia::ListKey
- Includes:
- DataType::CollectionBase
- Defined in:
- lib/familia/data_type/types/listkey.rb
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 DataType::ClassMethods
-
#parent ⇒ Object
included
from DataType::ClassMethods
Returns the value of attribute parent.
-
#prefix ⇒ Object
included
from DataType::ClassMethods
Returns the value of attribute prefix.
-
#suffix ⇒ Object
included
from DataType::ClassMethods
Returns the value of attribute suffix.
-
#uri(val = nil) ⇒ Object
included
from DataType::ClassMethods
Returns the value of attribute uri.
Attributes included from Settings
#current_key_version, #default_expiration, #delim, #encryption_hkdf_salt, #encryption_hkdf_salt_history, #encryption_keys, #encryption_personalization, #encryption_personalization_history, #logical_database, #prefix, #raise_on_unsaved_parent_write, #schema_path, #schema_validator, #schemas, #strict_write_order, #suffix, #transaction_mode
Class Method Summary collapse
-
.supports_max_length? ⇒ Boolean
ListKey implements :max_length (capped collection, issue #351) with per-end semantics — intentionally not unified with SortedSet's score-based trim: #push trims from the head (keeps the newest tail, LTRIM -max_length -1), while #unshift trims from the tail (keeps the newest head, LTRIM 0 max_length-1).
Instance Method Summary collapse
- #<<(val) ⇒ Object (also: #add_element, #add)
- #[](idx, count = nil) ⇒ Object (also: #slice)
- #at(idx) ⇒ Object
- #collectraw ⇒ Object
-
#each(batch_size: 100) {|element| ... } ⇒ Enumerator, self
Iterates over elements of the list.
- #eachraw ⇒ Object
- #eachraw_with_index ⇒ Object
-
#element_count ⇒ Integer
(also: #size, #length, #count)
Returns the number of elements in the list.
- #empty? ⇒ Boolean
-
#enforce_max_length!(keep: :tail) ⇒ Integer, Redis::Future
Trims an already-oversized list down to its :max_length cap.
- #first ⇒ Object
-
#insert(position, pivot, value) ⇒ Integer
(also: #linsert)
Inserts an element before or after a pivot element.
- #last ⇒ Object
- #member?(value) ⇒ Boolean
- #members(count = -1)) ⇒ Object (also: #all, #to_a)
- #membersraw(count = -1)) ⇒ Object
-
#move(destination, wherefrom, whereto) ⇒ Object?
(also: #lmove)
Moves an element from this list to another list atomically.
-
#pop(count = nil) ⇒ Object, ...
Removes and returns the last element(s) from the list.
- #push(*values) ⇒ Object (also: #append)
-
#pushx(*values) ⇒ Integer
(also: #rpushx)
Pushes values only if the list already exists.
- #range(sidx = 0, eidx = -1)) ⇒ Object
- #rangeraw(sidx = 0, eidx = -1)) ⇒ Object
-
#remove_element(value, count = 0) ⇒ Integer
(also: #remove)
Removes elements equal to value from the list.
- #selectraw ⇒ Object
-
#set(index, value) ⇒ String
(also: #lset)
Sets the element at the specified index.
-
#shift(count = nil) ⇒ Object, ...
Removes and returns the first element(s) from the list.
-
#trim(start, stop) ⇒ String
(also: #ltrim)
Trims the list to the specified range.
- #unshift(*values) ⇒ Object (also: #prepend)
-
#unshiftx(*values) ⇒ Integer
(also: #lpushx)
Pushes values to the head only if the list already exists.
Methods included from DataType::CollectionBase
#collection_type?, #each_record
Methods included from Features::Autoloader
autoload_files, included, normalize_to_config_name
Methods included from DataType::Serialization
#deserialize_value, #deserialize_values, #deserialize_values_with_nil, #serialize_value, #strip_legacy_json_encoding
Methods included from DataType::DatabaseCommands
#current_expiration, #delete!, #echo, #exists?, #expire, #expireat, #persist, #rename, #renamenx, #type
Methods included from DataType::Connection
Methods included from Connection::Behavior
#connect, #create_dbclient, #multi, #normalize_uri, #pipeline, #pipelined, #transaction, #uri=, #url, #url=
Methods included from Settings
#configure, #default_suffix, #dirty_write_warnings, #dirty_write_warnings=, #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
This class inherits a constructor from Familia::DataType
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 DataType::ClassMethods
#parent ⇒ Object Originally defined in module DataType::ClassMethods
Returns the value of attribute parent.
#prefix ⇒ Object Originally defined in module DataType::ClassMethods
Returns the value of attribute prefix.
#suffix ⇒ Object Originally defined in module DataType::ClassMethods
Returns the value of attribute suffix.
#uri(val = nil) ⇒ Object Originally defined in module DataType::ClassMethods
Returns the value of attribute uri.
Class Method Details
.supports_max_length? ⇒ Boolean
ListKey implements :max_length (capped collection, issue #351) with per-end semantics — intentionally not unified with SortedSet's score-based trim: #push trims from the head (keeps the newest tail, LTRIM -max_length -1), while #unshift trims from the tail (keeps the newest head, LTRIM 0 max_length-1). Either way the max_length most recently written elements survive. NOT capped: pushx/unshiftx, insert, set, move destinations, and external writers.
16 17 18 |
# File 'lib/familia/data_type/types/listkey.rb', line 16 def self.supports_max_length? true end |
Instance Method Details
#<<(val) ⇒ Object Also known as: add_element, add
58 59 60 |
# File 'lib/familia/data_type/types/listkey.rb', line 58 def <<(val) push(val) end |
#[](idx, count = nil) ⇒ Object Also known as: slice
118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/familia/data_type/types/listkey.rb', line 118 def [](idx, count = nil) if idx.is_a? Range range idx.first, idx.last elsif count case count <=> 0 when 1 then range(idx, idx + count - 1) when 0 then [] when -1 then nil end else at idx end end |
#at(idx) ⇒ Object
222 223 224 |
# File 'lib/familia/data_type/types/listkey.rb', line 222 def at(idx) deserialize_value dbclient.lindex(dbkey, idx) end |
#collectraw ⇒ Object
214 215 216 |
# File 'lib/familia/data_type/types/listkey.rb', line 214 def collectraw(&) rangeraw.collect(&) end |
#each(batch_size: 100) {|element| ... } ⇒ Enumerator, self
Iterates over elements of the list.
Uses LRANGE pagination for memory-efficient iteration over large lists. Unlike sets, Redis lists do not support SCAN, so we paginate through the list using index ranges.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/familia/data_type/types/listkey.rb', line 187 def each(batch_size: 100, &block) return to_enum(:each, batch_size: batch_size) unless block offset = 0 loop do # LRANGE is inclusive on both ends, so end_idx = offset + batch_size - 1 elements = range(offset, offset + batch_size - 1) break if elements.empty? elements.each(&block) offset += elements.size # If we got fewer than batch_size, we've reached the end break if elements.size < batch_size end self end |
#eachraw ⇒ Object
206 207 208 |
# File 'lib/familia/data_type/types/listkey.rb', line 206 def eachraw(&) rangeraw.each(&) end |
#eachraw_with_index ⇒ Object
210 211 212 |
# File 'lib/familia/data_type/types/listkey.rb', line 210 def eachraw_with_index(&) rangeraw.each_with_index(&) end |
#element_count ⇒ Integer Also known as: size, length, count
Returns the number of elements in the list
22 23 24 |
# File 'lib/familia/data_type/types/listkey.rb', line 22 def element_count dbclient.llen dbkey end |
#empty? ⇒ Boolean
29 30 31 |
# File 'lib/familia/data_type/types/listkey.rb', line 29 def empty? element_count.zero? end |
#enforce_max_length!(keep: :tail) ⇒ Integer, Redis::Future
Trims an already-oversized list down to its :max_length cap. Capped writes enforce the cap going forward, but nothing runs at definition time: adding max_length: to a live collection leaves the existing overage in place until the next push/unshift. Call this once, e.g. as a migration step, to enforce the cap immediately.
The cap's per-end semantics belong to the write method (push keeps the tail, unshift keeps the head), so a manual trim must be told which end to keep: keep: :tail matches a push-fed list (the default), keep: :head an unshift-fed one.
LLEN and LTRIM run in one MULTI so the removed count is exact even under concurrent writes. Inside a caller transaction or pipeline the pair is queued bare and the pre-trim length's future is returned instead of a count.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/familia/data_type/types/listkey.rb', line 259 def enforce_max_length!(keep: :tail) max = require_max_length! start, stop = case keep when :tail then [-max, -1] when :head then [0, max - 1] else raise ArgumentError, "keep must be :tail or :head, got #{keep.inspect}" end warn_if_dirty! length = execute_capped_write do |conn| ret = conn.llen(dbkey) conn.ltrim(dbkey, start, stop) ret end update_expiration length.is_a?(Integer) ? [length - max, 0].max : length end |
#first ⇒ Object
362 363 364 |
# File 'lib/familia/data_type/types/listkey.rb', line 362 def first at 0 end |
#insert(position, pivot, value) ⇒ Integer Also known as: linsert
Inserts an element before or after a pivot element
295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/familia/data_type/types/listkey.rb', line 295 def insert(position, pivot, value) warn_if_dirty! pos = case position when :before, 'BEFORE' then 'BEFORE' when :after, 'AFTER' then 'AFTER' else raise ArgumentError, "position must be :before or :after, got #{position.inspect}" end result = dbclient.linsert dbkey, pos, serialize_value(pivot), serialize_value(value) update_expiration if Familia.positive?(result) == true result end |
#last ⇒ Object
366 367 368 |
# File 'lib/familia/data_type/types/listkey.rb', line 366 def last at(-1) end |
#member?(value) ⇒ Boolean
133 134 135 |
# File 'lib/familia/data_type/types/listkey.rb', line 133 def member?(value) !dbclient.lpos(dbkey, serialize_value(value)).nil? end |
#members(count = -1)) ⇒ Object Also known as: all, to_a
158 159 160 161 162 |
# File 'lib/familia/data_type/types/listkey.rb', line 158 def members(count = -1) echo :members, Familia.pretty_stack(limit: 1) if Familia.debug count -= 1 if count.positive? range 0, count end |
#membersraw(count = -1)) ⇒ Object
166 167 168 169 |
# File 'lib/familia/data_type/types/listkey.rb', line 166 def membersraw(count = -1) count -= 1 if count.positive? rangeraw 0, count end |
#move(destination, wherefrom, whereto) ⇒ Object? Also known as: lmove
Moves an element from this list to another list atomically
314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/familia/data_type/types/listkey.rb', line 314 def move(destination, wherefrom, whereto) warn_if_dirty! dest_key = destination.respond_to?(:dbkey) ? destination.dbkey : destination from = wherefrom.to_s.upcase to = whereto.to_s.upcase unless %w[LEFT RIGHT].include?(from) && %w[LEFT RIGHT].include?(to) raise ArgumentError, 'wherefrom and whereto must be :left or :right' end result = dbclient.lmove dbkey, dest_key, from, to update_expiration deserialize_value result end |
#pop(count = nil) ⇒ Object, ...
Removes and returns the last element(s) from the list
91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/familia/data_type/types/listkey.rb', line 91 def pop(count = nil) warn_if_dirty! ret = if count result = dbclient.rpop(dbkey, count) result.nil? ? nil : deserialize_values(*result) else deserialize_value dbclient.rpop(dbkey) end update_expiration ret end |
#push(*values) ⇒ Object Also known as: append
This method executes a Redis RPUSH immediately, unlike scalar field setters which are deferred until save. If the parent object has unsaved scalar field changes, consider calling save first to avoid split-brain state.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/familia/data_type/types/listkey.rb', line 36 def push(*values) warn_if_dirty! echo :push, Familia.pretty_stack(limit: 1) if Familia.debug serialized = values.flatten.compact.map { |v| serialize_value(v) } unless serialized.empty? if (max = @opts[:max_length]) # Keep the tail: RPUSH appends there, so trimming from the head # retains the max_length newest elements. execute_capped_write do |conn| ret = conn.rpush(dbkey, serialized) conn.ltrim(dbkey, -max, -1) ret end else dbclient.rpush(dbkey, serialized) end end update_expiration self end |
#pushx(*values) ⇒ Integer Also known as: rpushx
Pushes values only if the list already exists
333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/familia/data_type/types/listkey.rb', line 333 def pushx(*values) return 0 if values.empty? serialized_values = values.flatten.compact.map { |v| serialize_value(v) } return 0 if serialized_values.empty? warn_if_dirty! result = dbclient.rpushx(dbkey, serialized_values) update_expiration if Familia.positive?(result) == true result end |
#range(sidx = 0, eidx = -1)) ⇒ Object
149 150 151 152 |
# File 'lib/familia/data_type/types/listkey.rb', line 149 def range(sidx = 0, eidx = -1) elements = rangeraw sidx, eidx deserialize_values(*elements) end |
#rangeraw(sidx = 0, eidx = -1)) ⇒ Object
154 155 156 |
# File 'lib/familia/data_type/types/listkey.rb', line 154 def rangeraw(sidx = 0, eidx = -1) dbclient.lrange(dbkey, sidx, eidx) end |
#remove_element(value, count = 0) ⇒ Integer Also known as: remove
Removes elements equal to value from the list
141 142 143 144 145 146 |
# File 'lib/familia/data_type/types/listkey.rb', line 141 def remove_element(value, count = 0) warn_if_dirty! ret = dbclient.lrem dbkey, count, serialize_value(value) update_expiration ret end |
#selectraw ⇒ Object
218 219 220 |
# File 'lib/familia/data_type/types/listkey.rb', line 218 def selectraw(&) rangeraw.select(&) end |
#set(index, value) ⇒ String Also known as: lset
Sets the element at the specified index
282 283 284 285 286 287 |
# File 'lib/familia/data_type/types/listkey.rb', line 282 def set(index, value) warn_if_dirty! result = dbclient.lset dbkey, index, serialize_value(value) update_expiration result end |
#shift(count = nil) ⇒ Object, ...
Removes and returns the first element(s) from the list
106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/familia/data_type/types/listkey.rb', line 106 def shift(count = nil) warn_if_dirty! ret = if count result = dbclient.lpop(dbkey, count) result.nil? ? nil : deserialize_values(*result) else deserialize_value dbclient.lpop(dbkey) end update_expiration ret end |
#trim(start, stop) ⇒ String Also known as: ltrim
Trims the list to the specified range
230 231 232 233 234 235 |
# File 'lib/familia/data_type/types/listkey.rb', line 230 def trim(start, stop) warn_if_dirty! result = dbclient.ltrim dbkey, start, stop update_expiration result end |
#unshift(*values) ⇒ Object Also known as: prepend
This method executes a Redis LPUSH immediately, unlike scalar field setters which are deferred until save. If the parent object has unsaved scalar field changes, consider calling save first to avoid split-brain state.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/familia/data_type/types/listkey.rb', line 67 def unshift(*values) warn_if_dirty! serialized = values.flatten.compact.map { |v| serialize_value(v) } unless serialized.empty? if (max = @opts[:max_length]) # Keep the head: LPUSH prepends there, so trimming from the tail # retains the max_length newest elements. execute_capped_write do |conn| ret = conn.lpush(dbkey, serialized) conn.ltrim(dbkey, 0, max - 1) ret end else dbclient.lpush(dbkey, serialized) end end update_expiration self end |
#unshiftx(*values) ⇒ Integer Also known as: lpushx
Pushes values to the head only if the list already exists
349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/familia/data_type/types/listkey.rb', line 349 def unshiftx(*values) return 0 if values.empty? serialized_values = values.flatten.compact.map { |v| serialize_value(v) } return 0 if serialized_values.empty? warn_if_dirty! result = dbclient.lpushx(dbkey, serialized_values) update_expiration if Familia.positive?(result) == true result end |