Skip to content

Typecasting data when inserting into Redis #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Sep 6, 2021
4 changes: 4 additions & 0 deletions lib/kredis/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def kredis_flag(name, key: nil, config: :shared, after_change: nil)
end
end

def kredis_float(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change
end

def kredis_enum(name, key: nil, values:, default:, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, values: values, default: default, config: config, after_change: after_change
end
Expand Down
15 changes: 15 additions & 0 deletions lib/kredis/type/datetime.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Kredis
module Type
class DateTime < ActiveModel::Type::DateTime
def serialize(value)
super&.iso8601(9)
end

def cast_value(value)
super&.to_datetime
end
end
end
end
19 changes: 19 additions & 0 deletions lib/kredis/type/json.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Kredis
module Type
class Json < ActiveModel::Type::Value
def type
:json
end

def cast_value(value)
JSON.load(value)
end

def serialize(value)
JSON.dump(value)
end
end
end
end
56 changes: 22 additions & 34 deletions lib/kredis/type_casting.rb
Original file line number Diff line number Diff line change
@@ -1,47 +1,35 @@
require "json"
require "active_model/type"
require "kredis/type/json"
require "kredis/type/datetime"

module Kredis::TypeCasting
class InvalidType < StandardError; end

VALID_TYPES = %i[ string integer decimal float boolean datetime json ]

def type_to_string(value)
case value
when nil
""
when Integer
value.to_s
when BigDecimal
value.to_d
when Float
value.to_s
when TrueClass, FalseClass
value ? "t" : "f"
when Time, DateTime, ActiveSupport::TimeWithZone
value.iso8601(9)
when Hash
JSON.dump(value)
else
value
end
TYPES = {
string: ActiveModel::Type::String.new,
integer: ActiveModel::Type::Integer.new,
decimal: ActiveModel::Type::Decimal.new,
float: ActiveModel::Type::Float.new,
boolean: ActiveModel::Type::Boolean.new,
datetime: Kredis::Type::DateTime.new,
json: Kredis::Type::Json.new
}

def type_to_string(value, type)
raise InvalidType if type && !TYPES.key?(type)

TYPES[type || :string].serialize(value)
end

def string_to_type(value, type)
raise InvalidType if type && !VALID_TYPES.include?(type)

case type
when nil, :string then value
when :integer then value.to_i
when :decimal then value.to_d
when :float then value.to_f
when :boolean then value == "t" ? true : false
when :datetime then Time.iso8601(value)
when :json then JSON.load(value)
end if value.present?
raise InvalidType if type && !TYPES.key?(type)

TYPES[type || :string].cast(value)
end

def types_to_strings(values)
Array(values).flatten.map { |value| type_to_string(value) }
def types_to_strings(values, type)
Array(values).flatten.map { |value| type_to_string(value, type) }
end

def strings_to_types(values, type)
Expand Down
2 changes: 2 additions & 0 deletions lib/kredis/types/enum.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "active_support/core_ext/object/inclusion"

class Kredis::Types::Enum < Kredis::Types::Proxying
proxying :set, :get, :del

Expand Down
7 changes: 3 additions & 4 deletions lib/kredis/types/hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,20 @@ def []=(key, value)
update key => value
end


def update(**entries)
hset types_to_strings(entries) if entries.flatten.any?
hset entries.transform_values{ |val| type_to_string(val, typed) } if entries.flatten.any?
end

def values_at(*keys)
strings_to_types(hmget(keys) || [], typed)
end

def delete(*keys)
hdel types_to_strings(keys) if keys.flatten.any?
hdel keys if keys.flatten.any?
end

def remove
del
del
end

def entries
Expand Down
6 changes: 3 additions & 3 deletions lib/kredis/types/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ def elements
alias to_a elements

def remove(*elements)
types_to_strings(elements).each { |element| lrem 0, element }
types_to_strings(elements, typed).each { |element| lrem 0, element }
end

def prepend(*elements)
lpush types_to_strings(elements) if elements.flatten.any?
lpush types_to_strings(elements, typed) if elements.flatten.any?
end

def append(*elements)
rpush types_to_strings(elements) if elements.flatten.any?
rpush types_to_strings(elements, typed) if elements.flatten.any?
end
alias << append
end
4 changes: 2 additions & 2 deletions lib/kredis/types/scalar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ class Kredis::Types::Scalar < Kredis::Types::Proxying
attr_accessor :typed, :default, :expires_in

def value=(value)
set type_to_string(value), ex: expires_in
set type_to_string(value, typed), ex: expires_in
end

def value
value_after_casting = string_to_type(get, typed)

if value_after_casting.nil?
default
else
Expand Down
6 changes: 3 additions & 3 deletions lib/kredis/types/set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ def members
alias to_a members

def add(*members)
sadd types_to_strings(members) if members.flatten.any?
sadd types_to_strings(members, typed) if members.flatten.any?
end
alias << add

def remove(*members)
srem types_to_strings(members) if members.flatten.any?
srem types_to_strings(members, typed) if members.flatten.any?
end

def replace(*members)
Expand All @@ -25,7 +25,7 @@ def replace(*members)
end

def include?(member)
sismember type_to_string(member)
sismember type_to_string(member, typed)
end

def size
Expand Down
9 changes: 8 additions & 1 deletion test/attributes_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Person
kredis_integer :age
kredis_decimal :salary
kredis_datetime :last_seen_at
kredis_float :height
kredis_enum :morning, values: %w[ bright blue black ], default: "bright"
kredis_slot :attention
kredis_slots :meetings, available: 3
Expand Down Expand Up @@ -103,7 +104,13 @@ class AttributesTest < ActiveSupport::TestCase
test "decimal" do
@person.salary.value = 10000.07
assert_equal 10000.07, @person.salary.value
assert_equal "10000.07", @person.salary.to_s
assert_equal "0.1000007e5", @person.salary.to_s
end

test "float" do
@person.height.value = 1.85
assert_equal 1.85, @person.height.value
assert_equal "1.85", @person.height.to_s
end

test "datetime" do
Expand Down
8 changes: 7 additions & 1 deletion test/types/scalar_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ class ScalarTest < ActiveSupport::TestCase
assert_nil datetime.value
end

test "datetime casting Dates" do
datetime = Kredis.datetime "myscalar"
datetime.value = Date.current
assert_equal Date.current.to_datetime, datetime.value
end

test "json" do
json = Kredis.json "myscalar"
json.value = { "one" => 1, "string" => "hello" }
Expand All @@ -58,7 +64,7 @@ class ScalarTest < ActiveSupport::TestCase

test "invalid type" do
nothere = Kredis.scalar "myscalar", typed: :nothere
nothere.value = true
assert_raises(Kredis::TypeCasting::InvalidType) { nothere.value = true }

assert_raises(Kredis::TypeCasting::InvalidType) { nothere.value }
end
Expand Down
4 changes: 4 additions & 0 deletions test/types/unique_list_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,9 @@ class UniqueListTest < ActiveSupport::TestCase

@list.remove(2)
assert_equal [ 1 ], @list.elements

@list.append [ "1-a", 2 ]

assert_equal [ 1, 2 ], @list.elements
end
end