基于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. 代码说明
- 统一格式:将选项名称转为小写,与目标结构保持一致,避免大小写冲突。
- 递归逻辑:
- 若无剩余选项,返回包含
sku的底层哈希; - 按当前选项值分组Item子集;
- 对每个分组递归处理下一层选项,直至完成所有层级。
- 若无剩余选项,返回包含
- 分组处理:用
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
相关产品推荐
相关产品推荐

