You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue+Rails项目:Jbuilder与Normalizr的数据扁平化方案求助

我来帮你梳理下问题,从前后端两个方面给出解决方案,毕竟你能同时修改两端,这会让问题解决起来更顺畅~

一、后端Jbuilder代码优化

首先先修正你代码里的一个小笔误:json.trip_details hike.trip_details里的hike应该是trip,不然会抛出未定义错误。

接下来,针对归一化需求,你有两种后端结构可选:

选项1:保持嵌套结构(适合前端按关联关系遍历)

这种结构和你当前的写法一致,只是确保每个实体都带唯一id(你已经做到了):

json.user @user
# All of user's lists and associated categories and items
json.lists @user.lists do |list|
  json.(list, :id, :name, :active, :user_id)
  json.categories list.categories do |category|
    json.(category, :id, :name, :user_id, :created_at, :updated_at, :list_id)
    json.items category.items do |item|
      json.(item, :id, :name, :website, :price, :information, :notes, :category_id, :quantity)
    end
  end
end
# User's total hiking miles
json.total_m @user.m_by_month
# User's upcoming trips properties
json.upcoming_trips @user.upcoming_trips do |trip|
  json.(trip, :id, :name, :start_date, :end_date, :data_id, :list_id)
  # User's todo items
  json.todos trip.todo_items.sort_by {|t| t[:position]} do |todo|
    json.(todo, :id, :title, :completed, :position, :upcoming_trip_id, :user_id)
  end
  # User's shopping list items
  json.shopping_list_items trip.shopping_list_items.sort_by {|s| s[:position]} do |item|
    json.(item, :id, :title, :completed, :position, :quantity, :upcoming_trip_id, :user_id)
  end
  # 修正笔误:hike改为trip
  json.trip_details trip.trip_details.order(updated_at: :desc) do |detail|
    json.(detail, :id, :trip_type, :label, :url, :upcoming_trip_id)
  end
end

选项2:拆分所有实体到顶层(更适合前端归一化)

把所有关联实体都提取到顶层数组,让前端可以直接对每个实体集合做归一化:

json.user @user
json.lists @user.lists, :id, :name, :active, :user_id
json.categories @user.categories, :id, :name, :user_id, :created_at, :updated_at, :list_id
json.items @user.items, :id, :name, :website, :price, :information, :notes, :category_id, :quantity
json.total_m @user.m_by_month
json.upcoming_trips @user.upcoming_trips, :id, :name, :start_date, :end_date, :data_id, :list_id
json.todos @user.todo_items.sort_by {|t| t[:position]}, :id, :title, :completed, :position, :upcoming_trip_id, :user_id
json.shopping_list_items @user.shopping_list_items.sort_by {|s| s[:position]}, :id, :title, :completed, :position, :quantity, :upcoming_trip_id, :user_id
json.trip_details @user.trip_details.order(updated_at: :desc), :id, :trip_type, :label, :url, :upcoming_trip_id

二、前端Normalizr正确配置

你的当前schema存在关联关系错误和未定义顶层响应结构的问题,下面是修正后的代码:

1. 正确定义实体关联关系

先梳理清楚你的实体依赖链:
User → Lists → Categories → Items
User → UpcomingTrips → Todos/ShoppingListItems/TripDetails

对应的schema代码:

import { schema } from 'normalizr';

// 最底层的Item实体,属于Category
const itemSchema = new schema.Entity('items');

// Category实体,包含items数组,属于List
const categorySchema = new schema.Entity('categories', {
  items: [itemSchema]
});

// List实体,包含categories数组,属于User
const listSchema = new schema.Entity('lists', {
  categories: [categorySchema]
});

// 独立的Todo/ShoppingList/TripDetail实体
const todoSchema = new schema.Entity('todos');
const shoppingListItemSchema = new schema.Entity('shoppingListItems');
const tripDetailSchema = new schema.Entity('tripDetails');

// UpcomingTrip实体,包含三个关联数组
const upcomingTripSchema = new schema.Entity('upcoming_trips', {
  todos: [todoSchema],
  shopping_list_items: [shoppingListItemSchema],
  trip_details: [tripDetailSchema]
});

// User实体,包含lists和upcoming_trips数组
const userSchema = new schema.Entity('user', {
  lists: [listSchema],
  upcoming_trips: [upcomingTripSchema]
});

// 定义顶层响应schema:匹配后端返回的完整对象结构
export const userResponseSchema = new schema.Object({
  user: userSchema,
  lists: [listSchema],
  upcoming_trips: [upcomingTripSchema]
  // total_m是统计数据,不需要归一化,会自动保留在result中
});

2. 正确调用normalize方法

注意要使用顶层的userResponseSchema,而不是单独的userSchema:

import { normalize } from 'normalizr';
import { userResponseSchema } from './schema';

// 假设response是后端返回的完整数据对象
const normalizedData = normalize(response, userResponseSchema);

3. 归一化后的预期结构

处理后你会得到清晰的实体映射和结果引用:

{
  entities: {
    user: { [userId]: {...} },
    lists: { [listId]: {...} },
    categories: { [categoryId]: {...} },
    items: { [itemId]: {...} },
    upcoming_trips: { [tripId]: {...} },
    todos: { [todoId]: {...} },
    shoppingListItems: { [itemId]: {...} },
    tripDetails: { [detailId]: {...} }
  },
  result: {
    user: userId, // 对应user实体的id
    lists: [listId1, listId2, ...],
    upcoming_trips: [tripId1, tripId2, ...],
    total_m: [...] // 原封不动保留的统计数据
  }
}

三、额外注意事项

  • 确保所有实体都有唯一id字段,Normalizr依赖这个区分不同实体;如果你的实体id不是id字段,可以通过idAttribute指定,比如new schema.Entity('items', {}, { idAttribute: 'item_id' })
  • 对于不需要归一化的纯数据字段(比如total_m),不需要在schema中定义,会直接保留在result里
  • 测试时可以通过console.log(normalizedData)确认结构,之后在Vue中就能通过entities快速访问任意实体,比如normalizedData.entities.items[itemId]直接获取某个商品

内容的提问来源于stack exchange,提问作者J. Jackson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:11:39