如何在Ruby on Rails中通过ActiveRecord向PostgreSQL插入裸null jsonb值
问题背景
先看PostgreSQL中三种和null相关的jsonb插入场景:
CREATE TABLE jsonb_null ( id bigserial PRIMARY KEY, value jsonb ); INSERT INTO jsonb_null (value) VALUES ('"null"'), ('null'), (NULL); SELECT id, value, jsonb_typeof(value) FROM jsonb_null ORDER BY id;
执行结果:
id | value | jsonb_typeof ----+--------+-------------- 1 | "null" | string 2 | null | null 3 | | (3 rows)
其中第2种是裸jsonb null值(jsonb_typeof返回null),第3种是数据库层面的NULL值(字段无内容)。现在需要通过ActiveRecord实现插入第2种情况。
用户提供的测试代码中,ActiveRecord默认会把Ruby的nil映射为数据库NULL,无法区分两种null,导致测试无法通过。
解决方案
要插入裸jsonb null值,需要明确告知PostgreSQL这是jsonb类型的null,以下是两种可行方式:
方式1:直接使用Arel.sql构造SQL片段
通过原生SQL片段指定jsonb类型的null,绕过ActiveRecord的默认序列化逻辑:
# 插入裸jsonb null JsonbNull.create!(value: Arel.sql("'null'::jsonb")) # 更新为裸jsonb null jsonb_record = JsonbNull.find(1) jsonb_record.update!(value: Arel.sql("'null'::jsonb"))
方式2:自定义字段类型(推荐)
定义自定义ActiveRecord类型,用特殊标识区分Ruby的nil(对应数据库NULL)和jsonb null:
# app/types/jsonb_null_type.rb class JsonbNullType < ActiveRecord::Type::Jsonb def serialize(value) # 用:json_null标识需要插入裸jsonb null value == :json_null ? 'null' : super end end # 在模型中使用自定义类型 class JsonbNull < ActiveRecord::Base attribute :value, JsonbNullType.new end # 插入裸jsonb null JsonbNull.create!(value: :json_null)
修正后的测试代码
以下代码可正确区分并验证两种null的差异:
require 'bundler/inline' gemfile(true) do source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem 'rails' gem 'pg' end require 'active_record' require 'minitest/autorun' require 'logger' # 先执行创建数据库命令:CREATE DATABASE rails_test; ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: 'rails_test') ActiveRecord::Base.logger = Logger.new($stdout) ActiveRecord::Base.connection.execute(<<~SQL) DROP TABLE IF EXISTS jsonb_nulls; CREATE TABLE jsonb_nulls ( id bigserial PRIMARY KEY, value jsonb ); INSERT INTO jsonb_nulls (id, value) VALUES (1, '"null"'), (2, 'null'), (3, NULL); SQL class JsonbNull < ActiveRecord::Base; end class JsonbNullTest < Minitest::Test def test_distinguish_jsonb_null_and_db_null # 直接查询验证类型差异 result = ActiveRecord::Base.connection.execute("SELECT id, jsonb_typeof(value) FROM jsonb_nulls ORDER BY id") assert_equal 'string', result.first['jsonb_typeof'] assert_equal 'null', result.to_a[1]['jsonb_typeof'] assert_nil result.to_a[2]['jsonb_typeof'] # 验证插入逻辑 # 插入裸jsonb null JsonbNull.create!(value: Arel.sql("'null'::jsonb")) json_null_record = JsonbNull.last json_null_type = ActiveRecord::Base.connection.select_value("SELECT jsonb_typeof(value) FROM jsonb_nulls WHERE id=#{json_null_record.id}") assert_equal 'null', json_null_type # 插入数据库NULL JsonbNull.create!(value: nil) db_null_record = JsonbNull.last db_null_type = ActiveRecord::Base.connection.select_value("SELECT jsonb_typeof(value) FROM jsonb_nulls WHERE id=#{db_null_record.id}") assert_nil db_null_type end end
内容的提问来源于stack exchange,提问作者Dmitry Matveyev
相关产品推荐
相关产品推荐

