Demo 10: Forms and Actions for Updating and Deleting Model Records
In this demonstration, I will show how to add controller actions and views that allow users to update and delete database records. We will continue to work on the QuizMe project from the previous demos.
In particular, we will update the index
page for multiple-choice questions such that each question has three link icons:
- π links to the
show
page for the question; - π links to a form for editing the question; and
- π deletes the question.
Figure 1 depicts an example of an index
page with these link icons.
We will also update the show
page, adding the π (edit) and π (delete) link icons, as depicted in Figure 2.
Clicking the π link for a question on either the index
or the show
page will take the user to an edit
page with a form for updating the question, as depicted in Figure 3.
If a user submits the edit
form, the submitted form data will be processed by an update
action. If the update
action successfully saves the data, the browser will be redirected to the index
page for multiple-choice questions and a flash success notification will be displayed, as depicted in Figure 4.
If the update
action is unable to save the submitted data, for example, because a model validation fails, then the action will re-render the edit
page, with the submitted data filled into the form and a flash error notification. (Note that this behavior is essentially the same as for the new
form we built in the previous demo.) Figure 5 depicts the result of submitting an edit
form with nothing filled in for the distractors.
Finally, clicking the π link for a question on either the index
or the show
page will cause a destroy
action to delete the question from the database and to redirect the browser to the index
page, displaying a flash success notification. Figure 6 depicts the result of deleting a question.
To implement these new features, we will first get the edit
page and update
action working and then the destroy
links.
1. Updating Records with a Form
The edit
/update
functionality will be similar to the new
/create
functionality from the previous demo in that users will edit the multiple-choice question attributes in a form. However, unlike the new
form, the edit
form will always use an existing object. For that reason, the route for edit
(and for update
as well) will contain a URL parameter :id
which holds the id
of the record to modify. Both the edit
and update
controller actions will need to use the find
model method to retrieve the correct object based on the id
in the URL.
1.1. Rendering the Form with the edit
Action
Letβs start by setting up the edit
action along with some other boilerplate code:
-
Add the standard resource routes for the
McQuestionsController
actionsedit
andupdate
:# index route # new route # create route get 'mc_questions/:id/edit', to: 'mc_questions#edit', as: 'edit_mc_question' # edit patch 'mc_questions/:id', to: 'mc_questions#update' # update (as needed) put 'mc_questions/:id', to: 'mc_questions#update' # update (full replacement) # show route
Notice that instead of using a POST request for
update
, we have two new HTTP request methods: PATCH and PUT. Anedit
form in Rails may use either of these two, so we list them both in the routes. The main difference between these two request methods is that PATCH is used when certain parts of a record are to be modified, whereas PUT is used when the whole record is to be replaced.As always, you must pay attention to the order of the routes. For example, the newly added PATCH/PUT routes with the URI pattern
'mc_questions/:id'
must come after thenew
route ('mc_questions/new'
); otherwise, requests that are meant to go tonew
will instead be routed to another (incorrect) action. -
In the
McQuestionsController
class, create anedit
action that will retrieve (using thefind
method) theMcQuestion
record with theid
given in the request URL (which is held inparams[:id]
), and then, will render theedit.html.erb
view, passing in theMcQuestion
object, like this:def edit # object to use in form question = McQuestion.find(params[:id]) respond_to do |format| format.html { render :edit, locals: { question: question } } end end
-
Add an empty
update
action to be filled in later, like this:def update # TODO end
-
Create an
edit.html.erb
file inapp/views/mc_questions
, and give it the following code:<h1>Edit Question</h1> <%= form_with model: question, url: mc_question_path, method: :patch, local: true, scope: :mc_question do |form| %> <!-- attribute fields --> <div> <%= form.label :question %><br> <%= form.text_field :question %> </div> <div> <%= form.label :answer %><br> <%= form.text_field :answer %> </div> <div> <%= form.label :distractor_1 %><br> <%= form.text_field :distractor_1 %> </div> <div> <%= form.label :distractor_2 %><br> <%= form.text_field :distractor_2 %> </div> <%= form.submit 'Update Question' %> <% end %>
This code is largely the same as in
new.html.erb
from the previous demo. The heading has changed to βEditβ. Theform_with
helperβsurl
andmethod
options have been changed to match the UPDATE route. The submit button text has been changed to βUpdateβ.At this point, you can try reseting the database, launching the Rails development server, and visiting http://localhost:3000/mc_questions/1/edit. You should see the data load and the form for the
McQuestion
object with anid
of 1 displayed. -
Add to the
index
view a π link to theshow
page and a π link to theedit
page for each question, like this:<p> <%= question.question %> <%= link_to 'π', mc_question_path(question) %> <%= link_to 'π', edit_mc_question_path(question) %> </p>
-
Add the
edit
-page link after the question text on theshow
page as well, like this:<p> <%= question.question %> <%= link_to 'π', edit_mc_question_path(question) %> </p>
You should now be able to go to the
edit
page for any question by clicking theπ
link for that question on theindex
page or theshow
page.
1.2. Processing Form Data with the update
Action
Now letβs fill in the logic to the update
action. The action must first retrieve the object to be updated from the database, using the id
in the request URL. Next, the action must attempt to update the object using the mc_question
data the params
hash. If saving the object is successful, the action will respond with an HTTP redirect to the index
page. If saving the object is unsuccessful, the action will render the edit
form again with a flash error message. Psuedocode for the logic would look like this:
def update
# load existing object again from URL param
# respond_to block
# html format block
# if question updates with permitted params
# success message
# redirect to index
# else
# error message
# render edit
end
Perform the following steps to fill in the actual code:
-
Add code to retrieve the existing
McQuestion
object usingfind
method and theid
passed in via theparams
hash, like this:# load existing object again from URL param question = McQuestion.find(params[:id])
-
Add the
respond_to
block andif
logic using theupdate
method, like this:# respond_to block respond_to do |format| # html format block format.html { # if question updates with permitted params if question.update(params.require(:mc_question).permit(:question, :answer, :distractor_1, :distractor_2)) # success message # redirect to index else # error message # render edit end } end
-
For
if
part wherequestion.update
is successful, add a success message to theflash
hash and a redirect to theindex
page, like this:# success message flash[:success] = 'Question updated successfully' # redirect to index redirect_to mc_questions_url
-
For
else
part wherequestion.update
fails, add an error message to theflash
hash usingflash.now
, and re-render theedit
page, like this:# error message flash.now[:error] = 'Error: Question could not be updated' # render edit render :edit, locals: { question: question }
Users should now be able to use the app to update existing questions.
2. Deleting Records with the destroy
Action
Now that the QuizMe app has features to create, read (index
/show
), and update (CRU_) multiple-choice questions, the last thing to do is add the functionality for deleting questions. Unlike the new
/create
and edit
/update
features, deleting records will not involve a form. Thus, we will add only one new controller action (destroy
), and we will add special hyperlinks to the index
and show
pages to invoke the destroy
action.
-
Add a standard resource route to map HTTP DELETE requests to the
McQuestionsController
actiondestroy
, like this:# show route delete 'mc_questions/:id', to: 'mc_questions#destroy' # destroy route
-
Add an empty
destroy
controller action. When finished, the action will first retrieve the object to be deleted based on theid
in the request URL, then delete the object, then set a flash notification, and finally respond with an HTTP redirect to theindex
page. The psuedocode for this logic looks like this:def destroy # load existing object again from URL param # destroy object # respond_to block # html format block # success message # redirect to index end
-
Add code to retrieve the object to be deleted using the
id
from the path inparams
hash, and then delete it using the modelβsdestroy
method, like this:# load existing object again from URL param question = McQuestion.find(params[:id]) # destroy object question.destroy
-
Complete the logic for the
respond_to
block by adding a success message to theflash
has and an HTTP redirect to theindex
page, like this:# respond_to block respond_to do |format| # html format block format.html { # success message flash[:success] = 'Question removed successfully' # redirect to index redirect_to mc_questions_url } end
-
Lastly, update the
index
page and theshow
page by adding a delete (π) link after theedit
link for each multiple-choice question, like this:<!-- edit link --> <!-- delete link --> <%= link_to 'π', mc_question_path(question), method: :delete %>
User of the app should now be able to delete any multiple-choice question by clicking the π
link for that question on the index
page.