如何通过Rails迁移为Active Record模型的JSON字段新增键且不覆盖现有数据
实现Rails JSON字段新增键且不覆盖原有数据的方案
你不需要提前备份数据再逐行写回,借助数据库原生的JSON合并能力即可安全完成需求,完整操作步骤如下:
1. 生成迁移文件
执行命令创建迁移,注意把示例中的Users替换为你自己的模型名:rails g migration add_address_key_to_users_data
2. 编写迁移逻辑
根据你使用的数据库类型选择对应代码,以下逻辑同时满足两个需求:
- 新创建的记录默认在
data字段中携带空值address键 - 现有记录仅新增
address键,不修改原有name、number的值
PostgreSQL(推荐使用jsonb类型字段)
class AddAddressKeyToUsersData < ActiveRecord::Migration[7.0] # 替换为你对应的Rails版本 def change # 给新记录设置默认值,不会影响现有记录 change_column_default :users, :data, from: {"name": "", "number": ""}, to: {"name": "", "number": "", "address": ""} # 给现有记录补address字段,使用PG的jsonb合并操作符||,仅新增不存在的键 reversible do |dir| dir.up do execute <<~SQL UPDATE users SET data = data || '{"address": ""}'::jsonb WHERE data ? 'address' = false; -- 仅更新还没有address键的记录,保障幂等性 SQL end end end end
MySQL
class AddAddressKeyToUsersData < ActiveRecord::Migration[7.0] def change change_column_default :users, :data, from: {"name": "", "number": ""}, to: {"name": "", "number": "", "address": ""} reversible do |dir| dir.up do execute <<~SQL UPDATE users SET data = JSON_MERGE_PATCH(data, '{"address": ""}') WHERE JSON_CONTAINS_PATH(data, 'one', '$.address') = 0; SQL end end end end
3. 模型层可选配置
如果你不想在数据库层面设置默认,也可以在模型中通过store_accessor配置默认值,代码更灵活:
class User < ApplicationRecord # Rails 7+ 支持直接给store_accessor加default参数 store_accessor :data, :name, :number, :address, default: "" # 低于Rails 7版本用after_initialize回调实现 # after_initialize do # return unless has_attribute?(:data) # self.address ||= "" # end end
注意事项
- 所有操作默认不会修改现有记录的
name、number值,仅新增不存在的address键 - 迁移中加了WHERE条件过滤已经存在
address键的记录,哪怕重复执行迁移也不会覆盖已有的address值 - 大数据量场景下直接执行SQL更新比遍历每条ActiveRecord对象效率高几个数量级
内容的提问来源于stack exchange,提问作者Petesta
相关产品推荐
相关产品推荐

