如何在Rails中使用update_all更新ActiveRecord store属性?
Rails中用update_all批量更新ActiveRecord Store属性的方案
我们在Rails模型里通过ActiveRecord的store来存储结构化配置:
store :settings, accessors: %i[enabled unit_of_distance], coder: JSON
这让我们能像访问普通数据库列一样操作这些属性:
Model.first.enabled => true Model.first.unit_of_distance => 'miles'
但直接用update_all批量更新store属性时会报错:
Model.update_all(unit_of_distance: 'miles')
错误信息:
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'model.unit_of_distance' in 'field list'
如果给属性加上settings前缀,虽然不会报错,但会覆盖整个settings字段里的其他属性,这不是我们想要的:
Model.update_all(settings: { unit_of_distance: 'miles' })
手动循环更新是可行的,但效率极低:
Model.all.each { |c| c.update(unit_of_distance: 'miles') }
高效解决方案:利用数据库JSON函数实现批量更新
不用循环,直接通过update_all结合数据库的JSON操作函数,就能只更新store里的指定属性,同时保留其他字段内容。
针对MySQL/MariaDB
使用JSON_SET函数,直接在update_all中写入SQL片段:
Model.update_all("settings = JSON_SET(settings, '$.unit_of_distance', 'miles')")
JSON_SET会仅更新指定路径的键值对,不会覆盖整个settings字段。
针对PostgreSQL
如果settings是jsonb类型(推荐使用该类型),使用jsonb_set:
Model.update_all("settings = jsonb_set(settings, '{unit_of_distance}', '\"miles\"'::jsonb)")
如果是json类型,用json_set:
Model.update_all("settings = json_set(settings, '{unit_of_distance}', '\"miles\"'::json)")
这里需要把字符串值转成JSON格式,所以用'\"miles\"',路径用'{unit_of_distance}'指定。
这种方式直接在数据库层面执行批量更新,和update_all的原生效率一致,无需加载所有模型实例,完美解决循环更新的性能问题。
内容的提问来源于stack exchange,提问作者Cameron
相关产品推荐
相关产品推荐

