基于MongoDB的Ruby服务端自定义函数执行替代方案咨询
Hey there! I totally get the pain of $eval needing full database permissions—definitely not something you want in production. Let's break down your questions and go through practical alternatives:
1. Can you call saved system.js functions without $eval?
Short answer: No, not directly. MongoDB's server-side JavaScript functions stored in system.js are designed to be invoked either via $eval, or within MapReduce/aggregation stages (with limitations). Since $eval is off the table due to permissions, relying on system.js isn't a viable path here. Let's look at better options.
2. Recommended Alternatives
Option 1: Calculate Document Size in Ruby (Application Layer)
This is the simplest and most permission-friendly approach. The MongoDB Ruby driver lets you serialize documents to BSON and calculate their byte size directly in your app:
# Initialize your client client = Mongo::Client.new(['your-mongo-host:27017'], database: 'your_database') customers_collection = client[:customers] # Iterate through customers and compute size customers_collection.find.each do |customer_doc| bson_size = customer_doc.to_bson.length puts "Customer #{customer_doc['_id']} document size: #{bson_size} bytes" end
No special MongoDB permissions needed here—just standard read access to the customers collection. You can also easily tweak this to exclude specific fields from the size calculation if needed.
Option 2: Use Aggregation Framework with $bsonSize (MongoDB 4.4+)
If you prefer doing the calculation on the database side (better for large datasets), use the $bsonSize operator in an aggregation pipeline. This requires only read permissions on the collection, no full database access:
size_calculation_pipeline = [ { '$addFields' => { 'document_size_bytes' => { '$bsonSize' => '$$ROOT' } } } ] # Run the aggregation and process results customers_collection.aggregate(size_calculation_pipeline).each do |doc| puts "Customer #{doc['_id']} size: #{doc['document_size_bytes']} bytes" end
This is efficient and leverages MongoDB's native capabilities—way better than $eval or MapReduce for modern MongoDB versions.
Option 3: MapReduce (For Older MongoDB Versions)
If you're stuck on a MongoDB version pre-4.4 (no $bsonSize), MapReduce can work. You can use your saved system.js function here without $eval:
First, save the size-calculation function to system.js via the Ruby driver:
client[:system.js].insert_one({ _id: 'getDocSize', value: BSON::Code.new('function(doc) { return Object.bsonsize(doc); }') })
Then run the MapReduce job:
map_func = BSON::Code.new('function() { emit(this._id, getDocSize(this)); }') reduce_func = BSON::Code.new('function(key, values) { return values[0]; }') results = customers_collection.map_reduce(map_func, reduce_func).to_a results.each do |result| puts "Customer #{result['_id']} size: #{result['value']} bytes" end
Note: MapReduce is deprecated in newer MongoDB versions, so use this only if you can't upgrade to 4.4+.
Final Recommendation
Go with Option 1 if you want maximum control and minimal database dependencies, or Option 2 if you need database-side processing for large datasets. Both avoid the permission issues of $eval entirely.
内容的提问来源于stack exchange,提问作者Michal Molhanec

