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