0

TL;DR: Rails 5.1, Ruby 2.4.0 is serialising a hash including Time objects with quotes around the string representation of the time. These quotes weren't there in Rails 2.3, Ruby 1.8.7 and break my app; how do I get rid of them?

Context and details

I'm upgrading an app from Rails 2.3, Ruby 1.8.7 to Rails 5.1, Ruby 2.4.0. I have a ReportService class, which has a report_params constructor argument which takes a hash. Upon creation of these objects, this hash gets serialised in YAML format.

class ReportService < ApplicationRecord
  # irrelevant AR associations omitted
   serialize :report_params
   serialize :output_urls
end

A user submits a form containing details of a report they want to be run, including a string that gets parsed using Time.parse(), which gets passed as a constructor argument; so the code (in procedural form to strip out irrelevant details, with lots of extraneous stuff ommitted) looks like

offset = customer.timezone.nil? ? '+0000' : customer.timezone.formatted_offset(:time => start_date)
params[:date_from] = Time.parse("#{start_date} #{params[:hour_from]}:{params[:min_from]} #{offset}").utc.strftime('%Y-%m-%d %H:%M:%S')
report_args = {...
                report_params: { ...
                                 date: params[:date_from]
                               }
               }
ReportService.create(report_args)

When I look in my MYSQL database, I find that my report_params field looks like ... date_from: '2017-12-27 00:00:00' .... The corresponding code in the old version produces a result that looks like ... date_from: 2017-12-27 00:00:00 .... This is a bad thing, because the YAML in that field is getting parsed by a (legacy) Java app that polls the database to check for new entries, and the quotes seem to break that deserialisation (throwing java.lang.Exception: BaseProperties.getdate()); if I manually edit the field to remove the quotes, the app works as expected. How can I prevent these quotation marks from being added?

rwold
  • 2,216
  • 1
  • 14
  • 22

1 Answers1

2

Rails5.1/Ruby2.4 do it correct, since 2017-12-27 00:00:00 is not a valid yaml value.

The good thing is serialize accepts two parameters, the second one being a serializer class.

So, all you need to do is:

class ReportService < ApplicationRecord
  # irrelevant AR associations omitted
   serialize :report_params, MyYaml
   serialize :output_urls, MyYaml
end

and implement MyYaml, delegating everything, save for date/time to YAML and producing whatever you need for them.

The above is valid for any format of serialized data, it’s completely format-agnostic. Examples.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • Thanks for the reply, but I’m serialising as YAML, not JSON. Because the legacy java app is parsing the yaml I need to preserve the output format. – rwold Jan 02 '18 at 17:28
  • You might serialize as whatever you need, just implement the serializer itself. – Aleksei Matiushkin Jan 02 '18 at 17:29
  • I see now, thanks. According to http://www.yamllint.com/ both old and new outputs are valid YAML however. Also, weirdly, if I apply the .to_yaml method on a hash, then there are no quotes in the representation of a Time object. e.g. `2.4.0 :004 > ds = Time.now().to_s => "2018-01-02 15:39:26 +0000" 2.4.0 :005 > time = Time.parse(ds) => 2018-01-02 15:39:26 +0000 #similarly, date = DateTime.parse(ds) 2.4.0 :009 > {date: date, time: time}.to_yaml => "---\n:date: !ruby/object:DateTime 2018-01-02 15:39:26.000000000 Z\n:time: 2018-01-02 15:39:26.000000000 +00:00\n` – rwold Jan 03 '18 at 09:14
  • Apologies, SO won't let me edit that comment any longer to make it more readable! – rwold Jan 03 '18 at 09:21
  • I've figured that the new Rails behaviour preserves class on serialisation and deserialisation, so it's because of the `strftime()` time method that it's adding the quotes. Your method should allow me to hack around this, so answer accepted. – rwold Jan 03 '18 at 09:57
  • Maybe in such a case it would be easier to just _remove trailing `strftime` transformation_ and return an instance of `Time`? – Aleksei Matiushkin Jan 03 '18 at 10:02
  • So the date format has been chosen for compatibility with whatever Java is doing to parse the date from the YAML. (I've been burned before with some of the standard representations breaking JS date parsing libraries.) There are many horrors in this #legacy system, and I'm trying to preserve interfaces as fully as I can whilst refactoring one bit at a time, rather than make changes that spill across projects ... – rwold Jan 03 '18 at 13:29
  • Nope, dark rails magic was at work: https://stackoverflow.com/questions/37228752/activerecord-serialize-sending-nil-to-custom-serializer-for-present-attribute – rwold Jan 03 '18 at 13:47