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
showpage 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.
index page for multiple-choice questions. Note the π, π, and π link icons.We will also update the show page, adding the π (edit) and π (delete) link icons, as depicted in Figure 2.
show page for multiple-choice questions. Note the π and π link icons.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.
edit page for multiple-choice questions. Data submitted via this form will be processed by an update action.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.
index page resulting from the successful processing of an edit-form submission. Note the success notification near the top of the page.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.
edit page resulting from the unsuccessful processing of an edit-form submission. Note that the form has been filled in with the rejected form data, and an error notification appears near the top of the page.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.
index page resulting from the successful deletion of a question. Note that a success notification appears near the top of the page.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
McQuestionsControlleractionseditandupdate:# 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 routeNotice that instead of using a POST request for
update, we have two new HTTP request methods: PATCH and PUT. Aneditform 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 thenewroute ('mc_questions/new'); otherwise, requests that are meant to go tonewwill instead be routed to another (incorrect) action. -
In the
McQuestionsControllerclass, create aneditaction that will retrieve (using thefindmethod) theMcQuestionrecord with theidgiven in the request URL (which is held inparams[:id]), and then, will render theedit.html.erbview, passing in theMcQuestionobject, 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
updateaction to be filled in later, like this:def update # TODO end -
Create an
edit.html.erbfile 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.erbfrom the previous demo. The heading has changed to βEditβ. Theform_withhelperβsurlandmethodoptions 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
McQuestionobject with anidof 1 displayed. -
Add to the
indexview a π link to theshowpage and a π link to theeditpage 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 theshowpage as well, like this:<p> <%= question.question %> <%= link_to 'π', edit_mc_question_path(question) %> </p>You should now be able to go to the
editpage for any question by clicking theπlink for that question on theindexpage or theshowpage.
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
McQuestionobject usingfindmethod and theidpassed in via theparamshash, like this:# load existing object again from URL param question = McQuestion.find(params[:id]) -
Add the
respond_toblock andiflogic using theupdatemethod, 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
ifpart wherequestion.updateis successful, add a success message to theflashhash and a redirect to theindexpage, like this:# success message flash[:success] = 'Question updated successfully' # redirect to index redirect_to mc_questions_url -
For
elsepart wherequestion.updatefails, add an error message to theflashhash usingflash.now, and re-render theeditpage, 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
McQuestionsControlleractiondestroy, like this:# show route delete 'mc_questions/:id', to: 'mc_questions#destroy' # destroy route -
Add an empty
destroycontroller action. When finished, the action will first retrieve the object to be deleted based on theidin the request URL, then delete the object, then set a flash notification, and finally respond with an HTTP redirect to theindexpage. 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
idfrom the path inparamshash, and then delete it using the modelβsdestroymethod, like this:# load existing object again from URL param question = McQuestion.find(params[:id]) # destroy object question.destroy -
Complete the logic for the
respond_toblock by adding a success message to theflashhas and an HTTP redirect to theindexpage, 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
indexpage and theshowpage by adding a delete (π) link after theeditlink 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.