Отфильтруйте модель ruby ​​по секундам с несколькими условиями - попытка оценки вопроса и без попыток - PullRequest
0 голосов
/ 25 июня 2019

Я создаю механизм вопросов с Ruby, у меня хорошо работает элемент вопроса - пользователи могут создавать и пытаться questions, а их попытки сохраняются в таблице question_attempts.

Сейчас я пытаюсь создать функцию банка вопросов, в которой пользователи могут фильтровать все вопросы по нескольким различным параметрам - тегам, сложности и результату их последней попытки (если она существует). И фильтрация по тегу (через отдельную таблицу tags и таблицу объединения taggings), и сложность (строка из четырех параметров, сохраненных в таблице question) работают должным образом.

У меня возникли проблемы с вопросами фильтра, основанными на попытках пользователя. Мне нужно иметь возможность фильтровать для трех разных условий:

  • Исправить - где question.attempt.score == 100
  • Неверно - где question.attempt.score <100 </li>
  • Не предпринималось попыток - там, где question.attempt == ноль / не существует

Модели 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+ раза, я хочу проверить результат только самой последней. В этом ответе мне хотелось бы использовать решение Марка Свардстрома, в первую очередь потому, что я его понимаю, но обеспокоен, что это будет неэффективный метод.

Спасибо за вашу помощь

Дайте мне знать, если вам нужно больше деталей.


Модель

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

Текущее решение

Вероятно, это не самый эффективный способ сделать это, но, похоже, он работает благодаря советам Тома Лорда. Приветствуются любые конструктивные отзывы / советы.

    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

1 Ответ

0 голосов
/ 26 июня 2019

Вот краткий обзор процесса очистки вашего текущего решения:

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, чтобы переместить большую часть этой логики за пределы контроллера.

Примечание. На самом деле я не запускал этот код, поэтому он может содержать некоторые незначительные ошибки. Пожалуйста, проверьте внимательно.

...