Ember 3.19更新记录并保存至Rails API的正确方法
看起来你遇到的核心问题是:Ember已经识别到模型属性的变更(changedAttributes()显示正确),但调用save()时发送的PATCH请求却携带了原始值。结合你的代码和Ember Data 3.19的特性,我整理了几个关键的排查和修复步骤:
1. 给模型属性指定正确的类型
你的Score模型中,wins、draws、losses没有指定属性类型,Ember Data默认会按字符串处理这些字段。当你把localStorage的字符串转成数字赋值时,虽然changedAttributes()能检测到变更,但序列化时可能因为类型不匹配,导致Ember Data没有正确把新值打包到请求中。
修改模型代码,明确指定数字类型:
// app/models/score.js import Model, { attr } from '@ember-data/model'; export default class ScoreModel extends Model { @attr username; @attr('number') wins; @attr('number') draws; @attr('number') losses; }
2. 优化记录获取与属性赋值逻辑
peekAll()是从本地store读取记录,但如果记录不是通过Ember Data的可编辑实例加载的,可能会出现更新不生效的情况。另外,批量赋值用setProperties()比单独赋值更规范,能确保变更被正确追踪:
// app/controllers/game.js async sendScore() { const username = localStorage.getItem("username"); if (!username) { console.error('请先输入用户名'); return; } // 优先从本地store查找记录 let entry = this.store.peekAll('score').find(score => score.username === username); // 本地没有则从后端查询 if (!entry) { try { entry = await this.store.queryRecord('score', { username }); } catch (error) { console.error('未找到该用户的分数记录:', error); // 如果需要自动新建记录,可以取消下面的注释 // entry = this.store.createRecord('score', { username }); return; } } // 安全解析localStorage中的数值(添加基数10避免八进制问题) const wins = parseInt(localStorage.getItem("wins"), 10); const losses = parseInt(localStorage.getItem("losses"), 10); const draws = parseInt(localStorage.getItem("draws"), 10); // 批量设置属性 entry.setProperties({ wins, losses, draws }); // 再次确认变更(方便调试) console.log('待更新的属性:', entry.changedAttributes()); try { await entry.save(); console.log('分数更新成功!'); } catch (error) { console.error('更新失败:', error); // 这里可以添加用户提示,比如显示错误信息到页面 } }
3. 确认序列化器与Rails后端参数格式匹配
Ember Data默认使用JSON API格式发送请求,但Rails API如果没有配置JSON API支持,会无法正确解析参数。
情况1:Rails使用JSON API格式
确保Rails项目安装了jsonapi-rails gem,并配置控制器处理JSON API参数:
# Gemfile gem 'jsonapi-rails' # app/controllers/scores_controller.rb class ScoresController < ApplicationController include JSONAPI::ActsAsResourceController private def score_params params.require(:data).require(:attributes).permit(:wins, :losses, :draws, :username) end end
情况2:Rails使用传统REST格式
如果Rails期望的是{ score: { wins: 56 } }这类参数,需要给Score模型添加REST序列化器:
// app/serializers/score.js import RESTSerializer from '@ember-data/serializer/rest'; export default class ScoreSerializer extends RESTSerializer { serialize(snapshot, options) { let json = super.serialize(snapshot, options); // 把属性包裹在score键中,匹配Rails的参数要求 return { score: json }; } }
同时对应的适配器保持REST风格:
// app/adapters/score.js import RESTAdapter from '@ember-data/adapter/rest'; export default class ScoreAdapter extends RESTAdapter { host = 'http://localhost:3000'; }
4. 调试时检查请求细节
打开浏览器开发者工具的Network标签,查看PATCH请求的:
- 请求URL是否正确(比如
http://localhost:3000/scores/1) - 请求体中的参数是否是新值
- 后端响应的状态码和内容(如果返回422,说明后端参数验证失败)
通过这些步骤,应该能解决你遇到的更新问题。
内容的提问来源于stack exchange,提问作者Sensanaty

