class Redstruct::List

Class to manipulate redis lists, modeled after Ruby's Array class. TODO: Add maximum instance variable and modify all methods (where applicable) to take it into account.

Public Instance Methods

<<(item) click to toggle source

Pushes the given element onto the list. As << is a binary operator, it can only take one argument in. It's more of a convenience method. @param [#to_s] item the item to append to the list @return [Integer] 1 if appended, 0 otherwise

# File lib/redstruct/list.rb, line 86
def <<(item)
  return append(item)
end
[](index) click to toggle source

Returns the item located at index @param [Integer] index the item located at index @return [String, nil] nil if no item at index, otherwise the value

# File lib/redstruct/list.rb, line 31
def [](index)
  return self.connection.lindex(@key, index.to_i)
end
[]=(index, value) click to toggle source

Sets or updates the value for item at index @param [Integer] index the index @param [#to_s] value the new value @raise Redis::BaseError when index is out of range @return [Boolean] true if set, false otherwise

# File lib/redstruct/list.rb, line 40
def []=(index, value)
  return coerce_bool(set_script(keys: @key, argv: [index.to_i, value]))
end
append(*items, max: 0, exists: false) click to toggle source

Appends the given items (from the right) to the list @param [Array<#to_s>] items the items to append @param [Integer] max optional; if given, appends the items and trims down the list to max afterwards @param [Boolean] exists optional; if true, only appends iff the list already exists (i.e. is not empty) @return [Integer] the number of items appended to the list

# File lib/redstruct/list.rb, line 70
def append(*items, max: 0, exists: false)
  max = max.to_i
  results = if max.positive? || exists
    push_and_trim_script(keys: @key, argv: [max - 1, false, exists] + items)
  else
    self.connection.rpush(@key, items)
  end

  return results
end
Also aliased as: push
clear() click to toggle source

Clears the set by simply removing the key from the DB @see Redstruct::Struct#clear

# File lib/redstruct/list.rb, line 17
def clear
  delete
end
empty?() click to toggle source

Checks if the set is empty by checking if the key actually exists on the underlying redis db @see Redstruct::Struct#exists? @return [Boolean] true if it is empty, false otherwise

# File lib/redstruct/list.rb, line 24
def empty?
  return !exists?
end
insert(value, index) click to toggle source

Inserts the given value at the given zero-based index. TODO: Support multiple insertion like Array#insert? The biggest issue here is that concatenating lists in Lua is O(n), so on very large lists, this operation would become slow. There are Redis Modules which implement splice operations (so a O(1) list split/merge), but there's no way to guarantee if the module will be present. Perhaps provide optional support if the module is detected? @param [#to_s] value the value to insert @param [#to_i] index the index at which to insert the value

# File lib/redstruct/list.rb, line 53
def insert(value, index)
  result = case index
  when 0 then prepend(value)
  when -1 then append(value)
  else
    index += 1 if index.negative?
    insert_script(keys: @key, argv: [value, index])
  end

  return coerce_bool(result)
end
pop(size = 1, timeout: nil) click to toggle source

Pops an item from the list, optionally blocking to wait until the list is non-empty @param [Integer] timeout the amount of time to wait in seconds; if 0, waits indefinitely @return [nil, String] nil if the list was empty, otherwise the item

# File lib/redstruct/list.rb, line 115
def pop(size = 1, timeout: nil)
  raise ArgumentError, 'size must be positive' unless size.positive?

  if timeout.nil?
    return self.connection.rpop(@key) if size == 1
    return shift_pop_script(keys: @key, argv: [-size, -1, 1])
  else
    raise ArgumentError, 'timeout is only supported if size == 1' unless size == 1
    return self.connection.brpop(@key, timeout: timeout)&.last
  end
end
popshift(list, timeout: nil) click to toggle source

Pops an element from this list and shifts it onto the given list. @param [Redstruct::List] list the list to shift the element onto @param [#to_i] timeout optional timeout to wait for in seconds @return [String] the element that was popped from the list and pushed onto the other

# File lib/redstruct/list.rb, line 146
def popshift(list, timeout: nil)
  raise ArgumentError, 'list must respond to #key' unless list.respond_to?(:key)

  if timeout.nil?
    return self.connection.rpoplpush(@key, list.key)
  else
    return self.connection.brpoplpush(@key, list.key, timeout: timeout)
  end
end
prepend(*items, max: nil, exists: false) click to toggle source

Prepends the given items (from the right) to the list @param [Array<#to_s>] items the items to prepend @param [Integer] max optional; if given, prepends the items and trims down the list to max afterwards @param [Boolean] exists optional; if true, only prepends iff the list already exists (i.e. is not empty) @return [Integer] the number of items prepended to the list

# File lib/redstruct/list.rb, line 95
def prepend(*items, max: nil, exists: false)
  max = max.to_i

  # redis literally prepends each element one at a time, so 1 2 will end up 2 1
  # to keep behaviour in sync with Array#unshift we preemptively reverse the list
  items = items.reverse

  results = if max.positive? || exists
    push_and_trim_script(keys: @key, argv: [max - 1, true, exists] + items)
  else
    self.connection.lpush(@key, items)
  end

  return results
end
Also aliased as: unshift
push(*items, max: 0, exists: false)
Alias for: append
remove(value, count: 1) click to toggle source

Removes the given item from the list. @param [Integer] count count > 0: Remove items equal to value moving from head to tail.

count < 0: Remove items equal to value moving from tail to head.
count = 0: Remove all items equal to value.

@return [Integer] the number of items removed

# File lib/redstruct/list.rb, line 161
def remove(value, count: 1)
  self.connection.lrem(@key, count.to_i, value)
end
shift(size = 1, timeout: nil) click to toggle source

Shifts an item from the list, optionally blocking to wait until the list is non-empty @param [Integer] timeout the amount of time to wait in seconds; if 0, waits indefinitely @return [nil, String] nil if the list was empty, otherwise the item

# File lib/redstruct/list.rb, line 130
def shift(size = 1, timeout: nil)
  raise ArgumentError, 'size must be positive' unless size.positive?

  if timeout.nil?
    return self.connection.lpop(@key) if size == 1
    return shift_pop_script(keys: @key, argv: [0, size - 1, 0])
  else
    raise ArgumentError, 'timeout is only supported if size == 1' unless size == 1
    return self.connection.blpop(@key, timeout: timeout)&.last
  end
end
size() click to toggle source

Checks how many items are in the list. @return [Integer] the number of items in the list

# File lib/redstruct/list.rb, line 167
def size
  return self.connection.llen(@key)
end
slice(start: 0, length: -1) click to toggle source

Returns a slice of the list starting at start (inclusively), up to length (inclusively) @example

pry> list.slice(start: 1, length: 10) #=> Array<...> # array with 11 items

@param [Integer] start the starting index for the slice; if start is larger than the end of the list, an empty list is returned @param [Integer] length the length of the slice (inclusively); if -1, returns everything @return [Array<String>] the requested slice, or an empty list

# File lib/redstruct/list.rb, line 177
def slice(start: 0, length: -1)
  length = length.to_i
  end_index = length.positive? ? start + length - 1 : length

  return self.connection.lrange(@key, start.to_i, end_index)
end
to_a() click to toggle source

Loads all items into memory and returns an array. NOTE: if the list is expected to be large, use to_enum @return [Array<String>] the items in the list

# File lib/redstruct/list.rb, line 187
def to_a
  return slice(start: 0, length: -1)
end
to_enum(match: '*', count: 10) click to toggle source

Since the list can be modified in between loops, this does not guarantee completion of the operation, nor that every single element of the list will be visited once; rather, it guarantees that it loops until no more elements are returned, using an incrementing offset. This means that should elements be removed in the meantime, they will not be seen, and others might be skipped as a result of this. If elements are added, it is however not an issue (although keep in mind that if elements are added faster than consumed, this can loop forever) @return [Enumerator] base enumerator to iterate of the list elements

# File lib/redstruct/list.rb, line 200
def to_enum(match: '*', count: 10)
  pattern = Regexp.compile("^#{Regexp.escape(match).gsub('\*', '.*')}$")

  return Enumerator.new do |yielder|
    offset = 0
    loop do
      items = slice(start: offset, length: offset + count)

      offset += items.size
      matched = items.select { |item| item =~ pattern }
      yielder << matched unless matched.empty?

      raise StopIteration if items.size < count
    end
  end
end
unshift(*items, max: nil, exists: false)
Alias for: prepend