Ruby on Rails类/对象列表缓存问题:新用户组关联验证失败
你遇到的这个问题,核心原因是验证规则里的in参数在类加载阶段就被静态求值了,而不是每次执行验证时动态获取最新的UserGroup ID列表。
当你的Rails应用启动(或者开发环境下第一次加载User类)时,UserGroup.full_list_of_ids会被执行一次,返回当时的所有UserGroup ID,这个值就被硬编码到验证规则里了。后续新创建的UserGroup根本不会被加入到这个固定列表中,所以保存User时就会触发验证错误——哪怕你在下拉框里能看到新的UserGroup,验证用的还是旧的ID列表。
把验证规则里的in参数改成一个lambda(匿名函数),这样每次执行验证时都会重新调用UserGroup.full_list_of_ids,拿到最新的ID列表:
class User < ActiveRecord::Base # ...其他代码... validates :user_group_id, inclusion: { in: -> { UserGroup.full_list_of_ids } }, unless: 'Rails.env.test?' # ...其他代码... end
或者更简洁一点,直接把逻辑写到lambda里,省去额外的类方法:
validates :user_group_id, inclusion: { in: -> { UserGroup.pluck(:id) } }, unless: 'Rails.env.test?'
在Rails的验证定义中,如果直接传入静态值(比如UserGroup.full_list_of_ids),这个值只会在类加载时计算一次。开发环境下虽然会在每次请求重新加载类,但如果是在同一个请求流程里先创建UserGroup再创建User(比如通过同一个表单或后台操作),这个静态值还是不会更新。用lambda的话,Rails会在每次执行验证时调用这个lambda,确保拿到的是数据库里的最新数据。
其实还有更符合Rails最佳实践的方式:既然User和UserGroup是关联关系,你可以直接用关联验证替代ID的包含验证,这样更简洁也更安全:
class User < ActiveRecord::Base belongs_to :user_group # ...其他代码... validates :user_group, presence: true, unless: 'Rails.env.test?' # ...其他代码... end
这种方式会自动验证user_group是否存在于数据库中,不需要手动维护ID列表,而且天然是动态的,完全避免了缓存问题。
内容的提问来源于stack exchange,提问作者Iain

