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

基于Rails ActiveRecord查询构建产品选项组合树形嵌套哈希

构建Rails产品选项嵌套树形哈希结构

我正在使用Rails 7,需要为指定产品构建可用选项组合的树形嵌套哈希结构,目标结构示例如下:

{
  "option": "color",
  "values": {
    "red": {
      "option": "size",
      "values": {
        "S": {
          "option": "material",
          "values": {
            "cotton": {
              "sku": "shirt-red-s-cotton"
            }
          }
        },
        "M": { ... },
        "L": { ... }
      }
    },
    "green": { ... },
    "blue": { ... }
  }
}

现有数据库Schema

ActiveRecord::Schema[7.0].define(version: 2022_11_04_214231) do
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "items", force: :cascade do |t|
    t.bigint "product_id", null: false
    t.json "p_options"
    t.string "sku"
    t.integer "stock"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["product_id"], name: "index_items_on_product_id"
    t.index ["sku"], name: "index_items_on_sku", unique: true
  end

  create_table "product_option_lists", force: :cascade do |t|
    t.bigint "product_id", null: false
    t.bigint "product_option_id", null: false
    t.string "option"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["product_id"], name: "index_product_option_lists_on_product_id"
    t.index ["product_option_id"], name: "index_product_option_lists_on_product_option_id"
  end

  create_table "product_options", force: :cascade do |t|
    t.string "option"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["option"], name: "index_product_options_on_option", unique: true
  end

  create_table "products", force: :cascade do |t|
    t.string "name"
    t.boolean "active"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["name"], name: "index_products_on_name", unique: true
  end

  add_foreign_key "items", "products"
  add_foreign_key "product_option_lists", "product_options"
  add_foreign_key "product_option_lists", "products"
end

可用数据查询

# 获取指定产品的所有Item
Item.where(product_id: <some_id>)

# 仅提取产品选项
Item.where(product_id: <some_id>).map{ |item| item.p_options }

第二个查询返回的数据示例:

[
  { "Size" => "S", "Color"=>"Red", "Material"=>"Cotton"},
  { "Size" => "S", "Color"=>"Green", "Material"=>"Silk"},
  # ... 更多条目
  { "Size" => "XL", "Color" => "Blue", "Material"=> "Cotton"}
]

另外我已实现接口,可返回指定产品的选项顺序及对应值:

[
  { "option": "color", "values": ["Red","Green", "Blue"]},
  { "option": "size", "values": ["S", "M", "L"]},
  // ... 更多选项
]

我计划通过递归遍历选项值构建结构,但尚未掌握哈希递归构建方法,需要解决方案。


解决方案

1. 明确选项层级顺序

直接使用你已实现的接口返回的选项顺序,它定义了树形结构的层级先后(比如color→size→material)。

2. 递归构建嵌套哈希

编写递归方法,接收当前层级选项、剩余选项列表、对应Item数据,逐步构建嵌套结构。

实现代码

可在Product模型或单独服务类中添加以下方法:

def build_option_tree(product_id)
  # 获取产品所有带sku的Item
  items = Item.where(product_id: product_id)
  # 获取选项顺序(替换为你实际的接口方法)
  option_sequence = product_options_with_values(product_id).map { |opt| opt["option"].downcase }

  # 递归构建逻辑
  build_tree = lambda do |current_option, remaining_options, items_subset|
    # 到达最底层,返回sku
    return { sku: items_subset.first.sku } if remaining_options.empty?

    # 按当前选项值分组Item
    grouped = items_subset.group_by do |item|
      item.p_options[current_option.capitalize].downcase
    end

    {
      option: current_option,
      values: grouped.transform_values do |items_group|
        build_tree.call(remaining_options.first, remaining_options.drop(1), items_group)
      end
    }
  end

  build_tree.call(option_sequence.first, option_sequence.drop(1), items) if option_sequence.present?
end

# 示例:获取产品选项顺序及值的方法(替换为你实际的实现)
def product_options_with_values(product_id)
  ProductOptionList.where(product_id: product_id).map do |pol|
    {
      option: pol.option.downcase,
      values: Item.where(product_id: product_id)
                 .pluck("p_options->>'#{pol.option}'")
                 .uniq
                 .map(&:downcase)
    }
  end
end

3. 代码说明

  • 统一格式:将选项名称转为小写,与目标结构保持一致,避免大小写冲突。
  • 递归逻辑:
    1. 若无剩余选项,返回包含sku的底层哈希;
    2. 按当前选项值分组Item子集;
    3. 对每个分组递归处理下一层选项,直至完成所有层级。
  • 分组处理:用group_by确保每个选项值分支对应正确的后续选项组合。

4. 使用示例

# 构建指定产品的选项树
product_id = 1
option_tree = build_option_tree(product_id)

# 输出JSON格式结果
puts option_tree.to_json

5. 优化建议

  • 若Item数量较多,可使用数据库JSON函数分组或预加载数据提升性能;
  • 需过滤无库存Item时,在查询中添加where(stock: > 0)条件。

内容的提问来源于stack exchange,提问作者Mauricio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 23:25:35