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

Rails Mongoid自定义Price字段遇undefined bson_type错误求助

Fixing the "undefined bson_type" Error with Custom Price Type in Nested Hashes (Rails + MongoDB)

Hey there, let's break down why you're hitting this undefined bson_type error now that you've added a nested tree-like Hash containing your custom Price objects. The core issue here is that while your Price type works fine as a top-level field, MongoDB's BSON serializer doesn't automatically know how to handle it when nested inside a Hash—let's fix that.

1. Ensure Your Price Class Implements Required BSON Methods

First, double-check that your Price class has all the BSON serialization hooks needed, especially for nested scenarios. Even if you have mongoize and demongoize, you might be missing bson_type and to_bson which are critical when the object is nested:

class Price
  attr_reader :value, :currency

  def initialize(value:, currency:)
    @value = value
    @currency = currency
  end

  # Required for BSON serialization to identify the type
  def bson_type
    'a' # Matches BSON's array type, since you store [value, currency]
  end

  # Convert Price object directly to a BSON-compatible array
  def to_bson
    [@value, @currency].to_bson
  end

  # Reverse the process for deserialization from BSON
  def self.from_bson(bson)
    value, currency = BSON::Array.from_bson(bson)
    new(value: value, currency: currency)
  end

  # Your existing mongoize/demongoize methods (keep these!)
  def self.mongoize(object)
    case object
    when Price then [object.value, object.currency]
    when Array then object
    else nil
    end
  end

  def self.demongoize(object)
    return nil unless object.is_a?(Array) && object.size == 2
    new(value: object[0], currency: object[1])
  end
end

2. Register Your Price Type with MongoDB/BSON

Next, you need to tell MongoDB's BSON registry how to handle your Price class. Add this to an initializer (e.g., config/initializers/mongo_custom_types.rb):

# Register Price type with BSON for proper serialization
BSON::Registry.register(Price, Price.new(value: 0, currency: "USD").bson_type) do |price|
  price.to_bson
end

# If using Mongoid, register the type for field handling
Mongoid::Fields.register_type(:price) do |type|
  Mongoid::Fields::Serializable.new(type)
end

3. Handle Nested Hashes Recursively

The main gotcha here is that Mongoid's default Hash field type doesn't recursively process custom types. You have two solid options to fix this:

Option A: Override the Hash Field's Getter/Setter

Manually mongoize/demongoize nested Price objects in your model's Hash field:

class YourModel
  include Mongoid::Document
  field :price, type: Price
  field :pricing_tree, type: Hash # Your nested tree-like Hash

  # Override setter to mongoize nested Price objects before storage
  def pricing_tree=(value)
    mongoized_tree = value.deep_transform_values do |v|
      v.is_a?(Price) ? Price.mongoize(v) : v
    end
    super(mongoized_tree)
  end

  # Override getter to demongoize back to Price objects when reading
  def pricing_tree
    raw_tree = super
    return raw_tree unless raw_tree

    raw_tree.deep_transform_values do |v|
      v.is_a?(Array) && v.size == 2 ? Price.demongoize(v) : v
    end
  end
end

Option B: Create a Custom Hash Field Type

For a more scalable solution, build a custom Mongoid field type that automatically handles nested custom types:

# app/models/fields/nested_hash_with_custom_types.rb
class NestedHashWithCustomTypes < Mongoid::Fields::Hash
  def serialize(value)
    return nil if value.nil?
    value.deep_transform_values do |v|
      v.respond_to?(:mongoize) ? v.mongoize : v
    end
  end

  def deserialize(value)
    return nil if value.nil?
    value.deep_transform_values do |v|
      # Adjust this logic to match how you detect Price-serialized arrays
      if v.is_a?(Array) && v.size == 2 && v[0].is_a?(Numeric) && v[1].is_a?(String)
        Price.demongoize(v)
      else
        v
      end
    end
  end
end

# Register the custom field type in your initializer
Mongoid::Fields.register_type(:nested_hash_with_prices, NestedHashWithCustomTypes)

# Use it in your model:
class YourModel
  include Mongoid::Document
  field :price, type: Price
  field :pricing_tree, type: :nested_hash_with_prices
end

4. Test the Fix

After implementing these changes, test creating/updating your model with the nested Hash containing Price objects. You should no longer see the undefined bson_type error, and the data should serialize/deserialize correctly between your Price objects, the MongoDB array format, and the frontend's expected array structure.

内容的提问来源于stack exchange,提问作者Michael H.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:27:01