Ember与Rails中的验证错误处理方案咨询
Hey there! Great to hear you're finding Ember productive even as a new user—let's fix that error handling issue for you.
First, let's break down what's off with your current approach, then walk through the Ember-friendly way to handle both backend and frontend validation errors.
What's Wrong With Your Current Setup
Right now you're manually maintaining an errors array in your controller, but Ember Data is designed to automatically handle JSON API-compliant errors from your Rails backend. When a save() fails, Ember Data parses those errors and attaches them directly to the model instance's errors property (a DS.Errors object), so you don't need to copy them over manually.
Step 1: Clean Up Your Controller
Let's remove the manual errors tracking and let Ember Data do the heavy lifting. We'll also use arrow functions to avoid self = this boilerplate:
// controllers/categories/new.js import Controller from '@ember/controller'; import { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; export default class CategoriesNewController extends Controller { @service store; @tracked title = ""; @tracked description = ""; @tracked category; // Track the category instance so the template can access its errors @action submitData(e) { e.preventDefault(); // Create and track the category record this.category = this.store.createRecord("category", { title: this.title, description: this.description, }); this.category.save() .then(() => this.transitionToRoute("categories")) .catch(() => { // No need to manually assign errors—Ember Data already did this! // You can add optional logic here like scrolling to the first error }); } }
Step 2: Update Your Template to Use Model Errors
Now you can directly reference the model's errors in your template. You can show errors per-field or display all errors together:
{{! templates/categories/new.hbs }} <form {{on "submit" this.submitData}}> {{! Title field with errors }} <div class="mb-4"> <label class="block mb-1" for="title">Title</label> <input type="text" id="title" value={{this.title}} {{on "input" (fn (mut this.title))}} class="border rounded p-2 w-full" /> {{#each this.category?.errors.title as |error|}} <p class="mt-1 text-sm font-medium text-red-700">{{error.message}}</p> {{/each}} </div> {{! Description field with errors }} <div class="mb-4"> <label class="block mb-1" for="description">Description</label> <input type="text" id="description" value={{this.description}} {{on "input" (fn (mut this.description))}} class="border rounded p-2 w-full" /> {{#each this.category?.errors.description as |error|}} <p class="mt-1 text-sm font-medium text-red-700">{{error.message}}</p> {{/each}} </div> {{! Optional: Show all global/field errors }} {{#each this.category?.errors.messages as |message|}} <p class="mb-2 text-sm font-medium text-red-700">{{message}}</p> {{/each}} <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded">Save Category</button> </form>
Step 3: Adding Client-Side Validation in the Model
If you want to add frontend validation (before sending to Rails), you can define a validation method directly in your model. This keeps your validation logic organized in the model layer where it belongs:
// models/category.js import Model, { attr } from '@ember-data/model'; export default class CategoryModel extends Model { @attr('string') title; @attr('string') description; // Custom client-side validation validate() { const errors = []; if (!this.title?.trim()) { errors.push({ attribute: 'title', message: 'Title can\'t be blank' }); } if (this.description?.length < 5) { errors.push({ attribute: 'description', message: 'Description must be at least 5 characters' }); } return errors; } }
Then update your controller to run this validation before saving:
@action submitData(e) { e.preventDefault(); this.category = this.store.createRecord("category", { title: this.title, description: this.description, }); // Run client-side validation first const validationErrors = this.category.validate(); if (validationErrors.length) { // Add validation errors to the model's errors object validationErrors.forEach(error => { this.category.errors.add(error.attribute, error.message); }); return; // Don't send to backend if validation fails } this.category.save() .then(() => this.transitionToRoute("categories")) .catch(() => {}); }
Key Notes
- Ember Data automatically parses your Rails API's JSON API errors because your response follows the spec—this is why the errors show up on the model's
errorsproperty without extra work. - The
DS.Errorsobject has handy methods likeget('attribute')to get errors for a specific field, andmessagesto get all error strings at once. - Keeping validation logic in the model follows Ember's convention of separating concerns, making your code more maintainable.
内容的提问来源于stack exchange,提问作者Abeid Ahmed

