如何让form_with将自定义ActiveModel对象识别为已持久化实例
解答
form_with判断传入模型实例是新建记录还是已持久化记录,完全依赖ActiveModel的标准接口约定,不需要额外引入依赖或者修改表单逻辑,在你的TransitProvider类中实现对应核心方法即可解决问题。
判断逻辑说明
Rails表单辅助方法的判定规则非常明确:
- 调用实例的
persisted?方法,返回false时判定为新记录,表单提交走POST请求,对应create动作 - 调用实例的
persisted?方法返回true时判定为已存在记录,表单提交走PATCH请求,对应update动作 - 生成已存在记录的提交路径时,会调用实例的
id方法获取唯一标识,拼接成员路由 - 部分表单组件、参数生成逻辑会调用
to_key方法获取记录主键,缺失时可能触发无方法报错
具体实现
TransitProvider作为聚合Provider和Service的组合对象,不需要对应独立数据库表,直接按业务规则实现对应方法即可,参考代码如下:
class TransitProvider include ActiveModel::Model attr_accessor :provider, :service # ... 原有属性、业务逻辑定义 # 返回唯一标识该聚合实例的ID,不需要是数据库自增整数 def id return unless provider.persisted? && service.persisted? # 用两个关联记录的ID拼接生成唯一ID即可,也可以根据业务规则自定义生成规则 "combo-#{provider.id}-#{service.id}" end # 核心判定方法,返回true时form_with就会识别为已持久化记录 def persisted? provider.persisted? && service.persisted? end # 兼容其他ActiveModel生态的辅助方法,避免无方法报错 def to_key persisted? ? [id] : nil end end
注意事项
id返回值支持字符串类型,只要你的控制器update、show等动作能根据这个ID值找到对应的Provider和Service记录、组装回TransitProvider实例即可- 如果自定义了独立的路由规则,也可以在
form_with调用时手动指定url和method参数兜底,但实现上述标准接口后不需要额外配置 - 不需要让该类继承
ActiveRecord::Base,纯ActiveModel实现完全可以兼容所有Rails内置表单辅助方法的逻辑
内容的提问来源于stack exchange,提问作者Dan Sharp
相关产品推荐
相关产品推荐

