Mongoid instanciate models from Mongo aggregations and mark as existing record

392 Views Asked by At

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.
3

There are 3 best solutions below

0
Jaffa On BEST ANSWER

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.

4
ABrowne On

Cells works on any PORO. So the easiest way to achieve what you want is to create a class that represents the data you want within your models files. Just create this as plain ruby class. You can hide your data query method for creating the aggregations and returning a set of classes as a class method.

Something like (you'll want to tidy this up, it is just a hack to get you started):

# some PORO
class Record
   attr_accessor :field_1, :field_2, :field_3

   def self.build
       # your existing code
       entries = [Class1, Class2, Class3].inject([]) do |array, clazz|
        entries << profile_collection.collection.aggregate([ { '$sample': { size: 10 } } ])    
       end

       array_of_objects = []

       # now for each record within the aggregate create an object
       entries.each do |obj|
          new_poro = self.new
          obj.keys.each do |key|
             new_poro.self.instance_variable_set(key, obj[key])
          end
          array_of_objects.push new_poro
       end  
       return array_of_objects 
    end
end


# to run it in your controller
@records_in_objects_for_cells = Record.build

# in your views
<%= cell(:record, collection: records_in_objects_for_cells %>
2
ABrowne On

To answer your edited question, you could just set it to false. The variable is new_record as seen here (http://www.rubydoc.info/github/mongoid/mongoid/Mongoid/Stateful:new_record%3F).

So:

r = MongoRecord.find_by(x:y)
e = r.new(e)
e.new_record?
=> true
e.new_record = false
e.new_record? 
=> false

MongoId uses this flag to know whether it's persisted. It uses the _id to know which record to update if a persistence event occurs.