Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 015/serializing postgres arrays #1018

Merged
merged 11 commits into from
Dec 8, 2017
Merged

Issue 015/serializing postgres arrays #1018

merged 11 commits into from
Dec 8, 2017

Conversation

hubertpompecki
Copy link
Contributor

@hubertpompecki hubertpompecki commented Dec 4, 2017

This PR addresses #1015

Starting from Rails version 5.0.2 the default serializer of PostgreSQL columns returns an ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array object instead of a string. This new object is not suitable for JSON encoding and breaks versioning of any array fields backed by Postgres.

Whenever a PostgreSQL array is used, instead of asking Active Record for a serializer we introduce our own, which simply returns the underlying array without any modifications.

module PaperTrail
module AttributeSerializers
# :nodoc:
class ArraySerializer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the OO design you've gone with. It gives us some parts that we can understand independently.

Regarding the name, the intention of the AttributeSerializers module is to "serialize" two attributes:

  1. object
  2. object_changes

There is no attribute named array, so I think this is either:

  1. not the correct module to put this class in, or
  2. the module should be given a more generic name

My first instinct is the former, that this is not the correct module to put this class in. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point and I'd also lean towards no. 1.

Given that the original implementation obtains an AR serializer by calling #type_for_attribute(attr) then perhaps PaperTrail::TypeSerializers::PostgresArraySerializer would be a better name?

@@ -0,0 +1,28 @@
module PaperTrail
module AttributeSerializers
# :nodoc:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide documentation for all new classes.

create_table :postgres_users, force: true do |t|
t.string :name
t.integer :post_ids, array: true
t.datetime :login_times, array: true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how you are testing two different types of arrays here. Also, it looks like this is the first test table to have arrays at all! Kind of surprising.

@@ -0,0 +1,37 @@
require "spec_helper"

RSpec.describe PaperTrail::AttributeSerializers::ObjectAttribute do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the nested-module style.

module PaperTrail
  module AttributeSerializers
    RSpec.describe ObjectAttribute

@@ -2,6 +2,7 @@
ENV["DB"] ||= "sqlite"

require "byebug"
require "support/database_specific_specs"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please create a separate PR for this. It's a good idea, but I'd like to handle it separately, and more comprehensively. For this PR, please use plain ruby conditionals the way other tests currently do.

# with custom PaperTrail ones.
module AttributeSerializer
def self.for(klass, attr)
case active_record_serializer = klass.type_for_attribute(attr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not use the assignment operator in a case.

active_record_serializer.subtype,
active_record_serializer.delimiter
)
else active_record_serializer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this onto its own line:

else
  active_record_serializer
end

# not suited for JSON encoding. This factory
# replaces certain default Active Record serializers
# with custom PaperTrail ones.
module AttributeSerializer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you were going for with this name. AttributeSerializer.for does read nicely, but I'd think I'd prefer AttributeSerializerFactory.for. It doesn't read as well, but you can tell the purpose of the module from its name alone.

# Values returned by some Active Record serializers are
# not suited for JSON encoding. This factory
# replaces certain default Active Record serializers
# with custom PaperTrail ones.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good, helpful comment. I'd change "JSON encoding" to "writing JSON to a text column.".

This phrasing disabiguates JSON-in-text-column from native postgres json(b).

@@ -32,15 +34,15 @@ def defined_enums
# This implementation uses AR 5's `serialize` and `deserialize`.
class CastAttributeSerializer
def serialize(attr, val)
@klass.type_for_attribute(attr).serialize(val)
PaperTrail::AttributeSerializers::AttributeSerializer.for(@klass, attr).serialize(val)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary to fully-qualify the constant here. AttributeSerializer alone is sufficient.

Copy link
Contributor Author

@hubertpompecki hubertpompecki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comprehensive code review @jaredbeck! A lot of good points there, I have made changes as requested.

@jaredbeck
Copy link
Member

Thanks for your work on this Hubert!

@hubertpompecki hubertpompecki deleted the issue_015/serializing_postgres_arrays branch December 14, 2017 15:08
aried3r pushed a commit to aried3r/paper_trail that referenced this pull request Dec 14, 2020
This PR addresses paper-trail-gem#1015

Starting from Rails version 5.0.2 the default serializer of PostgreSQL columns returns an ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array object instead of a string. This new object is not suitable for JSON encoding and breaks versioning of any array fields backed by Postgres.

Whenever a PostgreSQL array is used, instead of asking Active Record for a serializer we introduce our own, which simply returns the underlying array without any modifications.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants