We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating articles.
The first step we'll take is adding an edit action to the ArticlesController, generally between the new and create actions, as shown:
def new@article = Article.newenddef edit@article = Article.find(params[:id])enddef create@article = Article.new(article_params)if @article.saveredirect_to @articleelserender 'new'endend
The view will contain a form similar to the one we used when creating new articles. Create a file called app/views/articles/edit.html.erb and make it look as follows:
<h1>Edit article</h1><%= form_with(model: @article, local: true) do |form| %><% if @article.errors.any? %><div id="error_explanation"><h2><%= pluralize(@article.errors.count, "error") %> prohibitedthis article from being saved:</h2><ul><% @article.errors.full_messages.each do |msg| %><li><%= msg %></li><% end %></ul></div><% end %><p><%= form.label :title %><br><%= form.text_field :title %></p><p><%= form.label :text %><br><%= form.text_area :text %></p><p><%= form.submit %></p><% end %><%= link_to 'Back', articles_path %>
This time we point the form to the update action, which is not defined yet but will be very soon.
Passing the article object to the method, will automagically create url for submitting the edited article form. This option tells Rails that we want this form to be submitted via the PATCH HTTP method which is the HTTP method you're expected to use to update resources according to the REST protocol.
The arguments to form_with could be model objects, say, model: @article which would cause the helper to fill in the form with the fields of the object. Passing in a symbol scope (scope: :article) just creates the fields but without anything filled into them. More details can be found in form_with documentation.
Next, we need to create the update action in app/controllers/articles_controller.rb. Add it between the create action and the private method:
def create@article = Article.new(article_params)if @article.saveredirect_to @articleelserender 'new'endenddef update@article = Article.find(params[:id])if @article.update(article_params)redirect_to @articleelserender 'edit'endendprivatedef article_paramsparams.require(:article).permit(:title, :text)end
The new method, update, is used when you want to update a record that already exists, and it accepts a hash containing the attributes that you want to update. As before, if there was an error updating the article we want to show the form back to the user.
We reuse the article_params method that we defined earlier for the create action.
It is not necessary to pass all the attributes to update. For example, if @article.update(title: 'A new title') was called, Rails would only update the title attribute, leaving all other attributes untouched.
Finally, we want to show a link to the edit action in the list of all the articles, so let's add that now to app/views/articles/index.html.erb to make it appear next to the "Show" link:
<table><tr><th>Title</th><th>Text</th><th colspan="2"></th></tr><% @articles.each do |article| %><tr><td><%= article.title %></td><td><%= article.text %></td><td><%= link_to 'Show', article_path(article) %></td><td><%= link_to 'Edit', edit_article_path(article) %></td></tr><% end %></table>
And we'll also add one to the app/views/articles/show.html.erb template as well, so that there's also an "Edit" link on an article's page. Add this at the bottom of the template:
... <%= link_to 'Edit', edit_article_path(@article) %> |<%= link_to 'Back', articles_path %>
And here's how our app looks so far:
Using partials to clean up duplication in views
Our edit page looks very similar to the new page; in fact, they both share the same code for displaying the form. Let's remove this duplication by using a view partial. By convention, partial files are prefixed with an underscore.
Create a new file app/views/articles/_form.html.erb with the following content:
<%= form_with model: @article, local: true do |form| %><% if @article.errors.any? %><div id="error_explanation"><h2><%= pluralize(@article.errors.count, "error") %> prohibitedthis article from being saved:</h2><ul><% @article.errors.full_messages.each do |msg| %><li><%= msg %></li><% end %></ul></div><% end %><p><%= form.label :title %><br><%= form.text_field :title %></p><p><%= form.label :text %><br><%= form.text_area :text %></p><p><%= form.submit %></p><% end %>
Everything except for the form_with declaration remained the same. The reason we can use this shorter, simpler form_with declaration to stand in for either of the other forms is that @article is a resource corresponding to a full set of RESTful routes, and Rails is able to infer which URI and method to use. For more information about this use of form_with, see Resource-oriented style.
Now, let's update the app/views/articles/new.html.erb view to use this new partial, rewriting it completely:
<h1>New article</h1><%= render 'form' %><%= link_to 'Back', articles_path %>
Then do the same for the app/views/articles/edit.html.erb view:
<h1>Edit article</h1><%= render 'form' %><%= link_to 'Back', articles_path %>