Skip to content

Commit 654db93

Browse files
committed
TypeCast WIP
1 parent c4fe37a commit 654db93

File tree

5 files changed

+289
-64
lines changed

5 files changed

+289
-64
lines changed

ext/panko_serializer/attributes_writer/type_cast/type_cast.c

+13
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,14 @@ VALUE public_type_cast(int argc, VALUE* argv, VALUE self) {
364364
return type_cast(type_metadata, value, &isJson);
365365
}
366366

367+
VALUE public_is_iso8601_time_string(VALUE klass, VALUE value) {
368+
return is_iso8601_time_string(StringValuePtr(value)) ? Qtrue : Qfalse;
369+
}
370+
371+
VALUE public_iso_ar_iso_datetime_string(VALUE klass, VALUE value) {
372+
return iso_ar_iso_datetime_string(StringValuePtr(value));
373+
}
374+
367375
void panko_init_type_cast(VALUE mPanko) {
368376
to_s_id = rb_intern("to_s");
369377
to_i_id = rb_intern("to_i");
@@ -375,6 +383,11 @@ void panko_init_type_cast(VALUE mPanko) {
375383
// TODO: pass 3 arguments here
376384
rb_define_singleton_method(mPanko, "_type_cast", public_type_cast, -1);
377385

386+
rb_define_singleton_method(mPanko, "is_iso8601_time_string",
387+
public_is_iso8601_time_string, 1);
388+
rb_define_singleton_method(mPanko, "iso_ar_iso_datetime_string",
389+
public_iso_ar_iso_datetime_string, 1);
390+
378391
panko_init_time(mPanko);
379392

380393
rb_global_variable(&oj_type);

lib/panko/impl/attributes_writer.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ def read_attribute(attribute)
8989
if !attribute.type.nil? && !value.nil?
9090
# TODO: handle is_json
9191
is_json = false
92-
return Panko._type_cast(attribute.type, value, is_json)
92+
93+
return TypeCast.public_type_cast(attribute.type, value, is_json)
9394
end
9495

9596
value

lib/panko/impl/type_cast.rb

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
module Panko::Impl
2+
class TypeCast
3+
@@to_s_id = nil
4+
@@to_i_id = nil
5+
6+
# Caching ActiveRecord Types
7+
@@ar_string_type = nil
8+
@@ar_text_type = nil
9+
@@ar_float_type = nil
10+
@@ar_integer_type = nil
11+
@@ar_boolean_type = nil
12+
@@ar_date_time_type = nil
13+
@@ar_json_type = nil
14+
@@ar_pg_integer_type = nil
15+
@@ar_pg_float_type = nil
16+
@@ar_pg_uuid_type = nil
17+
@@ar_pg_json_type = nil
18+
@@ar_pg_jsonb_type = nil
19+
@@ar_pg_array_type = nil
20+
@@ar_pg_date_time_type = nil
21+
@@ar_pg_timestamp_type = nil
22+
23+
@@initialized = false
24+
25+
def self.cache_postgres_type_lookup
26+
ar_connection_adapters = ActiveRecord::ConnectionAdapters if defined?(ActiveRecord::ConnectionAdapters)
27+
28+
return false unless ar_connection_adapters
29+
30+
case ActiveRecord::Base.connection.adapter_name
31+
when "PostgreSQL"
32+
adapter_module = ar_connection_adapters::PostgreSQL if defined?(ar_connection_adapters::PostgreSQL)
33+
return false unless adapter_module
34+
35+
ar_oid = adapter_module::OID if defined?(adapter_module::OID)
36+
return false unless ar_oid
37+
38+
@@ar_pg_float_type = ar_oid::Float if defined?(ar_oid::Float)
39+
@@ar_pg_integer_type = ar_oid::Integer if defined?(ar_oid::Integer)
40+
@@ar_pg_uuid_type = ar_oid::Uuid if defined?(ar_oid::Uuid)
41+
@@ar_pg_json_type = ar_oid::Json if defined?(ar_oid::Json)
42+
@@ar_pg_jsonb_type = ar_oid::Jsonb if defined?(ar_oid::Jsonb)
43+
@@ar_pg_date_time_type = ar_oid::DateTime if defined?(ar_oid::DateTime)
44+
@@ar_pg_timestamp_type = ar_oid::Timestamp if defined?(ar_oid::Timestamp)
45+
when "SQLite"
46+
adapter_module = ar_connection_adapters::SQLite3Adapter if defined?(ar_connection_adapters::SQLite3Adapter)
47+
return false unless adapter_module
48+
49+
@@ar_pg_integer_type = adapter_module::SQLite3Integer if defined?(adapter_module::SQLite3Integer)
50+
end
51+
52+
true
53+
end
54+
55+
def self.cache_type_lookup
56+
return if @@initialized
57+
58+
@@initialized = true
59+
60+
if defined?(ActiveRecord::Type)
61+
@@ar_string_type = ActiveRecord::Type::String if defined?(ActiveRecord::Type::String)
62+
@@ar_text_type = ActiveRecord::Type::Text if defined?(ActiveRecord::Type::Text)
63+
@@ar_float_type = ActiveRecord::Type::Float if defined?(ActiveRecord::Type::Float)
64+
@@ar_integer_type = ActiveRecord::Type::Integer if defined?(ActiveRecord::Type::Integer)
65+
@@ar_boolean_type = ActiveRecord::Type::Boolean if defined?(ActiveRecord::Type::Boolean)
66+
@@ar_date_time_type = ActiveRecord::Type::DateTime if defined?(ActiveRecord::Type::DateTime)
67+
68+
@@ar_json_type = ActiveRecord::Type::Json if defined?(ActiveRecord::Type::Json)
69+
end
70+
71+
begin
72+
cache_postgres_type_lookup
73+
rescue
74+
# TODO: add to some debug log
75+
end
76+
end
77+
78+
def self.is_string_or_text_type(type_klass)
79+
type_klass == @@ar_string_type || type_klass == @@ar_text_type ||
80+
(@@ar_pg_uuid_type && type_klass == @@ar_pg_uuid_type)
81+
end
82+
83+
def self.cast_string_or_text_type(value)
84+
return value if value.is_a?(String)
85+
return "t" if value == true
86+
return "f" if value == false
87+
88+
value.to_s
89+
end
90+
91+
def self.is_float_type(type_klass)
92+
type_klass == @@ar_float_type || (@@ar_pg_float_type && type_klass == @@ar_pg_float_type)
93+
end
94+
95+
def self.cast_float_type(value)
96+
return value if value.is_a?(Float)
97+
return value.to_f if value.is_a?(String)
98+
99+
nil
100+
end
101+
102+
def self.is_integer_type(type_klass)
103+
type_klass == @@ar_integer_type || (@@ar_pg_integer_type && type_klass == @@ar_pg_integer_type)
104+
end
105+
106+
def self.cast_integer_type(value)
107+
return value if value.is_a?(Integer)
108+
return value.to_i if value.is_a?(String) && !value.empty?
109+
return value.to_i if value.is_a?(Float)
110+
return 1 if value == true
111+
return 0 if value == false
112+
113+
nil
114+
end
115+
116+
def self.is_json_type(type_klass)
117+
@@ar_pg_json_type && type_klass == @@ar_pg_json_type ||
118+
@@ar_pg_jsonb_type && type_klass == @@ar_pg_jsonb_type ||
119+
@@ar_json_type && type_klass == @@ar_json_type
120+
end
121+
122+
def self.is_json_value(value)
123+
return value unless value.is_a?(String)
124+
125+
return false if value.length == 0
126+
127+
begin
128+
result = Oj.sc_parse(Object.new, value)
129+
130+
return true if result.nil?
131+
return false if result == false
132+
rescue Oj::ParseError
133+
return false
134+
end
135+
136+
false
137+
end
138+
139+
def self.is_boolean_type(type_klass)
140+
type_klass == @@ar_boolean_type
141+
end
142+
143+
def self.cast_boolean_type(value)
144+
return value if value == true || value == false
145+
return nil if value.nil?
146+
147+
if value.is_a?(String)
148+
return nil if value.length == 0
149+
150+
is_false_value =
151+
value == "0" || (value == "f" || value == "F") ||
152+
(value.downcase == "false" || value.downcase == "off")
153+
154+
return is_false_value ? true : false
155+
end
156+
157+
if value.is_a?(Integer)
158+
return value == 1
159+
end
160+
161+
nil
162+
end
163+
164+
def self.is_date_time_type(type_klass)
165+
type_klass == @@ar_date_time_type ||
166+
type_klass == @@ar_pg_date_time_type ||
167+
type_klass == @@ar_pg_timestamp_type
168+
end
169+
170+
def self.cast_date_time_type(value)
171+
if value.is_a?(String)
172+
return value if value.end_with?("Z") && Panko.is_iso8601_time_string(value) # TODO: Implement `is_iso8601_time_string`
173+
iso8601_string = Panko.iso_ar_iso_datetime_string(value) # TODO: Implement `iso_ar_iso_datetime_string`
174+
return iso8601_string unless iso8601_string.nil?
175+
end
176+
177+
nil
178+
end
179+
180+
def self.public_type_cast(type_metadata, value, is_json = false)
181+
return value if value.nil?
182+
183+
cache_type_lookup
184+
185+
type_klass = type_metadata.class
186+
type_casted_value = nil
187+
188+
if is_string_or_text_type(type_klass)
189+
type_casted_value = cast_string_or_text_type(value)
190+
elsif is_integer_type(type_klass)
191+
type_casted_value = cast_integer_type(value)
192+
elsif is_boolean_type(type_klass)
193+
type_casted_value = cast_boolean_type(value)
194+
elsif is_date_time_type(type_klass)
195+
type_casted_value = cast_date_time_type(value)
196+
elsif is_float_type(type_klass)
197+
type_casted_value = cast_float_type(value)
198+
end
199+
200+
if is_json_type(type_klass)
201+
return nil if is_json_value(value) == false
202+
return value
203+
end
204+
205+
return type_metadata.deserialize(value) if type_casted_value.nil?
206+
207+
type_casted_value
208+
end
209+
end
210+
end

lib/panko_serializer.rb

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require "panko/version"
4+
require "panko/impl/type_cast"
45
require "panko/impl/attributes_writer"
56
require "panko/impl/serializer"
67
require "panko/attribute"

0 commit comments

Comments
 (0)