- Title: - <%= @article.title %> -
+Articles
-- Text: - <%= @article.text %> -
+-
+ <% @articles.each do |article| %>
+
- + <%= article.title %> + + <% end %> +
Listing Articles
+<%= @article.title %>
-Title | -Text | -- |
---|---|---|
<%= article.title %> | -<%= article.text %> | -<%= link_to 'Show', article_path(article) %> | -
Hello, Rails!
-<%= link_to 'My Blog', controller: 'articles' %> -``` - -The `link_to` method is one of Rails' built-in view helpers. It creates a -hyperlink based on text to display and where to go - in this case, to the path -for articles. - -Let's add links to the other views as well, starting with adding this -"New Article" link to `app/views/articles/index.html.erb`, placing it above the -`Title | -Text | -- | |
---|---|---|---|
<%= article.title %> | -<%= article.text %> | -<%= link_to 'Show', article_path(article) %> | -<%= link_to 'Edit', edit_article_path(article) %> | -
Articles
-<%= link_to 'Edit', edit_article_path(@article) %> | -<%= link_to 'Back', articles_path %> -``` - -And here's how our app looks so far: - -![Index action with edit link](images/getting_started/index_action_with_edit_link.png) - -### 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. - -TIP: You can read more about partials in the -[Layouts and Rendering in Rails](layouts_and_rendering.html) guide. - -Create a new file `app/views/articles/_form.html.erb` with the following -content: - -```html+erb -<%= form_with model: @article, local: true do |form| %> - - <% if @article.errors.any? %> -- <%= pluralize(@article.errors.count, "error") %> prohibited - this article from being saved: -
--
- <% @article.errors.full_messages.each do |msg| %>
-
- <%= msg %> - <% end %> -
-
+ <% @articles.each do |article| %>
+
- + <%= link_to article.title, article %> + <% end %> - -
- <%= form.label :title %>
- <%= form.text_field :title %>
-
- <%= form.label :text %>
- <%= form.text_area :text %>
-
- <%= form.submit %> -
- -<% end %> +New Article
+### Creating a New Article -<%= render 'form' %> +Now we move on to the "C" (Create) of CRUD. Typically, in web applications, +creating a new resource is a multi-step process. First, the user requests a form +to fill out. Then, the user submits the form. If there are no errors, then the +resource is created and some kind of confirmation is displayed. Else, the form +is redisplayed with error messages, and the process is repeated. -<%= link_to 'Back', articles_path %> -``` - -Then do the same for the `app/views/articles/edit.html.erb` view: - -```html+erb -Edit Article
- -<%= render 'form' %> - -<%= link_to 'Back', articles_path %> -``` - -### Deleting Articles - -We're now ready to cover the "D" part of CRUD, deleting articles from the -database. Following the REST convention, the route for -deleting articles as per output of `bin/rails routes` is: - -```ruby -DELETE /articles/:id(.:format) articles#destroy -``` - -The `delete` routing method should be used for routes that destroy -resources. If this was left as a typical `get` route, it could be possible for -people to craft malicious URLs like this: - -```html -look at this cat! -``` - -We use the `delete` method for destroying resources, and this route is mapped -to the `destroy` action inside `app/controllers/articles_controller.rb`, which -doesn't exist yet. The `destroy` method is generally the last CRUD action in -the controller, and like the other public CRUD actions, it must be placed -before any `private` or `protected` methods. Let's add it: - -```ruby -def destroy - @article = Article.find(params[:id]) - @article.destroy - - redirect_to articles_path -end -``` - -The complete `ArticlesController` in the -`app/controllers/articles_controller.rb` file should now look like this: +In a Rails application, these steps are conventionally handled by a controller's +`new` and `create` actions. Let's add a typical implementation of these actions +to `app/controllers/articles_controller.rb`, below the `show` action: ```ruby class ArticlesController < ApplicationController @@ -1503,27 +795,470 @@ class ArticlesController < ApplicationController @article = Article.new end - def edit + def create + @article = Article.new(title: "...", body: "...") + + if @article.save + redirect_to @article + else + render :new + end + end +end +``` + +The `new` action instantiates a new article, but does not save it. This article +will be used in the view when building the form. By default, the `new` action +will render `app/views/articles/new.html.erb`, which we will create next. + +The `create` action instantiates a new article with values for the title and +body, and attempts to save it. If the article is saved successfully, the action +redirects the browser to the article's page at `"http://localhost:3000/articles/#{@article.id}"`. +Else, the action redisplays the form by rendering `app/views/articles/new.html.erb`. +The title and body here are dummy values. After we create the form, we will come +back and change these. + +NOTE: [`redirect_to`](https://api.rubyonrails.org/classes/ActionController/Redirecting.html#method-i-redirect_to) +will cause the browser to make a new request, +whereas [`render`](https://api.rubyonrails.org/classes/AbstractController/Rendering.html#method-i-render) +renders the specified view for the current request. +It is important to use `redirect_to` after mutating the database or application state. +Otherwise, if the user refreshes the page, the browser will make the same request, and the mutation will be repeated. + +#### Using a Form Builder + +We will use a feature of Rails called a *form builder* to create our form. Using +a form builder, we can write a minimal amount of code to output a form that is +fully configured and follows Rails conventions. + +Let's create `app/views/articles/new.html.erb` with the following contents: + +```html+erb +New Article
+ +<%= form_with model: @article, local: true do |form| %> ++ <%= form.text_field :title %> +
+ <%= form.text_area :body %> +
New Article
+ +<%= form_with model: @article, local: true do |form| %> ++ <%= form.text_field :title %> + <%= @article.errors.full_messages_for(:title).each do |message| %> +
+ <%= form.text_area :body %>
+ <%= @article.errors.full_messages_for(:body).each do |message| %> +
Articles
+ +-
+ <% @articles.each do |article| %>
+
- + <%= link_to article.title, article %> + + <% end %> +
+ <%= form.text_field :title %> + <%= article.errors.full_messages_for(:title).each do |message| %> +
+ <%= form.text_area :body %>
+ <%= article.errors.full_messages_for(:body).each do |message| %> +
New Article
+ +<%= render "form", article: @article %> +``` + +NOTE: A partial's filename must be prefixed **with** an underscore, e.g. +`_form.html.erb`. But when rendering, it is referenced **without** the +underscore, e.g. `render "form"`. + +And now, let's create a very similar `app/views/articles/edit.html.erb`: + +```html+erb +Edit Article
+ +<%= render "form", article: @article %> +``` + +TIP: To learn more about partials, see [Layouts and Rendering in Rails § Using +Partials](layouts_and_rendering.html#using-partials). + +#### Finishing Up + +We can now update an article by visiting its edit page, e.g. +<%= @article.title %>
+ +<%= @article.body %>
+ +-
+
- <%= link_to "Edit", edit_article_path(@article) %> +
Listing Articles
-<%= link_to 'New article', new_article_path %> -Title | -Text | -- | ||
---|---|---|---|---|
<%= article.title %> | -<%= article.text %> | -<%= link_to 'Show', article_path(article) %> | -<%= link_to 'Edit', edit_article_path(article) %> | -<%= link_to 'Destroy', article_path(article), - method: :delete, - data: { confirm: 'Are you sure?' } %> | -
<%= @article.body %>
+ +-
+
- <%= link_to "Edit", edit_article_path(@article) %> +
- <%= link_to "Destroy", article_path(@article), + method: :delete, + data: { confirm: "Are you sure?" } %> +
- Title: - <%= @article.title %> -
+<%= @article.title %>
-- Text: - <%= @article.text %> -
+<%= @article.body %>
+ +-
+
- <%= link_to "Edit", edit_article_path(@article) %> +
- <%= link_to "Destroy", article_path(@article), + method: :delete, + data: { confirm: "Are you sure?" } %> +
Add a comment:
<%= form_with model: [ @article, @article.comments.build ], local: true do |form| %> @@ -1781,9 +1507,6 @@ So first, we'll wire up the Article show template <%= form.submit %> <% end %> - -<%= link_to 'Edit', edit_article_path(@article) %> | -<%= link_to 'Back', articles_path %> ``` This adds a form on the `Article` show page that creates a new comment by @@ -1825,15 +1548,16 @@ the `show` action of the `ArticlesController` which in turn renders the add that to the `app/views/articles/show.html.erb`. ```html+erb -- Title: - <%= @article.title %> -
+<%= @article.title %>
-- Text: - <%= @article.text %> -
+<%= @article.body %>
+ +-
+
- <%= link_to "Edit", edit_article_path(@article) %> +
- <%= link_to "Destroy", article_path(@article), + method: :delete, + data: { confirm: "Are you sure?" } %> +
Comments
<% @article.comments.each do |comment| %> @@ -1862,9 +1586,6 @@ add that to the `app/views/articles/show.html.erb`. <%= form.submit %> <% end %> - -<%= link_to 'Edit', edit_article_path(@article) %> | -<%= link_to 'Back', articles_path %> ``` Now you can add articles and comments to your blog and have them show up in the @@ -1901,15 +1622,16 @@ Then you can change `app/views/articles/show.html.erb` to look like the following: ```html+erb -- Title: - <%= @article.title %> -
+<%= @article.title %>
-- Text: - <%= @article.text %> -
+<%= @article.body %>
+ +-
+
- <%= link_to "Edit", edit_article_path(@article) %> +
- <%= link_to "Destroy", article_path(@article), + method: :delete, + data: { confirm: "Are you sure?" } %> +
Comments
<%= render @article.comments %> @@ -1928,9 +1650,6 @@ following: <%= form.submit %> <% end %> - -<%= link_to 'Edit', edit_article_path(@article) %> | -<%= link_to 'Back', articles_path %> ``` This will now render the partial in `app/views/comments/_comment.html.erb` once @@ -1963,24 +1682,22 @@ create a file `app/views/comments/_form.html.erb` containing: Then you make the `app/views/articles/show.html.erb` look like the following: ```html+erb -- Title: - <%= @article.title %> -
+<%= @article.title %>
-- Text: - <%= @article.text %> -
+<%= @article.body %>
+ +-
+
- <%= link_to "Edit", edit_article_path(@article) %> +
- <%= link_to "Destroy", article_path(@article), + method: :delete, + data: { confirm: "Are you sure?" } %> +
Comments
<%= render @article.comments %>Add a comment:
<%= render 'comments/form' %> - -<%= link_to 'Edit', edit_article_path(@article) %> | -<%= link_to 'Back', articles_path %> ``` The second render just defines the partial template we want to render, @@ -2010,8 +1727,9 @@ Within the `article` model, after running a migration to add a `status` column, ```ruby class Article < ApplicationRecord has_many :comments - validates :title, presence: true, - length: { minimum: 5 } + + validates :title, presence: true + validates :body, presence: true, length: { minimum: 10 } VALID_STATUSES = ['public', 'private', 'archived'] @@ -2042,29 +1760,19 @@ end Then, in our `index` action template (`app/views/articles/index.html.erb`) we would use the `archived?` method to avoid displaying any article that is archived: ```html+erb -Listing Articles
-<%= link_to 'New article', new_article_path %> -Title | -Text | -- | ||
---|---|---|---|---|
<%= article.title %> | -<%= article.text %> | -<%= link_to 'Show', article_path(article) %> | -<%= link_to 'Edit', edit_article_path(article) %> | -<%= link_to 'Destroy', article_path(article), - method: :delete, - data: { confirm: 'Are you sure?' } %> | -
Listing Articles
-Our blog has <%= Article.public_count %> articles and counting! Add yours now. -<%= link_to 'New article', new_article_path %> -Title | -Text | -- | ||
---|---|---|---|---|
<%= article.title %> | -<%= article.text %> | -<%= link_to 'Show', article_path(article) %> | -<%= link_to 'Edit', edit_article_path(article) %> | -<%= link_to 'Destroy', article_path(article), - method: :delete, - data: { confirm: 'Are you sure?' } %> | -
<%= link_to 'Destroy Comment', [comment.article, comment], method: :delete, - data: { confirm: 'Are you sure?' } %> + data: { confirm: "Are you sure?" } %>
``` @@ -2249,8 +1945,9 @@ class Article < ApplicationRecord include Visible has_many :comments, dependent: :destroy - validates :title, presence: true, - length: { minimum: 5 } + + validates :title, presence: true + validates :body, presence: true, length: { minimum: 10 } end ```