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