Rails Mongoid自定义Price字段遇undefined bson_type错误求助
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.

