【问题标题】:Filter ruby model by second with multiple conditions - question attempt grades and unattempted使用多个条件按秒过滤 ruby​​ 模型 - 问题尝试等级和未尝试
【发布时间】: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: userbelongs_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 &gt;= ? AND question_attempts.score &lt;= ?", ...) 之类的逻辑定义一个范围 - 所以你可以将0, 99 作为参数传递或100, 100 作为参数传递。但根据您提供的信息,这样的范围可能有点过头了。
  • 您可以考虑做的另一件事是 - 根据您的 UI 设计/要求 - 考虑重构整个模型:QuizQuestions 的集合。而QuizAttemptQuestionAnswers 的集合(可以跳过吗?)。显示分数时,请考虑每个QuestionAnswer QuizAttempt 关联的值。这为“最新答案”和“总分”提供了一个更简单、更有意义的定义。
  • 感谢汤姆的提示。不幸的是,我认为重组模型不会奏效,因为可以在不进行测验的情况下提出问题。通过结合我的意思正是你提到的 - 用户可能想要他们过去正确和错误回答的问题 - 即你对.where("question_attempts.score &gt;= ? AND question_attempts.score &lt;= ?", ...)的建议。我希望避免创建一系列if else 检查,但也许我可以使用OR 语句创建一系列我可以.join 的字符串。我会玩弄你推荐的东西。
  • 如果您想要“已回答的问题”而不是“未回答的问题”的第四个范围,那么简单的 INNER JOIN 就足够了。 (与外连接相反,看到了吗?)像我建议的范围也可以工作 - 但虽然它节省了几行代码,但它增加了对score 的不必要检查。但无论哪种方式,这里都不需要任何if 语句。

标签: ruby-on-rails ruby join activerecord where-clause


【解决方案1】:

以下是清理当前解决方案的快速方法:

def tags
  params[:topics]&.split(",")
end

def difficulties
  params[:difficulties]&.split(",")
end

# ...

questions = Question.joins(:question_attempts).where(
    question_attempts: { user_id: current_user.id },
    private: false,
    draft: false,
    approved: true
  )

# If both are true, no need to scope query
if params[:correct] == "true" && params[:incorrect] != "true"
  questions = questions.where('question_attempts.score = 100')
elsif params[:incorrect] == "true"
  questions = questions.where('question_attempts.score < 100')
end

questions = questions.where(difficulty: difficulties) if difficulties

questions = questions.joins(:tags).where(tags: { name: tags }) if tags

if params[:unattempted] == "true"
  questions = questions.or(
    Question.includes(:question_attempts).where(question_attempts: { user_id: nil})
  )
end

questions.distinct

我的版本和你的版本之间的主要区别在于我在逻辑组件中编写查询,而不是尝试一次性编写整个内容。

这减少了整体长度和复杂性,同时保持相同的结果。

您可以通过在 Question 模型上定义 范围 来进一步简化此操作,将大部分逻辑移到控制器之外。

注意:我实际上并没有运行此代码,因此它可能包含一些小错误。请仔细测试。

【讨论】:

  • 谢谢,这样肯定好多了。我调整了 params[incorrect] 的逻辑,以确保 params[correct] 为假(否则当它们都为真时它匹配)。唯一的问题是我收到错误ArgumentError (Relation passed to #or must be structurally compatible. Incompatible values: [:joins]):。我已经做了一些不同的事情,但仍然突然出现 - 我认为以下内容会起作用(因为我看不到任何加入!),当不在 .or() - Question.where.not( id: QuestionAttempt.where( user_id: current_user.id ).pluck(:question_id) ) 内时它会起作用
  • 最后修复了它,它不像你的解决方案那么优雅,它比我的原始解决方案优雅得多。非常感谢您的帮助!
  • 我更新了我的问题以反映我现在在做什么。在使用.pluck() 获取相关ID 之后,基本上是一大堆.where 语句。再次感谢您的帮助。
猜你喜欢
  • 2018-05-22
  • 2017-09-22
  • 1970-01-01
  • 1970-01-01
  • 2013-09-13
  • 1970-01-01
  • 1970-01-01
  • 2014-08-11
  • 1970-01-01
相关资源
最近更新 更多