diff --git a/lib/kredis/attributes.rb b/lib/kredis/attributes.rb index 087009a..d2c1071 100644 --- a/lib/kredis/attributes.rb +++ b/lib/kredis/attributes.rb @@ -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 diff --git a/lib/kredis/type/datetime.rb b/lib/kredis/type/datetime.rb new file mode 100644 index 0000000..8841772 --- /dev/null +++ b/lib/kredis/type/datetime.rb @@ -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 diff --git a/lib/kredis/type/json.rb b/lib/kredis/type/json.rb new file mode 100644 index 0000000..4183d5f --- /dev/null +++ b/lib/kredis/type/json.rb @@ -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 diff --git a/lib/kredis/type_casting.rb b/lib/kredis/type_casting.rb index 39741e8..412d485 100644 --- a/lib/kredis/type_casting.rb +++ b/lib/kredis/type_casting.rb @@ -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) diff --git a/lib/kredis/types/enum.rb b/lib/kredis/types/enum.rb index 3ad3b73..da58d81 100644 --- a/lib/kredis/types/enum.rb +++ b/lib/kredis/types/enum.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/object/inclusion" + class Kredis::Types::Enum < Kredis::Types::Proxying proxying :set, :get, :del diff --git a/lib/kredis/types/hash.rb b/lib/kredis/types/hash.rb index a21e0b8..1b1a45d 100644 --- a/lib/kredis/types/hash.rb +++ b/lib/kredis/types/hash.rb @@ -13,9 +13,8 @@ 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) @@ -23,11 +22,11 @@ def values_at(*keys) 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 diff --git a/lib/kredis/types/list.rb b/lib/kredis/types/list.rb index 0f6c84a..d13a617 100644 --- a/lib/kredis/types/list.rb +++ b/lib/kredis/types/list.rb @@ -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 diff --git a/lib/kredis/types/scalar.rb b/lib/kredis/types/scalar.rb index a552eda..03ddf52 100644 --- a/lib/kredis/types/scalar.rb +++ b/lib/kredis/types/scalar.rb @@ -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 diff --git a/lib/kredis/types/set.rb b/lib/kredis/types/set.rb index b88f17c..5a9df53 100644 --- a/lib/kredis/types/set.rb +++ b/lib/kredis/types/set.rb @@ -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) @@ -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 diff --git a/test/attributes_test.rb b/test/attributes_test.rb index b16a113..c24aaf3 100644 --- a/test/attributes_test.rb +++ b/test/attributes_test.rb @@ -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 @@ -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 diff --git a/test/types/scalar_test.rb b/test/types/scalar_test.rb index 3c7c078..03a3ddc 100644 --- a/test/types/scalar_test.rb +++ b/test/types/scalar_test.rb @@ -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" } @@ -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 diff --git a/test/types/unique_list_test.rb b/test/types/unique_list_test.rb index 71ceb19..be01ebd 100644 --- a/test/types/unique_list_test.rb +++ b/test/types/unique_list_test.rb @@ -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