现在我正在制作一个Web应用程序(在线单词学习),允许用户选择单词的正确含义。当他们点击开始时,它将从数据库中随机选择一个单词并显示给用户。用户选择答案后,将转到下一个问题。 请看下面的图片:
如果我使用Word.order(“rand()”)。limit(1),我想这个单词是否可以用最后选择的单词重复? 使用上图中的应用程序,有什么更好的想法来解决这个问题吗?
答案 0 :(得分:1)
我会将以下范围添加到模型中(取决于您使用的数据库):
# in app/models/word.rb
# 'RANDOM' works with postgresql and sqlite, whereas mysql uses 'RAND'
scope :random, -> { order('RAND()') }
scope :without, ->(ids) { where.not(id: ids) }
使用该范围,您可以在控制器中编写以下查询:
@word = Word.random.without(params[:last_ids]).limit(1)
如果要在视图中加载新的随机元素,只需将当前单词的ID添加到请求中即可。这可以确保不会随机选择此ID(params[:last_ids]
)。
答案 1 :(得分:0)
长话短说,为了不重复自己,你必须把这些话存放在某个地方。要么显示的那些,要么已经显示的那些。如果我是你,我会选择以下路线之一:
在开始测验之前获取所有单词并随机化它们。这可能是这样的:
session[:words] = Word.order("RAND()").select(:id).take(10)
甚至可以通过为随机单词定义范围来实现更好:
class Word < ActiveRecord::Base
# ...
scope :random_quiz, -> { order("RAND()").take(10).pluck(:id) }
# ...
end
# ... in the controller when the quiz is getting started:
session[:words] = Word.random_quiz
# ... in the controller when you want to show the word:
new_word = Word.find(sessions[:words].pop)
由于ORDER BY RAND()
是一项非常昂贵的操作,这可能是有道理的。然后,您只需使用session[:words].pop
逐个弹出单词ID,然后提出问题。
这样可以保证您不会重复测验中的单词并为您提供非常佳的表现。
在您提出问题并保存您已经询问过的问题时,逐步取词。
class Word < ActiveRecord::Base
# ...
def self.random_word(exclusions)
eligible = where('id NOT IN (?)', exclusions)
eligible.offset(rand(0..eligible.count)).take!(1)
end
# ..
end
# ... in the controller when you need a new word:
session[:words_shown] ||= [ ]
new_word = Word.random_word(session[:words_shown])
# mark the word as shown:
session[:words_shown].push(new_word.id)
您可能已经注意到在第二个示例中获取随机记录的奇怪方式。事实证明它生成以下查询更有效:
SELECT * FROM words OFFSET _random_number_ LIMIT 1
而不是:
SELECT * FROM words ORDER BY RAND() LIMIT 1
第一个只是一个普通的选择,而第二个需要通过整个表的RAND()
进行无索引排序,然后才能给出随机结果。事实证明,前者几乎比后者快十倍。
希望有意义!