ActiveRecord 6.1 numeric Change

How ActiveRecord 6.1 changes the way Postgres numeric values are handled

Posted by Harry Lascelles on October 15, 2023

ActiveRecord 6.1 changed the way Postgres numeric values are handled. This was a breaking change for any application that was migrating from ActiveRecord 6.0 to ActiveRecord 6.1 (and by extension, Rails 6.0 to Rails 6.1), has a database that uses a non-integer numeric type, and uses raw ActiveRecord::Base.connection.execute queries.

What has changed?

The change is visible when using raw SQL queries rather than using ActiveRecord models. You may wish to do this if you have tables in the DB that are not managed by Rails.

Take an example where you have a products table with a price column that is a numeric type.

In ActiveRecord 6.0, the price column would be returned as a String value. In ActiveRecord 6.1 it is is returned as a BigDecimal value.

result = ActiveRecord::Base.connection.execute(<<~SQL)
  SELECT price FROM products;
SQL

# ActiveRecord 6.0
puts result.to_a
# => [{"price"=>"420.03"}]

# ActiveRecord 6.1
puts result.to_a
# => [{"price"=>0.42003e3}]

The change was introduced in this commit in the Rails repository. The commit added the PG::TextDecoder::Numeric class to the list of decoders that are used by default.

Full example

Here is a full example you can run to see this for yourself

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "activerecord", "6.0.6.1" # Change to 6.1.7.2 to see different results
  gem "pg"
end

require "active_record"

# Set up the database connection
ActiveRecord::Base.establish_connection(
  adapter: "postgresql",
  host: "localhost",
  username: "postgres",
  password: "postgres",
  database: "postgres"
)

# Create a new table with a numeric column
ActiveRecord::Schema.define do
  create_table :numeric_models do |t|
    t.decimal :value, precision: 10, scale: 2
  end
end

# Insert 3 random rows
ActiveRecord::Base.connection.execute(<<~SQL)
  INSERT INTO numeric_models (value) VALUES
    (0.01),
    (0.02),
    (0.03);
SQL

result = ActiveRecord::Base.connection.execute(<<~SQL)
  SELECT * FROM numeric_models;
SQL

puts result.to_a