Skip to content

Commit f85af53

Browse files
lsylvesterDavid Heinemeier Hansson
andauthored
Typecasting data when inserting into Redis (#22)
* use ActiveModel::Types to cast the values in Redis to their type * cast values to type before storing in redis to ensure that they are valid and have been formatted correctly. * Remove extra CR * Remove extra CR * add floats to the types for the attributes api * cast floats as floats and not decimals * require inclusiion before using presence_in * only cast the valuees of the hash type * add a test for the float type. Update decimal test to reflect that it is being cast to a decimal instead of a float. Co-authored-by: David Heinemeier Hansson <david@loudthinking.com>
1 parent 870fce0 commit f85af53

File tree

12 files changed

+92
-48
lines changed

12 files changed

+92
-48
lines changed

lib/kredis/attributes.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ def kredis_flag(name, key: nil, config: :shared, after_change: nil)
3030
end
3131
end
3232

33+
def kredis_float(name, key: nil, config: :shared, after_change: nil)
34+
kredis_connection_with __method__, name, key, config: config, after_change: after_change
35+
end
36+
3337
def kredis_enum(name, key: nil, values:, default:, config: :shared, after_change: nil)
3438
kredis_connection_with __method__, name, key, values: values, default: default, config: config, after_change: after_change
3539
end

lib/kredis/type/datetime.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
module Kredis
4+
module Type
5+
class DateTime < ActiveModel::Type::DateTime
6+
def serialize(value)
7+
super&.iso8601(9)
8+
end
9+
10+
def cast_value(value)
11+
super&.to_datetime
12+
end
13+
end
14+
end
15+
end

lib/kredis/type/json.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# frozen_string_literal: true
2+
3+
module Kredis
4+
module Type
5+
class Json < ActiveModel::Type::Value
6+
def type
7+
:json
8+
end
9+
10+
def cast_value(value)
11+
JSON.load(value)
12+
end
13+
14+
def serialize(value)
15+
JSON.dump(value)
16+
end
17+
end
18+
end
19+
end

lib/kredis/type_casting.rb

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,35 @@
11
require "json"
2+
require "active_model/type"
3+
require "kredis/type/json"
4+
require "kredis/type/datetime"
25

36
module Kredis::TypeCasting
47
class InvalidType < StandardError; end
58

6-
VALID_TYPES = %i[ string integer decimal float boolean datetime json ]
7-
8-
def type_to_string(value)
9-
case value
10-
when nil
11-
""
12-
when Integer
13-
value.to_s
14-
when BigDecimal
15-
value.to_d
16-
when Float
17-
value.to_s
18-
when TrueClass, FalseClass
19-
value ? "t" : "f"
20-
when Time, DateTime, ActiveSupport::TimeWithZone
21-
value.iso8601(9)
22-
when Hash
23-
JSON.dump(value)
24-
else
25-
value
26-
end
9+
TYPES = {
10+
string: ActiveModel::Type::String.new,
11+
integer: ActiveModel::Type::Integer.new,
12+
decimal: ActiveModel::Type::Decimal.new,
13+
float: ActiveModel::Type::Float.new,
14+
boolean: ActiveModel::Type::Boolean.new,
15+
datetime: Kredis::Type::DateTime.new,
16+
json: Kredis::Type::Json.new
17+
}
18+
19+
def type_to_string(value, type)
20+
raise InvalidType if type && !TYPES.key?(type)
21+
22+
TYPES[type || :string].serialize(value)
2723
end
2824

2925
def string_to_type(value, type)
30-
raise InvalidType if type && !VALID_TYPES.include?(type)
31-
32-
case type
33-
when nil, :string then value
34-
when :integer then value.to_i
35-
when :decimal then value.to_d
36-
when :float then value.to_f
37-
when :boolean then value == "t" ? true : false
38-
when :datetime then Time.iso8601(value)
39-
when :json then JSON.load(value)
40-
end if value.present?
26+
raise InvalidType if type && !TYPES.key?(type)
27+
28+
TYPES[type || :string].cast(value)
4129
end
4230

43-
def types_to_strings(values)
44-
Array(values).flatten.map { |value| type_to_string(value) }
31+
def types_to_strings(values, type)
32+
Array(values).flatten.map { |value| type_to_string(value, type) }
4533
end
4634

4735
def strings_to_types(values, type)

lib/kredis/types/enum.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require "active_support/core_ext/object/inclusion"
2+
13
class Kredis::Types::Enum < Kredis::Types::Proxying
24
proxying :set, :get, :del
35

lib/kredis/types/hash.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,20 @@ def []=(key, value)
1313
update key => value
1414
end
1515

16-
1716
def update(**entries)
18-
hset types_to_strings(entries) if entries.flatten.any?
17+
hset entries.transform_values{ |val| type_to_string(val, typed) } if entries.flatten.any?
1918
end
2019

2120
def values_at(*keys)
2221
strings_to_types(hmget(keys) || [], typed)
2322
end
2423

2524
def delete(*keys)
26-
hdel types_to_strings(keys) if keys.flatten.any?
25+
hdel keys if keys.flatten.any?
2726
end
2827

2928
def remove
30-
del
29+
del
3130
end
3231

3332
def entries

lib/kredis/types/list.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ def elements
99
alias to_a elements
1010

1111
def remove(*elements)
12-
types_to_strings(elements).each { |element| lrem 0, element }
12+
types_to_strings(elements, typed).each { |element| lrem 0, element }
1313
end
1414

1515
def prepend(*elements)
16-
lpush types_to_strings(elements) if elements.flatten.any?
16+
lpush types_to_strings(elements, typed) if elements.flatten.any?
1717
end
1818

1919
def append(*elements)
20-
rpush types_to_strings(elements) if elements.flatten.any?
20+
rpush types_to_strings(elements, typed) if elements.flatten.any?
2121
end
2222
alias << append
2323
end

lib/kredis/types/scalar.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ class Kredis::Types::Scalar < Kredis::Types::Proxying
44
attr_accessor :typed, :default, :expires_in
55

66
def value=(value)
7-
set type_to_string(value), ex: expires_in
7+
set type_to_string(value, typed), ex: expires_in
88
end
99

1010
def value
1111
value_after_casting = string_to_type(get, typed)
12-
12+
1313
if value_after_casting.nil?
1414
default
1515
else

lib/kredis/types/set.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ def members
99
alias to_a members
1010

1111
def add(*members)
12-
sadd types_to_strings(members) if members.flatten.any?
12+
sadd types_to_strings(members, typed) if members.flatten.any?
1313
end
1414
alias << add
1515

1616
def remove(*members)
17-
srem types_to_strings(members) if members.flatten.any?
17+
srem types_to_strings(members, typed) if members.flatten.any?
1818
end
1919

2020
def replace(*members)
@@ -25,7 +25,7 @@ def replace(*members)
2525
end
2626

2727
def include?(member)
28-
sismember type_to_string(member)
28+
sismember type_to_string(member, typed)
2929
end
3030

3131
def size

test/attributes_test.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Person
1414
kredis_integer :age
1515
kredis_decimal :salary
1616
kredis_datetime :last_seen_at
17+
kredis_float :height
1718
kredis_enum :morning, values: %w[ bright blue black ], default: "bright"
1819
kredis_slot :attention
1920
kredis_slots :meetings, available: 3
@@ -103,7 +104,13 @@ class AttributesTest < ActiveSupport::TestCase
103104
test "decimal" do
104105
@person.salary.value = 10000.07
105106
assert_equal 10000.07, @person.salary.value
106-
assert_equal "10000.07", @person.salary.to_s
107+
assert_equal "0.1000007e5", @person.salary.to_s
108+
end
109+
110+
test "float" do
111+
@person.height.value = 1.85
112+
assert_equal 1.85, @person.height.value
113+
assert_equal "1.85", @person.height.to_s
107114
end
108115

109116
test "datetime" do

0 commit comments

Comments
 (0)