Search code examples
mongodbmongoidrails-cells

Mongoid instanciate models from Mongo aggregations and mark as existing record


Since the Mongoid API did not make the MongoDB $sample operation visible, I had to manually run a query with the Mongo driver, and I don't know what to do with the results.

I have different classes/collections that adhere to some common interface (I did not want to use inheritance for several reasons), and I am trying to render those as a single collection. I have a code that samples from those three classes

entries = [Class1, Class2, Class3].inject([]) do |array, clazz|
  entries << clazz.collection.aggregate([ { '$sample': { size: 10 } } ])    
end

This gives me an array of three different Mongo::Collection::View::Aggregation. I'd like to somehow merge those and be able to instanciate the objects so I can use them in my views (with cells for example)

<%= cell(:profile, collection: entries) %>

Using entries.to_a will return an array of hashes and not an array of (model) objects. I was hoping it would be the case and that I would then use cells builder to handle the rest of subtle differences between the models

builds do |model, options|
    case model
    when Class1; Class1Cell
    when Class2; Class2Cell
    when Class3; Class3Cell
  end

EDIT :

I can actually still use to_a and use the key _type to find the corresponding Constant/Model. Now the newt question, is how to instanciate a model with the hash, that does not return true on new_record?

sample = entries.to_a.first
  instance = Utility.resolve_class(sample[:_type]).new(entry_hash)
  # Problem is...
  instance.new_record? # => returns true, but since it comes from the DB it means it has already been persisted so it should return false.

Solution

  • The best approach would be to use Mongoid::Document's class method instantiate:

    Person.instantiate(document)
    # or even
    Person.instantiate({firstname: 'John', lastname: 'Doe'})
    

    Or for your example:

    entries = [Class1, Class2, Class3].inject([]) do |array, clazz|
      entries << clazz.collection.aggregate([
        { '$sample': { size: 10 } }
      ]).map do |document|
        clazz.instantiate(document)
      end    
    end
    

    As stated in the description:

    Instantiate a new object, only when loaded from the database or when the attributes have already been typecast.

    Moreover, it takes selected_fields as a second parameter which is useful to let it know only the given fields have been loaded from the database.