Mongoid random document

前端 未结 9 1390
迷失自我
迷失自我 2021-02-06 00:49

Lets say I have a Collection of users. Is there a way of using mongoid to find n random users in the collection where it does not return the same user twice? For now lets say

相关标签:
9条回答
  • 2021-02-06 01:03

    You can do this by

    1. generate random offset which will further satisfy to pick the next n elements (without exceeding the limit)
    2. Assume count is 10, and the n is 5
    3. to do this check the given n is less than the total count
    4. if no set the offset to 0, and go to step 8
    5. if yes, subtract the n from the total count, and you will get a number 5
    6. Use this to find a random number, the number definitely will be from 0 to 5 (Assume 2)
    7. Use the random number 2 as offset
    8. now you can take the random 5 users by simply passing this offset and the n (5) as a limit.
    9. now you get users from 3 to 7

    code

    >> cnt = User.count
    => 10
    >> n = 5
    => 5
    >> offset = 0
    => 0
    >> if n<cnt
    >>    offset = rand(cnt-n)
    >>  end
    >> 2
    >> User.skip(offset).limit(n)
    

    and you can put this in a method

    def get_random_users(n)
      offset = 0
      cnt = User.count
      if n < cnt
        offset = rand(cnt-n)
      end
      User.skip(offset).limit(n)
    end
    

    and call it like

    rand_users = get_random_users(5)
    

    hope this helps

    0 讨论(0)
  • 2021-02-06 01:07

    If you just want one document, and don't want to define a new criteria method, you could just do this:

    random_model = Model.skip(rand(Model.count)).first
    

    If you want to find a random model based on some criteria:

    criteria = Model.scoped_whatever.where(conditions) # query example
    random_model = criteria.skip(rand(criteria.count)).first
    
    0 讨论(0)
  • 2021-02-06 01:08

    Since I want to keep a criteria, I do:

    scope :random, ->{
      random_field_for_ordering = fields.keys.sample
      random_direction_to_order = %w(asc desc).sample
      order_by([[random_field_for_ordering, random_direction_to_order]])
    }
    
    0 讨论(0)
  • 2021-02-06 01:18

    The approach from @moox is really interesting but I doubt that monkeypatching the whole Mongoid is a good idea here. So my approach is just to write a concern Randomizable that can included in each model you use this feature. This goes to app/models/concerns/randomizeable.rb:

    module Randomizable
      extend ActiveSupport::Concern
    
      module ClassMethods
        def random(n = 1)
          indexes = (0..count - 1).sort_by { rand }.slice(0, n).collect!
    
          return skip(indexes.first).first if n == 1
          indexes.map { |index| skip(index).first }
        end
      end
    end
    

    Then your User model would look like this:

    class User
      include Mongoid::Document
      include Randomizable
    
      field :name
    end
    

    And the tests....

    require 'spec_helper'
    
    class RandomizableCollection
      include Mongoid::Document
      include Randomizable
    
      field :name
    end
    
    describe RandomizableCollection do
      before do
        RandomizableCollection.create name: 'Hans Bratwurst'
        RandomizableCollection.create name: 'Werner Salami'
        RandomizableCollection.create name: 'Susi Wienerli'
      end
    
      it 'returns a random document' do
        srand(2)
    
        expect(RandomizableCollection.random(1).name).to eq 'Werner Salami'
      end
    
      it 'returns an array of random documents' do
        srand(1)
    
        expect(RandomizableCollection.random(2).map &:name).to eq ['Susi Wienerli', 'Hans Bratwurst']
      end
    end
    
    0 讨论(0)
  • 2021-02-06 01:19

    I think it is better to focus on randomizing the returned result set so I tried:

    Model.all.to_a.shuffle
    

    Hope this helps.

    0 讨论(0)
  • 2021-02-06 01:22

    The best solution is going to depend on the expected size of the collection.

    For tiny collections, just get all of them and .shuffle.slice!

    For small sizes of n, you can get away with something like this:

    result = (0..User.count-1).sort_by{rand}.slice(0, n).collect! do |i| User.skip(i).first end
    

    For large sizes of n, I would recommend creating a "random" column to sort by. See here for details: http://cookbook.mongodb.org/patterns/random-attribute/ https://github.com/mongodb/cookbook/blob/master/content/patterns/random-attribute.txt

    0 讨论(0)
提交回复
热议问题