【发布时间】:2019-11-07 13:06:12
【问题描述】:
我正在使用 Ruby 构建一个问题引擎,我的问题元素运行良好 - 用户可以创建和尝试 questions,他们的尝试保存在 question_attempts 表中。
我现在正在尝试构建一个问题库功能,用户可以通过几个不同的选项过滤所有问题 - 标签、难度和他们上次尝试的结果(如果存在)。按标签过滤(通过单独的tags 表和连接taggings 表)和难度(保存在question 表中的四个选项的字符串)都按预期工作。
我在根据用户的尝试过滤问题时遇到了一些问题。我需要能够过滤三种不同的条件:
- 正确 - 其中 question.attempt.score == 100
- 不正确 - 其中 question.attempt.score
- 未尝试 - 其中 question.attempt == nil / 不存在
question_attempts 模型 belongs_to: user 和 belongs_to: question。他们尝试的score 被保存为标准化的百分比浮点数,最大值为 100.0。
对于前两个,我可以使用与标签类似的方法,这两个都有效:
.where(question_attempts: { score: 100, user_id: current_user.id }
.where.not(question_attempts: { score: 100 }).where(user_id: current_user.id })
我可以使用以下行获取单个用户尚未提交回复的问题:
.where.not(id: QuestionAttempt.where( user_id: current_user.id ).select( "question_id" ) )
我的问题是:
-
如何组合这些过滤器选项? 我试图避免编写一长串
if else语句,但如果需要,我会这样做(我可以在没有帮助的情况下这样做,只是想检查是否有在破解之前不是更好的方法) - 如何将结果检查限制在他们最后一次尝试?即如果他们提交了 2 次以上的问题回复,我只想检查最近一次的结果。我很想在answer 中使用 Mark Swardstrom 的解决方案,主要是因为我理解它,但我担心这会是一种低效的方法。
感谢您的帮助
如果您需要更多详细信息,请告诉我。
模型
class Question < ApplicationRecord
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
has_many :question_attempts, dependent: :destroy
end
class QuestionAttempt < ApplicationRecord
belongs_to :question
belongs_to :user
end
控制器
# Gather topics / tags
# This is working
if params[:topics] != nil
@tags = params[:topics].split(",")
end
# Gather difficulties
# This is working as expected
# If nil, then show all difficulties
if params[:difficulties] != nil
@difficulties = params[:difficulties].split(",")
else
flash[:notice] = 'Showing all difficulties as none selected'
@difficulties = ["Untested", "Easy", "Medium", "Hard"]
end
# This is the element not yet working
# I can gather the options Unattempted / Correct / Incorrect from the params
# Not sure how to then join this with my question_attempts table
@attempts = params[:attempts].split(",")
# Gather questions matching the filters
if @tags == nil
@questions = Question.joins(:question_attempts).where(difficulty: @difficulties, private: false, draft: false, approved: true).distinct
else
@questions = Question.joins(:question_attempts).joins(:tags).where(tags: { name: @tags }, difficulty: @difficulties, private: false, draft: false, approved: true).distinct
end
当前解决方案
这可能不是最有效的方法,但多亏了 Tom Lord 的提示,它似乎很有效。欢迎任何建设性的反馈/提示。
def tags
params[:topics]&.split(",")
end
def difficulties
params[:difficulties]&.split(",")
end
def filter
# Check for previous answers
if params[:correct] == "true" && params[:incorrect] != "true"
@questions = Question.where( id: QuestionAttempt.where(user_id: current_user.id).where('score = 100').pluck(:question_id) )
elsif params[:incorrect] == "true" && params[:correct] != "true"
@questions = Question.where( id: QuestionAttempt.where(user_id: current_user.id).where('score < 100').pluck(:question_id) )
elsif params[:incorrect] == "true" && params[:correct] == "true"
@questions = Question.where( id: QuestionAttempt.where(user_id: current_user.id).pluck(:question_id) )
end
# Check for unanswered filters
if params[:unattempted] == "true"
if @questions == nil
@questions = Question.where.not( id: QuestionAttempt.where( user_id: current_user.id ).pluck(:question_id) )
else
@questions = Question.where.not( id: QuestionAttempt.where( user_id: current_user.id ).pluck(:question_id) ).or (
@questions
)
end
end
# Add all other filters afterwards as they should apply to all
@questions = @questions.where(difficulty: difficulties) if difficulties
@questions = @questions.joins(:tags).where(tags: { name: tags }) if tags
@questions = @questions.where(
private: false,
draft: false,
approved: true
)
@questions.distinct
【问题讨论】:
-
要查看他们的“最新答案”,您可以考虑使用
order(:created_at).limit(1)。要执行第三次检查,在用户没有提交尝试的情况下,执行 OUTER JOIN 比通过这样的 ID 查询要高效得多。从字面上看,这就是外连接的设计目的;现代 Rails 有一个内置的方法。 -
我真的看不到“组合”这些查询的方法(这甚至意味着什么??);我唯一能做的就是用
.where("question_attempts.score >= ? AND question_attempts.score <= ?", ...)之类的逻辑定义一个范围 - 所以你可以将0, 99作为参数传递或100, 100作为参数传递。但根据您提供的信息,这样的范围可能有点过头了。 -
您可以考虑做的另一件事是 - 根据您的 UI 设计/要求 - 考虑重构整个模型:
Quiz是Questions 的集合。而QuizAttempt是QuestionAnswers 的集合(可以跳过吗?)。显示分数时,请考虑每个QuestionAnswer与QuizAttempt关联的值。这为“最新答案”和“总分”提供了一个更简单、更有意义的定义。 -
感谢汤姆的提示。不幸的是,我认为重组模型不会奏效,因为可以在不进行测验的情况下提出问题。通过结合我的意思正是你提到的 - 用户可能想要他们过去正确和错误回答的问题 - 即你对
.where("question_attempts.score >= ? AND question_attempts.score <= ?", ...)的建议。我希望避免创建一系列if else检查,但也许我可以使用OR语句创建一系列我可以.join的字符串。我会玩弄你推荐的东西。 -
如果您想要“已回答的问题”而不是“未回答的问题”的第四个范围,那么简单的 INNER JOIN 就足够了。 (与外连接相反,看到了吗?)像我建议的范围也可以工作 - 但虽然它节省了几行代码,但它增加了对
score的不必要检查。但无论哪种方式,这里都不需要任何if语句。
标签: ruby-on-rails ruby join activerecord where-clause