Rails controller for a vocabulary quiz
up vote
2
down vote
favorite
I'm new to refactoring and I have just refactored an old Rails project that had bloated controller methods.
I spun off the bloated methods into its own separate controller following this logic, since the quiz functionality can be considered a separate resource compared to the vocab controller's responsibilities (Single Responsibility Principle).
Please critique my refactored version:
- Would I have been better off using a design pattern (Service object, Concerns) instead of splitting it into a separate controller?
- Should this be refactored further? Smaller methods?
This was the original code app/controllers/vocabs_controller.rb:
class VocabsController < ApplicationController
before_action :require_login, except: [:index, :quiz, :answer, :result]
def index
@vocabs = Vocab.all.order("word")
session[:score] = nil
session[:already_asked] = nil
end
...
def quiz
initiate_score
end
def answer
#Keep score and question id's already asked
if params[:answer] == params[:orig]
session[:score] += 1
session[:already_asked] << params[:answer].to_i
flash[:notice] = "You got it right!"
redirect_to quiz_path
else
session[:already_asked] << params[:orig].to_i
flash[:notice] = "Sorry, wrong answer!"
redirect_to quiz_path
end
end
def initiate_score
#Initiate score session
session[:score] ||= 0
#Initiate session to hold questions already asked
session[:already_asked] ||=
#Total score
session[:amount_questions] = Vocab.all.length - 4
#Get list of words that hasn't been asked before
@left_words = Vocab.all.where.not(id: session[:already_asked])
#Questions remaining
@questions_remaining = @left_words.length - 4
#Pick four words from leftover words list
@four = @left_words.shuffle.take(4)
#Create question variable if there are enough words left in list
if @left_words.length >= 4
@question = @four.first.word
else
redirect_to result_path
end
#save score to user database if all questions done and logged in
if @questions_remaining == 0
high_score = Score.new
high_score.user_id = session[:user_id]
high_score.score = session[:score] / session[:amount_questions].to_f
high_score.save
redirect_to result_path
end
end
private
def vocab_params
params.require(:vocab).permit(:word, :definition)
end
end
The refactored version app/controllers/quizzes_controller.rb:
class QuizzesController < ApplicationController
def start_quiz
clear_session
redirect_to action: "quiz"
end
def quiz
initiate_quiz
remaining_words
if @questions_remaining.zero?
save_score_to_db
redirect_to result_path
end
end
def answer
# Keep track of score and questions already asked
if params[:answer] == params[:orig]
right_answer
else
wrong_answer
end
redirect_to quiz_path
end
private
def initiate_quiz
session[:score] ||= 0
session[:vocab_already_asked] ||=
session[:number_questions_remaining] = Vocab.all.length - 4
end
def remaining_words
@remaining_words = Vocab.all.where.not(id: session[:vocab_already_asked])
@questions_remaining = @remaining_words.length - 4
@quiz_words = @remaining_words.shuffle.take(4)
if @remaining_words.length >= 4
@question = @quiz_words.first.word
else
redirect_to result_path
end
end
def save_score_to_db
high_score = Score.new
high_score.user_id = session[:user_id]
high_score.score = session[:score] / session[:number_questions_remaining].to_f
high_score.save
end
def right_answer
session[:score] += 1
session[:vocab_already_asked] << params[:answer].to_i
flash[:notice] = 'You got it right!'
end
def wrong_answer
session[:vocab_already_asked] << params[:orig].to_i
flash[:notice] = 'Sorry, wrong answer!'
end
def clear_session
session[:score] = 0
session[:vocab_already_asked] =
end
end
ruby ruby-on-rails quiz controller
bumped to the homepage by Community♦ 3 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
up vote
2
down vote
favorite
I'm new to refactoring and I have just refactored an old Rails project that had bloated controller methods.
I spun off the bloated methods into its own separate controller following this logic, since the quiz functionality can be considered a separate resource compared to the vocab controller's responsibilities (Single Responsibility Principle).
Please critique my refactored version:
- Would I have been better off using a design pattern (Service object, Concerns) instead of splitting it into a separate controller?
- Should this be refactored further? Smaller methods?
This was the original code app/controllers/vocabs_controller.rb:
class VocabsController < ApplicationController
before_action :require_login, except: [:index, :quiz, :answer, :result]
def index
@vocabs = Vocab.all.order("word")
session[:score] = nil
session[:already_asked] = nil
end
...
def quiz
initiate_score
end
def answer
#Keep score and question id's already asked
if params[:answer] == params[:orig]
session[:score] += 1
session[:already_asked] << params[:answer].to_i
flash[:notice] = "You got it right!"
redirect_to quiz_path
else
session[:already_asked] << params[:orig].to_i
flash[:notice] = "Sorry, wrong answer!"
redirect_to quiz_path
end
end
def initiate_score
#Initiate score session
session[:score] ||= 0
#Initiate session to hold questions already asked
session[:already_asked] ||=
#Total score
session[:amount_questions] = Vocab.all.length - 4
#Get list of words that hasn't been asked before
@left_words = Vocab.all.where.not(id: session[:already_asked])
#Questions remaining
@questions_remaining = @left_words.length - 4
#Pick four words from leftover words list
@four = @left_words.shuffle.take(4)
#Create question variable if there are enough words left in list
if @left_words.length >= 4
@question = @four.first.word
else
redirect_to result_path
end
#save score to user database if all questions done and logged in
if @questions_remaining == 0
high_score = Score.new
high_score.user_id = session[:user_id]
high_score.score = session[:score] / session[:amount_questions].to_f
high_score.save
redirect_to result_path
end
end
private
def vocab_params
params.require(:vocab).permit(:word, :definition)
end
end
The refactored version app/controllers/quizzes_controller.rb:
class QuizzesController < ApplicationController
def start_quiz
clear_session
redirect_to action: "quiz"
end
def quiz
initiate_quiz
remaining_words
if @questions_remaining.zero?
save_score_to_db
redirect_to result_path
end
end
def answer
# Keep track of score and questions already asked
if params[:answer] == params[:orig]
right_answer
else
wrong_answer
end
redirect_to quiz_path
end
private
def initiate_quiz
session[:score] ||= 0
session[:vocab_already_asked] ||=
session[:number_questions_remaining] = Vocab.all.length - 4
end
def remaining_words
@remaining_words = Vocab.all.where.not(id: session[:vocab_already_asked])
@questions_remaining = @remaining_words.length - 4
@quiz_words = @remaining_words.shuffle.take(4)
if @remaining_words.length >= 4
@question = @quiz_words.first.word
else
redirect_to result_path
end
end
def save_score_to_db
high_score = Score.new
high_score.user_id = session[:user_id]
high_score.score = session[:score] / session[:number_questions_remaining].to_f
high_score.save
end
def right_answer
session[:score] += 1
session[:vocab_already_asked] << params[:answer].to_i
flash[:notice] = 'You got it right!'
end
def wrong_answer
session[:vocab_already_asked] << params[:orig].to_i
flash[:notice] = 'Sorry, wrong answer!'
end
def clear_session
session[:score] = 0
session[:vocab_already_asked] =
end
end
ruby ruby-on-rails quiz controller
bumped to the homepage by Community♦ 3 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
I'm new to refactoring and I have just refactored an old Rails project that had bloated controller methods.
I spun off the bloated methods into its own separate controller following this logic, since the quiz functionality can be considered a separate resource compared to the vocab controller's responsibilities (Single Responsibility Principle).
Please critique my refactored version:
- Would I have been better off using a design pattern (Service object, Concerns) instead of splitting it into a separate controller?
- Should this be refactored further? Smaller methods?
This was the original code app/controllers/vocabs_controller.rb:
class VocabsController < ApplicationController
before_action :require_login, except: [:index, :quiz, :answer, :result]
def index
@vocabs = Vocab.all.order("word")
session[:score] = nil
session[:already_asked] = nil
end
...
def quiz
initiate_score
end
def answer
#Keep score and question id's already asked
if params[:answer] == params[:orig]
session[:score] += 1
session[:already_asked] << params[:answer].to_i
flash[:notice] = "You got it right!"
redirect_to quiz_path
else
session[:already_asked] << params[:orig].to_i
flash[:notice] = "Sorry, wrong answer!"
redirect_to quiz_path
end
end
def initiate_score
#Initiate score session
session[:score] ||= 0
#Initiate session to hold questions already asked
session[:already_asked] ||=
#Total score
session[:amount_questions] = Vocab.all.length - 4
#Get list of words that hasn't been asked before
@left_words = Vocab.all.where.not(id: session[:already_asked])
#Questions remaining
@questions_remaining = @left_words.length - 4
#Pick four words from leftover words list
@four = @left_words.shuffle.take(4)
#Create question variable if there are enough words left in list
if @left_words.length >= 4
@question = @four.first.word
else
redirect_to result_path
end
#save score to user database if all questions done and logged in
if @questions_remaining == 0
high_score = Score.new
high_score.user_id = session[:user_id]
high_score.score = session[:score] / session[:amount_questions].to_f
high_score.save
redirect_to result_path
end
end
private
def vocab_params
params.require(:vocab).permit(:word, :definition)
end
end
The refactored version app/controllers/quizzes_controller.rb:
class QuizzesController < ApplicationController
def start_quiz
clear_session
redirect_to action: "quiz"
end
def quiz
initiate_quiz
remaining_words
if @questions_remaining.zero?
save_score_to_db
redirect_to result_path
end
end
def answer
# Keep track of score and questions already asked
if params[:answer] == params[:orig]
right_answer
else
wrong_answer
end
redirect_to quiz_path
end
private
def initiate_quiz
session[:score] ||= 0
session[:vocab_already_asked] ||=
session[:number_questions_remaining] = Vocab.all.length - 4
end
def remaining_words
@remaining_words = Vocab.all.where.not(id: session[:vocab_already_asked])
@questions_remaining = @remaining_words.length - 4
@quiz_words = @remaining_words.shuffle.take(4)
if @remaining_words.length >= 4
@question = @quiz_words.first.word
else
redirect_to result_path
end
end
def save_score_to_db
high_score = Score.new
high_score.user_id = session[:user_id]
high_score.score = session[:score] / session[:number_questions_remaining].to_f
high_score.save
end
def right_answer
session[:score] += 1
session[:vocab_already_asked] << params[:answer].to_i
flash[:notice] = 'You got it right!'
end
def wrong_answer
session[:vocab_already_asked] << params[:orig].to_i
flash[:notice] = 'Sorry, wrong answer!'
end
def clear_session
session[:score] = 0
session[:vocab_already_asked] =
end
end
ruby ruby-on-rails quiz controller
I'm new to refactoring and I have just refactored an old Rails project that had bloated controller methods.
I spun off the bloated methods into its own separate controller following this logic, since the quiz functionality can be considered a separate resource compared to the vocab controller's responsibilities (Single Responsibility Principle).
Please critique my refactored version:
- Would I have been better off using a design pattern (Service object, Concerns) instead of splitting it into a separate controller?
- Should this be refactored further? Smaller methods?
This was the original code app/controllers/vocabs_controller.rb:
class VocabsController < ApplicationController
before_action :require_login, except: [:index, :quiz, :answer, :result]
def index
@vocabs = Vocab.all.order("word")
session[:score] = nil
session[:already_asked] = nil
end
...
def quiz
initiate_score
end
def answer
#Keep score and question id's already asked
if params[:answer] == params[:orig]
session[:score] += 1
session[:already_asked] << params[:answer].to_i
flash[:notice] = "You got it right!"
redirect_to quiz_path
else
session[:already_asked] << params[:orig].to_i
flash[:notice] = "Sorry, wrong answer!"
redirect_to quiz_path
end
end
def initiate_score
#Initiate score session
session[:score] ||= 0
#Initiate session to hold questions already asked
session[:already_asked] ||=
#Total score
session[:amount_questions] = Vocab.all.length - 4
#Get list of words that hasn't been asked before
@left_words = Vocab.all.where.not(id: session[:already_asked])
#Questions remaining
@questions_remaining = @left_words.length - 4
#Pick four words from leftover words list
@four = @left_words.shuffle.take(4)
#Create question variable if there are enough words left in list
if @left_words.length >= 4
@question = @four.first.word
else
redirect_to result_path
end
#save score to user database if all questions done and logged in
if @questions_remaining == 0
high_score = Score.new
high_score.user_id = session[:user_id]
high_score.score = session[:score] / session[:amount_questions].to_f
high_score.save
redirect_to result_path
end
end
private
def vocab_params
params.require(:vocab).permit(:word, :definition)
end
end
The refactored version app/controllers/quizzes_controller.rb:
class QuizzesController < ApplicationController
def start_quiz
clear_session
redirect_to action: "quiz"
end
def quiz
initiate_quiz
remaining_words
if @questions_remaining.zero?
save_score_to_db
redirect_to result_path
end
end
def answer
# Keep track of score and questions already asked
if params[:answer] == params[:orig]
right_answer
else
wrong_answer
end
redirect_to quiz_path
end
private
def initiate_quiz
session[:score] ||= 0
session[:vocab_already_asked] ||=
session[:number_questions_remaining] = Vocab.all.length - 4
end
def remaining_words
@remaining_words = Vocab.all.where.not(id: session[:vocab_already_asked])
@questions_remaining = @remaining_words.length - 4
@quiz_words = @remaining_words.shuffle.take(4)
if @remaining_words.length >= 4
@question = @quiz_words.first.word
else
redirect_to result_path
end
end
def save_score_to_db
high_score = Score.new
high_score.user_id = session[:user_id]
high_score.score = session[:score] / session[:number_questions_remaining].to_f
high_score.save
end
def right_answer
session[:score] += 1
session[:vocab_already_asked] << params[:answer].to_i
flash[:notice] = 'You got it right!'
end
def wrong_answer
session[:vocab_already_asked] << params[:orig].to_i
flash[:notice] = 'Sorry, wrong answer!'
end
def clear_session
session[:score] = 0
session[:vocab_already_asked] =
end
end
ruby ruby-on-rails quiz controller
ruby ruby-on-rails quiz controller
edited Jun 4 at 22:30
200_success
127k15148412
127k15148412
asked Jun 3 at 20:39
MLZ
112
112
bumped to the homepage by Community♦ 3 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
bumped to the homepage by Community♦ 3 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
up vote
0
down vote
What is the point of
clear_sessionit is never called without also callinginitiate_quizyet repeats the same code.I would define a constant for the number of questions (or pass it as a parameter) rather than repeating it.
Usually I expect a method with a noun name like
remaining_wordsto return the remaining words. Since this method does other things I would call it something else maybeget_wordsorinitialize_wordsI also avoid doing redirects inside methods that are doing calculations. i.e. I would move the redirect out of
remaining_wordssave_score_to_dbcould be turned into a single line usingScore.create(...)
add a comment |
up vote
0
down vote
1) Single Responsibility Principle is very important but checkout other principles too: SOLID
2) Not a fan of concerns, so wouldn't recommend them. There are very good posts about their downsides (just Google it).
3) Design patterns are useful. However, I wouldn't recommend starting with them. If you have duplications in your controllers/models (or high complexity), then you can think about implementing them. Check out 7 Patterns to Refactor Fat ActiveRecord Models' Form Objects section.
4) If most part of your application requires signing-in, move require_login before hook to ApplicationController and skip it when necessary.
5) Most importantly, make your controllers CRUD. Example:
class AnswersController < ApplicationController
def create
# Keep track of score and questions already asked
if params[:answer] == params[:orig]
right_answer
else
wrong_answer
end
redirect_to quiz_path
end
end
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
What is the point of
clear_sessionit is never called without also callinginitiate_quizyet repeats the same code.I would define a constant for the number of questions (or pass it as a parameter) rather than repeating it.
Usually I expect a method with a noun name like
remaining_wordsto return the remaining words. Since this method does other things I would call it something else maybeget_wordsorinitialize_wordsI also avoid doing redirects inside methods that are doing calculations. i.e. I would move the redirect out of
remaining_wordssave_score_to_dbcould be turned into a single line usingScore.create(...)
add a comment |
up vote
0
down vote
What is the point of
clear_sessionit is never called without also callinginitiate_quizyet repeats the same code.I would define a constant for the number of questions (or pass it as a parameter) rather than repeating it.
Usually I expect a method with a noun name like
remaining_wordsto return the remaining words. Since this method does other things I would call it something else maybeget_wordsorinitialize_wordsI also avoid doing redirects inside methods that are doing calculations. i.e. I would move the redirect out of
remaining_wordssave_score_to_dbcould be turned into a single line usingScore.create(...)
add a comment |
up vote
0
down vote
up vote
0
down vote
What is the point of
clear_sessionit is never called without also callinginitiate_quizyet repeats the same code.I would define a constant for the number of questions (or pass it as a parameter) rather than repeating it.
Usually I expect a method with a noun name like
remaining_wordsto return the remaining words. Since this method does other things I would call it something else maybeget_wordsorinitialize_wordsI also avoid doing redirects inside methods that are doing calculations. i.e. I would move the redirect out of
remaining_wordssave_score_to_dbcould be turned into a single line usingScore.create(...)
What is the point of
clear_sessionit is never called without also callinginitiate_quizyet repeats the same code.I would define a constant for the number of questions (or pass it as a parameter) rather than repeating it.
Usually I expect a method with a noun name like
remaining_wordsto return the remaining words. Since this method does other things I would call it something else maybeget_wordsorinitialize_wordsI also avoid doing redirects inside methods that are doing calculations. i.e. I would move the redirect out of
remaining_wordssave_score_to_dbcould be turned into a single line usingScore.create(...)
answered Jun 4 at 15:40
Marc Rohloff
2,95236
2,95236
add a comment |
add a comment |
up vote
0
down vote
1) Single Responsibility Principle is very important but checkout other principles too: SOLID
2) Not a fan of concerns, so wouldn't recommend them. There are very good posts about their downsides (just Google it).
3) Design patterns are useful. However, I wouldn't recommend starting with them. If you have duplications in your controllers/models (or high complexity), then you can think about implementing them. Check out 7 Patterns to Refactor Fat ActiveRecord Models' Form Objects section.
4) If most part of your application requires signing-in, move require_login before hook to ApplicationController and skip it when necessary.
5) Most importantly, make your controllers CRUD. Example:
class AnswersController < ApplicationController
def create
# Keep track of score and questions already asked
if params[:answer] == params[:orig]
right_answer
else
wrong_answer
end
redirect_to quiz_path
end
end
add a comment |
up vote
0
down vote
1) Single Responsibility Principle is very important but checkout other principles too: SOLID
2) Not a fan of concerns, so wouldn't recommend them. There are very good posts about their downsides (just Google it).
3) Design patterns are useful. However, I wouldn't recommend starting with them. If you have duplications in your controllers/models (or high complexity), then you can think about implementing them. Check out 7 Patterns to Refactor Fat ActiveRecord Models' Form Objects section.
4) If most part of your application requires signing-in, move require_login before hook to ApplicationController and skip it when necessary.
5) Most importantly, make your controllers CRUD. Example:
class AnswersController < ApplicationController
def create
# Keep track of score and questions already asked
if params[:answer] == params[:orig]
right_answer
else
wrong_answer
end
redirect_to quiz_path
end
end
add a comment |
up vote
0
down vote
up vote
0
down vote
1) Single Responsibility Principle is very important but checkout other principles too: SOLID
2) Not a fan of concerns, so wouldn't recommend them. There are very good posts about their downsides (just Google it).
3) Design patterns are useful. However, I wouldn't recommend starting with them. If you have duplications in your controllers/models (or high complexity), then you can think about implementing them. Check out 7 Patterns to Refactor Fat ActiveRecord Models' Form Objects section.
4) If most part of your application requires signing-in, move require_login before hook to ApplicationController and skip it when necessary.
5) Most importantly, make your controllers CRUD. Example:
class AnswersController < ApplicationController
def create
# Keep track of score and questions already asked
if params[:answer] == params[:orig]
right_answer
else
wrong_answer
end
redirect_to quiz_path
end
end
1) Single Responsibility Principle is very important but checkout other principles too: SOLID
2) Not a fan of concerns, so wouldn't recommend them. There are very good posts about their downsides (just Google it).
3) Design patterns are useful. However, I wouldn't recommend starting with them. If you have duplications in your controllers/models (or high complexity), then you can think about implementing them. Check out 7 Patterns to Refactor Fat ActiveRecord Models' Form Objects section.
4) If most part of your application requires signing-in, move require_login before hook to ApplicationController and skip it when necessary.
5) Most importantly, make your controllers CRUD. Example:
class AnswersController < ApplicationController
def create
# Keep track of score and questions already asked
if params[:answer] == params[:orig]
right_answer
else
wrong_answer
end
redirect_to quiz_path
end
end
answered Jun 4 at 16:14
ogirginc
1464
1464
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195764%2frails-controller-for-a-vocabulary-quiz%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown