CSC/ECE 517 Spring 2015/oss E1509 lds: Difference between revisions
| Line 229: | Line 229: | ||
end | end | ||
</pre> | </pre> | ||
The method <b>update_topic_info</b> updates topics' some attributes according to received parameters, such as permitted max number of choosers and waitlisted users. | |||
<pre> | <pre> | ||
def check_after_create | def check_after_create | ||
Revision as of 20:09, 21 March 2015
E1509. Refactoring SignUpController and SignUpSheetController
This page talks about an open source project based on Expertiza. As a part of contribution to Expertiza, this project aims to refactor SignUpController and SignUpSheetControllers.
Introduction to Expertiza
Expertiza is a web application developed by Ruby on Rails framework. It serves as a peer review system for professors and students at NC State and some other colleges and universities. Students can submit different assignments and peer-review reusable learning objects (articles, code, web sites, etc)<ref>https://github.com/expertiza/expertiza</ref>. The Expertiza project is supported by the National Science Foundation. And it is an open source application and the source code can be cloned on Github .
Problem Statement
Classes involved
* sign_up_sheet.rb * sign_up_topic.rb * assignments_controller.rb * response.rb * and possibly other model classes
What it does
Lists topics available for an assignment, checks whether a user is signed up for a topic, allows users to sign up for topics.
What's wrong with it
- These two controllers seem to do almost the same thing. They have many of the same methods. SignUpSheetController is much longer, and has many more recent mods, but some methods of SignUpController seem more sophisticated than SignUpSheetController. So, your first job is to figure out if both controllers are being used. If not, remove the unused controller. Or, move the functions to a single controller if that makes sense to do.
- Neither controller is at all RESTful; i.e.., its method names aren’t the standard names new, create, edit, delete, etc. Functionality is divided differently than in a standard controller.
a. def confirm_topic(creator_id, topic_id, assignment_id)
b. def delete_signup
c. def delete_signup_for_topic(assignment_id,topic_id)
d. def other_confirmed_topic_for_user(assignment_id, creator_id)
e. def signup
f. def slotAvailable?(topic_id)
- Functionality that should be in models is incorporated into the controller.
- Some methods are too long and appear to do more than one thing
- This class interfaces with assignments_controller so that a list of topics can be displayed when one is editing an assignment. Please be careful that your changes do not affect the functionality on the Topics tab of editing an assignment.
- Rename the controller(s) to SignupController and/or SignupSheetController. (“Sign up”, which gets written as SignUp in camel case, is a verb, whereas “Signup” is a noun.)
Modification We Made
Removing unused controller
RESTful style implementation
The purpose of the list method in the SignupSheet controller is to list all the topics in the specific assignments. According to RESTful rules, a method that returns a list of all available objects should be named as index. Therefore, we renamed the list method to the index method in the controller and in all the files that had references to the list method of the signup_sheet_controller.
Before Refactoring :
Method : list
def list
@assignment_id = params[:id]
@sign_up_topics = SignUpTopic.where( ['assignment_id = ?', params[:id]]).all
@slots_filled = SignUpTopic.find_slots_filled(params[:id])
@slots_waitlisted = SignUpTopic.find_slots_waitlisted(params[:id])
@show_actions = true
@priority = 0
assignment=Assignment.find(params[:id])
if assignment.due_dates.find_by_deadline_type_id(1)!= nil
unless !(assignment.staggered_deadline? and assignment.due_dates.find_by_deadline_type_id(1).due_at < Time.now )
@show_actions = false
end
users_team = SignedUpUser.find_team_users(params[:id],(session[:user].id))
if users_team.size == 0
@selected_topics = nil
else
@selected_topics = SignUpSheetController.other_confirmed_topic_for_user(params[:id], users_team[0].t_id)
end
SignUpTopic.remove_team(users_team, @assignment_id)
end
end
After Refactoring :
Method : index
def index
@assignment_id = params[:id]
@sign_up_topics =SignUpTopic.find_with_assignment_id( params[:id]).all
filled_and_waitlisted_topics(@assignment_id)
@show_actions = true
@priority = 0
assignment=Assignment.find(params[:id])
if assignment.due_dates.find_by_deadline_type_id(1)!= nil
unless !(assignment.staggered_deadline? and assignment.due_dates.find_by_deadline_type_id(1).due_at < Time.now )
@show_actions = false
end
users_team = SignedUpUser.find_team_users(params[:id],(session[:user].id))
if users_team.size == 0
@selected_topics = nil
else
@selected_topics = SignupSheetController.other_confirmed_topic_for_user(params[:id], users_team[0].t_id)
end
SignUpTopic.remove_team(users_team, @assignment_id)
end
end
For some others, it is not good to combine some functions with the standard ones. For example, create function creates topics, which is one of the administrator’s functions. However signup creates a record that includes information for topic and student, which happens when student clicks signup button. We can see that create and signup are designed for different role and different usage so it is not a good idea to combine them together.
Moving inappropriate functionality from controller to model
The controller connects the model with the view. In Rails, controllers are implemented as ActionController classes. The controller knows how to process the data that comes from the model and how to pass it onto the view. The controller should not include any database related actions (such as modifying data before it gets saved inside the database). This should be handled in the proper model. So we remove the database related functionality in the signup_sheet_controller to related models. The following files were modified:
app/controllers/signup_sheet_controller.rb app/models/deadline_type.rb app/models/due_date.rb app/models/participant.rb app/models/sign_up_topic.rb app/models/signed_up_user.rb app/models/topic_deadline.rb app/models/topic_dependency.rb
Here are two examples on how these files changed.
1. replace "where" in the controller with a new method in related model.
Before Refactoring
In signup_sheet_controller.rb:
def create
puts "2222222 call create"
topic = SignUpTopic.where(topic_name: params[:topic][:topic_name], assignment_id: params[:id]).first
#if the topic already exists then update
if topic
update_topic_info(topic)
After Refactoring In signup_sheet_controller.rb:
def create
puts "2222222 call create"
topic = SignUpTopic.find_with_name_and_assignment_id(params[:topic][:topic_name], params[:id]).first
#if the topic already exists then update
if topic
update_topic_info(topic)
Also add a related method in sign_up_topic.rb
def find_with_name_and_assignment_id(name, aid)
SignUpTopic.where(topic_name: name, assignment_id: aid)
end
2. replace "find_by_sql" in the controller with a new method in related model.
Before Refactoring
In signup_sheet_controller.rb:
def set_duedate_info
@review_rounds = Assignment.find(params[:id]).get_review_rounds
@topics = SignUpTopic.where(assignment_id: params[:id])
#Use this until you figure out how to initialize this array
@duedates = SignUpTopic.find_by_sql("SELECT s.id as topic_id FROM sign_up_topics s WHERE s.assignment_id = " + params[:id].to_s)
unless @topics.nil?
After Refactoring In signup_sheet_controller.rb:
def set_duedate_info
@review_rounds = Assignment.find(params[:id]).get_review_rounds
@topics = SignUpTopic.find_with_assignment_id( params[:id])
#Use this until you figure out how to initialize this array
@duedates = SignUpTopic.find_topic_id_with_assignment_id(params[:id].to_s)
unless @topics.nil?
Also add a related method in sign_up_topic.rb
def find_topic_id_with_assignment_id(aid)
SignUpTopic.find_by_sql("SELECT s.id as topic_id FROM sign_up_topics s WHERE s.assignment_id = " + aid)
end
Breaking down long methods
We’ve find that lots of methods do more than one thing so we separate a long function into several concise functions and call them in the original function. Here is an example:
There are some preparation works and check works before or after the actual create operation. So we separate two functions “update_topic_info” and “check_after_create” from “create”. And we call these two functions in “create” method instead of executing these codes directly.
Before Refactoring :
Method : create
def create
topic = SignUpTopic.where(topic_name: params[:topic][:topic_name], assignment_id: params[:id]).first
if topic != nil
topic.topic_identifier = params[:topic][:topic_identifier]
if SignedUpUser.find_by_topic_id(topic.id).nil? || topic.max_choosers == params[:topic][:max_choosers]
topic.max_choosers = params[:topic][:max_choosers]
else
if topic.max_choosers.to_i < params[:topic][:max_choosers].to_i
topic.update_waitlisted_users(params[:topic][:max_choosers])
topic.max_choosers = params[:topic][:max_choosers]
else
flash[:error] = 'Value of maximum choosers can only be increased! No change has been made to max choosers.'
end
end
topic.category = params[:topic][:category]
topic.save
redirect_to_sign_up(params[:id])
else
set_values_for_new_topic
if @assignment.is_microtask?
@sign_up_topic.micropayment = params[:topic][:micropayment]
end
if @assignment.staggered_deadline?
topic_set = Array.new
topic = @sign_up_topic.id
end
if @sign_up_topic.save
undo_link("Topic: \"#{@sign_up_topic.topic_name}\" has been created successfully. ")
redirect_to edit_assignment_path(@sign_up_topic.assignment_id) + "#tabs-5"
else
render :action => 'new', :id => params[:id]
end
end
end
After Refactoring
Method : update_topic_info, check_after_create, create
def update_topic_info(topic)
topic.topic_identifier = params[:topic][:topic_identifier]
if SignedUpUser.find_with_topic_id(topic.id).nil? || topic.max_choosers == params[:topic][:max_choosers]
topic.max_choosers = params[:topic][:max_choosers]
else
if topic.max_choosers.to_i < params[:topic][:max_choosers].to_i
topic.update_waitlisted_users(params[:topic][:max_choosers])
topic.max_choosers = params[:topic][:max_choosers]
else
flash[:error] = 'Value of maximum choosers can only be increased! No change has been made to max choosers.'
end
end
end
The method update_topic_info updates topics' some attributes according to received parameters, such as permitted max number of choosers and waitlisted users.
def check_after_create
if @assignment.is_microtask?
@sign_up_topic.micropayment = params[:topic][:micropayment]
end
if @assignment.staggered_deadline?
topic_set = Array.new
topic = @sign_up_topic.id
end
end
def create
topic = SignUpTopic.find_with_name_and_assignment_id(params[:topic][:topic_name], params[:id]).first
if topic
update_topic_info(topic)
topic.category = params[:topic][:category]
topic.save
redirect_to_sign_up(params[:id])
else
set_values_for_new_topic
check_after_create
if @sign_up_topic.save
undo_link("Topic: \"#{@sign_up_topic.topic_name}\" has been created successfully. ")
redirect_to edit_assignment_path(@sign_up_topic.assignment_id) + "#tabs-5"
else
render action: 'new', id: params[:id]
end
end
end
Deleting redundant button
Renaming controller
Following global rules
References
<references/>