You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 20:16:03