-
Notifications
You must be signed in to change notification settings - Fork 909
Add support for Postgres Range Types #1534
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||
| # frozen_string_literal: true | ||||||
|
|
||||||
| module PaperTrail | ||||||
| module TypeSerializers | ||||||
| # Provides an alternative method of serialization | ||||||
| # and deserialization of PostgreSQL range columns. | ||||||
| class PostgresRangeSerializer | ||||||
| # @see https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L147-L152 | ||||||
| RANGE_TYPES = %i[ | ||||||
| daterange | ||||||
| numrange | ||||||
| tsrange | ||||||
| tstzrange | ||||||
| int4range | ||||||
| int8range | ||||||
| ].freeze | ||||||
|
|
||||||
| def self.range_type?(type) | ||||||
| RANGE_TYPES.include?(type) | ||||||
| end | ||||||
|
|
||||||
| def initialize(active_record_serializer) | ||||||
| @active_record_serializer = active_record_serializer | ||||||
| end | ||||||
|
|
||||||
| def serialize(range) | ||||||
| range | ||||||
| end | ||||||
|
|
||||||
| def deserialize(range) | ||||||
| range.is_a?(String) ? deserialize_with_ar(range) : range | ||||||
| end | ||||||
|
|
||||||
| private | ||||||
|
|
||||||
| def deserialize_with_ar(string) | ||||||
| return nil if string.blank? | ||||||
|
|
||||||
| delimiter = string[/\.{2,3}/] | ||||||
|
||||||
| delimiter = string[/\.{2,3}/] | |
| delimiter = string[/(\.\.\.?)/] |
sirwolfgang marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -8,15 +8,21 @@ module AttributeSerializers | |||||
| if ENV["DB"] == "postgres" | ||||||
| describe "postgres-specific column types" do | ||||||
| describe "#serialize" do | ||||||
| it "serializes a postgres array into a plain array" do | ||||||
| it "serializes a postgres array into a ruby array" do | ||||||
| attrs = { "post_ids" => [1, 2, 3] } | ||||||
| described_class.new(PostgresUser).serialize(attrs) | ||||||
| expect(attrs["post_ids"]).to eq [1, 2, 3] | ||||||
| end | ||||||
|
|
||||||
| it "serializes a postgres range into a ruby array" do | ||||||
|
||||||
| it "serializes a postgres range into a ruby array" do | |
| it "serializes a postgres range into a ruby range" do |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require "spec_helper" | ||
|
|
||
| module PaperTrail | ||
| module TypeSerializers | ||
| ::RSpec.describe PostgresRangeSerializer do | ||
| if ENV["DB"] == "postgres" | ||
| let(:active_record_serializer) { | ||
| ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Range.new(subtype) | ||
| } | ||
| let(:serializer) { described_class.new(active_record_serializer) } | ||
|
|
||
| describe ".deserialize" do | ||
| let(:range_string) { range_ruby.to_s } | ||
|
|
||
| context "with daterange" do | ||
| let(:subtype) { ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Date.new } | ||
| let(:range_ruby) { Date.new(2024, 1, 1)..Date.new(2024, 1, 31) } | ||
|
|
||
| it "deserializes to Ruby" do | ||
| expect(serializer.deserialize(range_string)).to eq(range_ruby) | ||
| end | ||
|
|
||
| context "with exclude_end" do | ||
| let(:range_ruby) { Date.new(2024, 1, 1)...Date.new(2024, 1, 31) } | ||
|
|
||
| it "deserializes to Ruby" do | ||
| expect(serializer.deserialize(range_string)).to eq(range_ruby) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| context "with numrange" do | ||
| let(:subtype) { ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Decimal.new } | ||
| let(:range_ruby) { 1.5..3.5 } | ||
|
|
||
| it "deserializes to Ruby" do | ||
| expect(serializer.deserialize(range_string)).to eq(range_ruby) | ||
| end | ||
| end | ||
|
|
||
| context "with tsrange" do | ||
| let(:subtype) { ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Timestamp.new } | ||
| let(:range_ruby) { 1.day.ago..1.day.from_now } | ||
|
|
||
| it "deserializes to Ruby" do | ||
| expect(serializer.deserialize(range_string)).to eq(range_ruby) | ||
| end | ||
| end | ||
|
|
||
| context "with tstzrange" do | ||
| let(:subtype) { | ||
| ActiveRecord::ConnectionAdapters::PostgreSQL::OID::TimestampWithTimeZone.new | ||
| } | ||
| let(:range_ruby) { Date.new(2021, 1, 1)..Date.new(2021, 1, 31) } | ||
|
|
||
| it "deserializes to Ruby" do | ||
| expect(serializer.deserialize(range_string)).to eq(range_ruby) | ||
| end | ||
| end | ||
|
|
||
| context "with int4range" do | ||
| let(:subtype) { ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer.new } | ||
| let(:range_ruby) { 1..10 } | ||
|
|
||
| it "deserializes to Ruby" do | ||
| expect(serializer.deserialize(range_string)).to eq(range_ruby) | ||
| end | ||
| end | ||
|
|
||
| context "with int8range" do | ||
| let(:subtype) { ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer.new } | ||
| let(:range_ruby) { 2_200_000_000..2_500_000_000 } | ||
|
|
||
| it "deserializes to Ruby" do | ||
| expect(serializer.deserialize(range_string)).to eq(range_ruby) | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CHANGELOG entry states "Fixed errors when deserializing Range types from Ruby style strings to Postgres" but this is confusing. It should clarify whether it's deserializing FROM Ruby strings or FROM Postgres format strings. Based on the code, it appears to deserialize Ruby-style range strings (e.g., "1..5") to Ruby Range objects. Consider rewording to: "Fixed support for PostgreSQL range column types in object/object_changes serialization"