Module: Paquito::Types
- Defined in:
- lib/paquito/types.rb,
lib/paquito/types/active_record_packer.rb
Defined Under Namespace
Classes: ActiveRecordPacker, CustomTypesRegistry
Constant Summary collapse
- TIME_FORMAT =
Do not change those formats, this would break current codecs.
"q< L<"
- TIME_WITH_ZONE_FORMAT =
"q< L< a*"
- DATE_TIME_FORMAT =
"s< C C C C q< L< c C"
- DATE_FORMAT =
"s< C C"
- MAX_UINT32 =
(2**32) - 1
- MAX_INT64 =
(2**63) - 1
- SERIALIZE_METHOD =
:as_pack
- SERIALIZE_PROC =
SERIALIZE_METHOD.to_proc
- DESERIALIZE_METHOD =
:from_pack
- TYPES =
[ { code: 0, class: "Symbol", version: 0, packer: Symbol.method_defined?(:name) ? :name.to_proc : :to_s.to_proc, unpacker: :to_sym.to_proc, optimized_symbols_parsing: true, }.freeze, { code: 1, class: "Time", version: 0, packer: method(:time_pack_deprecated), unpacker: method(:time_unpack_deprecated), }.freeze, { code: 2, class: "DateTime", version: 0, packer: method(:datetime_pack_deprecated), unpacker: method(:datetime_unpack_deprecated), }.freeze, { code: 3, class: "Date", version: 0, packer: method(:date_pack), unpacker: method(:date_unpack), }.freeze, { code: 4, class: "BigDecimal", version: 0, packer: :_dump, unpacker: ::BigDecimal.method(:_load), }.freeze, # { code: 5, class: "Range" }, do not recycle that code { code: 6, class: "ActiveRecord::Base", version: 0, packer: ->(value) { ActiveRecordPacker.dump(value) }, unpacker: ->(value) { ActiveRecordPacker.load(value) }, }.freeze, { code: 7, class: "ActiveSupport::HashWithIndifferentAccess", version: 0, packer: method(:hash_with_indifferent_access_pack), unpacker: method(:hash_with_indifferent_access_unpack), recursive: true, }.freeze, { code: 8, class: "ActiveSupport::TimeWithZone", version: 0, packer: method(:time_with_zone_deprecated_pack), unpacker: method(:time_with_zone_deprecated_unpack), }.freeze, { code: 9, class: "Set", version: 0, packer: ->(value, packer) { packer.write(value.to_a) }, unpacker: ->(unpacker) { unpacker.read.to_set }, recursive: true, }.freeze, # { code: 10, class: "Integer" }, reserved for oversized Integer { code: 11, class: "Time", version: 1, recursive: true, packer: method(:time_pack), unpacker: method(:time_unpack), }.freeze, { code: 12, class: "DateTime", version: 1, recursive: true, packer: method(:datetime_pack), unpacker: method(:datetime_unpack), }.freeze, { code: 13, class: "ActiveSupport::TimeWithZone", version: 1, recursive: true, packer: method(:time_with_zone_pack), unpacker: method(:time_with_zone_unpack), }.freeze, # { code: 127, class: "Object" }, reserved for serializable Object type ]
Class Method Summary collapse
- .date_pack(value) ⇒ Object
- .date_unpack(payload) ⇒ Object
- .datetime_pack(value, packer) ⇒ Object
- .datetime_pack_deprecated(value) ⇒ Object
- .datetime_unpack(unpacker) ⇒ Object
- .datetime_unpack_deprecated(payload) ⇒ Object
- .define_custom_type(klass, packer: nil, unpacker:) ⇒ Object
- .hash_with_indifferent_access_pack(value, packer) ⇒ Object
- .hash_with_indifferent_access_unpack(unpacker) ⇒ Object
- .register(factory, types, format_version: Paquito.format_version) ⇒ Object
- .register_serializable_type(factory) ⇒ Object
- .time_pack(value, packer) ⇒ Object
- .time_pack_deprecated(value) ⇒ Object
- .time_unpack_deprecated(payload) ⇒ Object
- .time_with_zone_deprecated_pack(value) ⇒ Object
- .time_with_zone_deprecated_unpack(payload) ⇒ Object
- .time_with_zone_pack(value, packer) ⇒ Object
Instance Method Summary collapse
Class Method Details
.date_pack(value) ⇒ Object
160 161 162 |
# File 'lib/paquito/types.rb', line 160 def date_pack(value) [value.year, value.month, value.day].pack(DATE_FORMAT) end |
.date_unpack(payload) ⇒ Object
164 165 166 167 |
# File 'lib/paquito/types.rb', line 164 def date_unpack(payload) year, month, day = payload.unpack(DATE_FORMAT) ::Date.new(year, month, day) end |
.datetime_pack(value, packer) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/paquito/types.rb', line 212 def datetime_pack(value, packer) packer.write(value.year) packer.write(value.month) packer.write(value.day) packer.write(value.hour) packer.write(value.minute) sec = value.sec + value.sec_fraction packer.write(sec.numerator) packer.write(sec.denominator) offset = value.offset packer.write(offset.numerator) packer.write(offset.denominator) end |
.datetime_pack_deprecated(value) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/paquito/types.rb', line 107 def datetime_pack_deprecated(value) sec = value.sec + value.sec_fraction offset = value.offset if sec.numerator > MAX_INT64 || sec.denominator > MAX_UINT32 raise PackError, "DateTime#sec_fraction out of bounds (#{sec.inspect}), see: https://github.com/Shopify/paquito/issues/26" end if offset.numerator > MAX_INT64 || offset.denominator > MAX_UINT32 raise PackError, "DateTime#offset out of bounds (#{offset.inspect}), see: https://github.com/Shopify/paquito/issues/26" end [ value.year, value.month, value.day, value.hour, value.minute, sec.numerator, sec.denominator, offset.numerator, offset.denominator, ].pack(DATE_TIME_FORMAT) end |
.datetime_unpack(unpacker) ⇒ Object
228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/paquito/types.rb', line 228 def datetime_unpack(unpacker) ::DateTime.new( unpacker.read, # year unpacker.read, # month unpacker.read, # day unpacker.read, # hour unpacker.read, # minute Rational(unpacker.read, unpacker.read), # sec fraction Rational(unpacker.read, unpacker.read), # offset fraction ) end |
.datetime_unpack_deprecated(payload) ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/paquito/types.rb', line 132 def datetime_unpack_deprecated(payload) ( year, month, day, hour, minute, sec_numerator, sec_denominator, offset_numerator, offset_denominator, ) = payload.unpack(DATE_TIME_FORMAT) begin ::DateTime.new( year, month, day, hour, minute, Rational(sec_numerator, sec_denominator), Rational(offset_numerator, offset_denominator), ) rescue ZeroDivisionError raise UnpackError, "Corrupted DateTime object, see: https://github.com/Shopify/paquito/issues/26" end end |
.define_custom_type(klass, packer: nil, unpacker:) ⇒ Object
428 429 430 |
# File 'lib/paquito/types.rb', line 428 def define_custom_type(klass, packer: nil, unpacker:) CustomTypesRegistry.register(klass, packer: packer, unpacker: unpacker) end |
.hash_with_indifferent_access_pack(value, packer) ⇒ Object
169 170 171 172 173 174 175 |
# File 'lib/paquito/types.rb', line 169 def hash_with_indifferent_access_pack(value, packer) unless value.instance_of?(ActiveSupport::HashWithIndifferentAccess) raise PackError.new("cannot pack HashWithIndifferentClass subclass", value) end packer.write(value.to_h) end |
.hash_with_indifferent_access_unpack(unpacker) ⇒ Object
177 178 179 |
# File 'lib/paquito/types.rb', line 177 def hash_with_indifferent_access_unpack(unpacker) ActiveSupport::HashWithIndifferentAccess.new(unpacker.read) end |
.register(factory, types, format_version: Paquito.format_version) ⇒ Object
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
# File 'lib/paquito/types.rb', line 375 def register(factory, types, format_version: Paquito.format_version) types.each do |type| # Up to Rails 7 ActiveSupport::TimeWithZone#name returns "Time" name = if defined?(ActiveSupport::TimeWithZone) && type == ActiveSupport::TimeWithZone "ActiveSupport::TimeWithZone" else type.name end matching_types = TYPES.select { |t| t[:class] == name } # If multiple types are registered for the same class, the last one will be used for # packing. So we sort all matching types so that the active one is registered last. past_types, future_types = matching_types.partition { |t| t.fetch(:version) <= format_version } if past_types.empty? raise KeyError, "No type found for #{name.inspect} with format_version=#{format_version}" end past_types.sort_by! { |t| t.fetch(:version) } (future_types + past_types).each do |type_attributes| factory.register_type( type_attributes.fetch(:code), type, type_attributes, ) end end end |
.register_serializable_type(factory) ⇒ Object
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 |
# File 'lib/paquito/types.rb', line 404 def register_serializable_type(factory) factory.register_type( 127, Object, packer: ->(value) do packer = CustomTypesRegistry.packer(value) class_name = value.class.to_s factory.dump([packer.call(value), class_name]) end, unpacker: ->(value) do payload, class_name = factory.load(value) begin klass = Object.const_get(class_name) rescue NameError raise ClassMissingError, "missing #{class_name} class" end unpacker = CustomTypesRegistry.unpacker(klass) unpacker.call(payload) end, ) end |
.time_pack(value, packer) ⇒ Object
196 197 198 199 200 |
# File 'lib/paquito/types.rb', line 196 def time_pack(value, packer) packer.write(value.tv_sec) packer.write(value.tv_nsec) packer.write(value.utc_offset) end |
.time_pack_deprecated(value) ⇒ Object
88 89 90 91 92 93 94 95 |
# File 'lib/paquito/types.rb', line 88 def time_pack_deprecated(value) rational = value.to_r if rational.numerator > MAX_INT64 || rational.denominator > MAX_UINT32 raise PackError, "Time instance out of bounds (#{rational.inspect}), see: https://github.com/Shopify/paquito/issues/26" end [rational.numerator, rational.denominator].pack(TIME_FORMAT) end |
.time_unpack_deprecated(payload) ⇒ Object
97 98 99 100 101 102 103 104 105 |
# File 'lib/paquito/types.rb', line 97 def time_unpack_deprecated(payload) numerator, denominator = payload.unpack(TIME_FORMAT) at = begin Rational(numerator, denominator) rescue ZeroDivisionError raise UnpackError, "Corrupted Time object, see: https://github.com/Shopify/paquito/issues/26" end Time.at(at).utc end |
.time_with_zone_deprecated_pack(value) ⇒ Object
181 182 183 184 185 186 187 |
# File 'lib/paquito/types.rb', line 181 def time_with_zone_deprecated_pack(value) [ value.utc.to_i, (value.time.sec_fraction * 1_000_000_000).to_i, value.time_zone.name, ].pack(TIME_WITH_ZONE_FORMAT) end |
.time_with_zone_deprecated_unpack(payload) ⇒ Object
189 190 191 192 193 194 |
# File 'lib/paquito/types.rb', line 189 def time_with_zone_deprecated_unpack(payload) sec, nsec, time_zone_name = payload.unpack(TIME_WITH_ZONE_FORMAT) time = Time.at(sec, nsec, :nsec, in: 0).utc time_zone = ::Time.find_zone(time_zone_name) ActiveSupport::TimeWithZone.new(time, time_zone) end |
.time_with_zone_pack(value, packer) ⇒ Object
240 241 242 243 244 245 |
# File 'lib/paquito/types.rb', line 240 def time_with_zone_pack(value, packer) time = value.utc packer.write(time.tv_sec) packer.write(time.tv_nsec) packer.write(value.time_zone.name) end |
Instance Method Details
#time_unpack(unpacker) ⇒ Object
203 204 205 |
# File 'lib/paquito/types.rb', line 203 def time_unpack(unpacker) ::Time.at_without_coercion(unpacker.read, unpacker.read, :nanosecond, in: unpacker.read) end |
#time_with_zone_unpack(unpacker) ⇒ Object
248 249 250 251 252 |
# File 'lib/paquito/types.rb', line 248 def time_with_zone_unpack(unpacker) utc = ::Time.at_without_coercion(unpacker.read, unpacker.read, :nanosecond, in: "UTC") time_zone = ::Time.find_zone(unpacker.read) ActiveSupport::TimeWithZone.new(utc, time_zone) end |