Module: Redcord::Serializer::ClassMethods

Extended by:
T::Sig
Defined in:
lib/redcord/serializer.rb

Constant Summary collapse

TIME_TYPES =

Redis only allows range queries on floats. To allow range queries on the Ruby Time type, encode_attr_value and decode_attr_value will implicitly encode and decode Time attributes to a float.

T.let(Set[Time, T.nilable(Time)], T::Set[T.untyped])

Instance Method Summary collapse

Instance Method Details

#coerce_and_set_id(redis_hash, id) ⇒ Object



194
195
196
197
198
199
200
# File 'lib/redcord/serializer.rb', line 194

def coerce_and_set_id(redis_hash, id)
  # Coerce each serialized result returned from Redis back into Model
  # instance
  instance = TypeCoerce.send(:[], self).new.from(from_redis_hash(redis_hash))
  instance.send(:id=, id)
  instance
end

#decode_attr_value(attribute, val) ⇒ Object



47
48
49
50
51
52
53
54
55
56
# File 'lib/redcord/serializer.rb', line 47

def decode_attr_value(attribute, val)
  if !val.blank? && TIME_TYPES.include?(props[attribute][:type])
    val = val.to_i
    nsec = val >= 0 ? val % 1_000_000_000 : -val % 1_000_000_000

    Time.zone.at(val / 1_000_000_000).change(nsec: nsec)
  else
    val
  end
end

#encode_attr_value(attribute, val) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
# File 'lib/redcord/serializer.rb', line 34

def encode_attr_value(attribute, val)
  if !val.blank? && TIME_TYPES.include?(props[attribute][:type])
    time_in_nano_sec = val.to_i * 1_000_000_000
    time_in_nano_sec >= 0 ? time_in_nano_sec + val.nsec : time_in_nano_sec - val.nsec
  elsif val.is_a?(Float)
    # Encode as round-trippable float64
    '%1.16e' % [val]
  else
    val
  end
end

#encode_range_index_attr_val(attribute, val) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/redcord/serializer.rb', line 162

def encode_range_index_attr_val(attribute, val)
  if val.is_a?(Redcord::RangeInterval)
    # nil is treated as -inf and +inf. This is supported in Redis sorted
    # sets so clients aren't required to know the highest and lowest scores
    # in a range
    min_val = !val.min ? '-inf' : encode_attr_value(attribute, val.min)
    max_val = !val.max ? '+inf' : encode_attr_value(attribute, val.max)

    # In Redis, by default min and max is closed. You can prefix the score
    # with '(' to specify an open interval.
    min_val = val.min_exclusive ? '(' + min_val.to_s : min_val.to_s
    max_val = val.max_exclusive ? '(' + max_val.to_s : max_val.to_s
    [min_val, max_val]
  else
    # Equality queries for range indices are be passed to redis as a range
    # [val, val].
    encoded_val = encode_attr_value(attribute, val)
    [encoded_val, encoded_val]
  end
end

#from_redis_hash(args) ⇒ Object



223
224
225
# File 'lib/redcord/serializer.rb', line 223

def from_redis_hash(args)
  args.map { |key, val| [key, decode_attr_value(key.to_sym, val)] }.to_h
end

#get_attr_type(attr_key) ⇒ Object



184
185
186
# File 'lib/redcord/serializer.rb', line 184

def get_attr_type(attr_key)
  props[attr_key][:type_object]
end

#model_keyObject



203
204
205
# File 'lib/redcord/serializer.rb', line 203

def model_key
  "Redcord:#{name}"
end

#to_redis_hash(args) ⇒ Object



212
213
214
215
216
# File 'lib/redcord/serializer.rb', line 212

def to_redis_hash(args)
  args.map do |key, val|
    [key.to_sym, encode_attr_value(key.to_sym, val)]
  end.to_h
end

#validate_and_adjust_custom_index_query_conditions(query_conditions) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
# File 'lib/redcord/serializer.rb', line 106

def validate_and_adjust_custom_index_query_conditions(query_conditions)
  adjusted_query_conditions = query_conditions.clone
  query_conditions.each do |attr_key, condition|
    if !condition.is_a?(Array)
      adjusted_query_conditions[attr_key] = [condition, condition]
    elsif condition[0].to_s[0] == '(' or condition[1].to_s[0] == '('
      raise(Redcord::CustomIndexInvalidQuery, "Custom index doesn't support exclusive ranges")
    end
  end
  adjusted_query_conditions
end

#validate_attr_type(attr_val, attr_type) ⇒ Object



146
147
148
149
150
151
152
153
154
# File 'lib/redcord/serializer.rb', line 146

def validate_attr_type(attr_val, attr_type)
  if (attr_type.is_a?(Class) && !attr_val.is_a?(attr_type)) ||
     (attr_type.is_a?(T::Types::Base) && !attr_type.valid?(attr_val))
    raise(
      Redcord::WrongAttributeType,
      "Expected type #{attr_type}, got #{attr_val.class.name}",
    )
  end
end

#validate_index_attributes(attr_keys, custom_index_name: nil) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/redcord/serializer.rb', line 79

def validate_index_attributes(attr_keys, custom_index_name: nil)
  custom_index_attributes = class_variable_get(:@@custom_index_attributes)[custom_index_name]
  attr_keys.each do |attr_key|
    next if attr_key == shard_by_attribute

    if !custom_index_attributes.empty?
      if !custom_index_attributes.include?(attr_key)
        raise(
          Redcord::AttributeNotIndexed,
          "#{attr_key} is not a part of #{custom_index_name} index.",
        )
      end
    else
      if !class_variable_get(:@@index_attributes).include?(attr_key) &&
        !class_variable_get(:@@range_index_attributes).include?(attr_key)
        raise(
          Redcord::AttributeNotIndexed,
          "#{attr_key} is not an indexed attribute.",
        )
      end
    end
  end
end

#validate_range_attr_types(attr_val, attr_type) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/redcord/serializer.rb', line 124

def validate_range_attr_types(attr_val, attr_type)
  # Validate attribute types for range index attributes
  if attr_val.is_a?(Redcord::RangeInterval)
    validate_attr_type(
      attr_val.min,
      T.cast(T.nilable(attr_type), T::Types::Base),
    )
    validate_attr_type(
      attr_val.max,
      T.cast(T.nilable(attr_type), T::Types::Base),
    )
  else
    validate_attr_type(attr_val, attr_type)
  end
end

#validate_types_and_encode_query(attr_key, attr_val) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/redcord/serializer.rb', line 59

def validate_types_and_encode_query(attr_key, attr_val)
  # Validate attribute types for index attributes
  attr_type = get_attr_type(attr_key)
  if class_variable_get(:@@index_attributes).include?(attr_key) || attr_key == shard_by_attribute
    validate_attr_type(attr_val, attr_type)
  else
    validate_range_attr_types(attr_val, attr_type)

    # Range index attributes need to be further encoded into a format
    # understood by the Lua script.
    unless attr_val.nil?
      attr_val = encode_range_index_attr_val(attr_key, attr_val)
    end
  end
  attr_val
end