Roberto Pezzali
Roberto Pezzali

Reputation: 2504

Get random record giving priority to record not already chosen

I'm trying to find a way to get some random record without repetition at least until I take all the records from a table one time.

I have a table with some questions (3000).

I create a quiz selecting some random questions (30). I need to create quiz with all the question so without repetition until I use all the 3000 questions. Then I can begin to us the questions for another cycle.

In a single user environment I can add a field "count" to increase in my Question table every time I use a question, but I have a multiuser environment, so I can't pollute the Question table.

Any idea?

Upvotes: 1

Views: 124

Answers (4)

Ruby Racer
Ruby Racer

Reputation: 5740

You must create an intermediate table.

Assuming your models are User and Question:

rails g migration create_users_questions user_id:integer question_id:integer
rake db:migrate

This will generate a users_questions table in your database.

Then, in your user.rb:

has_and_belongs_to_many :questions

and in your question.rb:

has_and_belongs_to_many :users

Each row of the new table will contain a user_id and a question_id

So, to get unused questions for user @user:

Generic Syntax

@unused_questions = Question.where('id not in (?)',@user.questions.map(&:id))


Rails ~> 4.0 Syntax [thanks D-Side]

@unused_questions = Question.where.not(id:@user.question_ids)

And to add a new, used, question (let's say @question) to @user:

@user.questions<<@question

That's it, end of story...

Finally, to reset status of the @user's questions:

if @unused_questions.empty?       # we have defined that above
    @user.questions.clear
end

And then you go adding once again...

Upvotes: 3

Alejandro Babio
Alejandro Babio

Reputation: 5229

I think you can use cache for store ids of sorted unused questions.

available_question_ids = Rails.cache.read('available_question_ids')
if available_questions_ids.nil? || available_question_ids.empty?
  available_question_ids = Question.pluck(:id).to_a.shuffle
end
quiz_questions_ids = available_question_ids.pop(30)
Rails.cache.write('available_question_ids', available_question_ids)

Now you can find your questions with quiz_questions = Question.find(quiz_questions_ids).

Upvotes: 0

Rose
Rose

Reputation: 230

You are going to need to add another model to track user interactions with a specific question.

User Questions:

  • belongs_to :user
  • belongs_to :question
  • used (boolean)

User:

  • has_many :user_questions
  • has_many :questions, through: :user_questions

Then you are going to need to run a limited, randomized query:

user_question = User.user_questions.where(used: false).order("RANDOM()").first

This will get you your next question to use. Once it's been answered you will mark it as used so it's not picked up again.

When you regenerate a quiz you would need to reset to false. Alternatively you can use a count... but if your last quiz didn't use all the questions you will need to reset in some way or your counts will be off.

Upvotes: 0

Kimooz
Kimooz

Reputation: 961

IF you don't want to add any database fields, the best way will be using redis

1- Adding all ure questions ids in redis

$redis.sadd 'set_one', Question.pluck(:id)

2- Hitting on a method which selects a random id and removes it from the list

def get_rand_question
 random_id =  $redis.spop "set_one"
 Question.find random_id
end

Upvotes: 0

Related Questions