如何在Mobility gem中正确配置带scope的唯一性验证
Mobility 带作用域的多语言字段唯一性验证实现方案
问题背景
使用Mobility gem为模型添加多语言能力时,遍历I18n.available_locales为各语言版本的name字段添加非空、按animal_type作用域的唯一性验证的写法在旧项目正常运行,在新项目中完全失效。
失效代码如下:
I18n.available_locales.each do |locale| validates :"name_#{locale}", presence: true, uniqueness: {scope: :animal_type} end
当前Mobility配置:
Mobility.configure do plugins do backend :container active_record reader writer backend_reader query cache presence locale_accessors end end
经定位失效点为带scope的uniqueness验证配置。
核心原因
- 新项目使用
:container后端存储多语言内容,所有语言的翻译值默认存在[字段名]_translations的JSON/JSONB列中,Rails原生的uniqueness验证只会直接匹配表字段,不会自动解析JSON列内的嵌套多语言键,因此无法正确触发校验。 - 配置中已经开启了
presence插件,Mobility会自动对所有可用语言的翻译字段做非空校验,手动遍历加presence: true属于冗余代码。
推荐实现方式
直接使用Mobility官方提供的uniqueness插件,和原生Rails验证用法完全一致,自动兼容所有后端(包括container、key_value、table等),不需要手动遍历locale。
- 首先在Mobility配置中引入
uniqueness插件,注意需要放在active_record插件之后:
Mobility.configure do plugins do backend :container active_record uniqueness # 新增该行 reader writer backend_reader query cache presence locale_accessors end end
- 模型层直接对翻译主字段添加验证即可,scope参数和Rails原生写法无区别,验证会自动覆盖所有配置的可用语言:
class Animal < ApplicationRecord extend Mobility translates :name, type: :string # 单条验证即可覆盖所有locale,支持任意scope配置 validates :name, presence: true, uniqueness: { scope: :animal_type } end
兼容手动实现方式(不推荐)
如果暂时不想启用官方uniqueness插件,需要手动修改uniqueness验证的查询逻辑,匹配container列内的对应语言键:
# 注意替换name_translations为你实际使用的container存储列名 TRANSLATION_COLUMN = :name_translations I18n.available_locales.each do |locale| validates :"name_#{locale}", presence: true validates :"name_#{locale}", uniqueness: { scope: :animal_type, conditions: ->(record, attr, value) { where("#{TRANSLATION_COLUMN} ->> :locale = :value", locale: locale.to_s, value: value) } } end
注意:该写法需要自行维护locale遍历逻辑,后续新增可用语言时需要同步确认验证覆盖范围,容易出现漏校验问题,优先使用官方插件方案。
内容的提问来源于stack exchange,提问作者Alexey Poimtsev
相关产品推荐
相关产品推荐

