Thêm mới bài viết trong ruby on rails

Lượt xem: 1030

Thông thường, trong các ứng dụng web, việc tạo tài nguyên mới là một quy trình gồm nhiều bước. Đầu tiên, người dùng yêu cầu một biểu mẫu để điền vào. Sau đó, người dùng gửi biểu mẫu. Nếu không có lỗi, thì tài nguyên được tạo và một số loại xác nhận được hiển thị. Mặt khác, biểu mẫu được hiển thị lại với các thông báo lỗi và quá trình này được lặp lại. Trong một ứng dụng Rails, các bước này thường được xử lý bởi các action new và create trong controller. Chúng ta cùng thêm 2 action này vào sau action show như sau:


class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(title: "...", body: "...")

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end
end

Action new khởi tạo bài viết (article) nhưng không lưu nó.  Bài viết này sẽ sử dụng trong view khi xây dựng form. Mặc định action new sẽ render view:  app/views/articles/new.html.erb

Action create khởi tạo một bài viết mới với các giá trị cho tiêu đề (title) và nội dung (body), đồng thời cố gắng lưu bài viết đó. Nếu bài viết được lưu thành công, hành động này sẽ chuyển hướng trình duyệt đến trang của bài viết tại "http://localhost:3000/articles/#{@article.id}". Mặt khác, hành động sẽ hiển thị lại biểu mẫu bằng cách hiển thị app/views/articles/new.html.erb với mã trạng thái 422 Thực thể không thể xử lý (422 Unprocessable Entity). Tiêu đề và nội dung ở đây là giá trị giả. Sau khi chúng ta tạo biểu mẫu, chúng ta sẽ quay lại và thay đổi những điều này.

Chú ý:


redirect_to sẽ khiến trình duyệt tạo một yêu cầu mới, trong khi đó, kết xuất sẽ hiển thị chế độ xem đã chỉ định cho yêu cầu hiện tại. Điều quan trọng là sử dụng redirect_to sau khi thay đổi trạng thái cơ sở dữ liệu hoặc ứng dụng. Mặt khác, nếu người dùng làm mới trang, trình duyệt sẽ đưa ra yêu cầu tương tự và quá trình thay đổi sẽ được lặp lại.

1. Sử dụng một form builder

Chúng ta sẽ sử dụng một tính năng của Rails được gọi là trình tạo biểu mẫu (form builder ) để tạo biểu mẫu (form) của chúng ta. Sử dụng trình tạo biểu mẫu, chúng ta có thể viết một lượng mã tối thiểu để xuất biểu mẫu được định cấu hình đầy đủ và tuân theo các quy ước của Rails.

Chúng ta tạo file app/views/articles/new.html.erb với nội dung sau:


<h1>New Article</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
 </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

Hàm form_with giúp khởi tạo một form builder. Trong khối form_with chúng ta gọi các phương thức label và text_field trên form builder để kết xuất các phần tử form thích hợp.

Đầu ra kết quả từ lệnh gọi form_with của chúng ta sẽ như sau:


<form action="/articles" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="...">

  <div>
    <label for="article_title">Title</label><br>
    <input type="text" name="article[title]" id="article_title">
  </div>

  <div>
    <label for="article_body">Body</label><br>
    <textarea name="article[body]" id="article_body"></textarea>
  </div>

  <div>
    <input type="submit" name="commit" value="Create Article" data-disable-with="Create Article">
  </div>
</form>

2. Sử dụng sức mạnh của parameters

Dữ liệu form đã gửi được đưa vào Hash tham số, cùng với các tham số định tuyến đã định nghĩa. Do đó, tác vụ create có thể truy cập tiêu đề đã gửi qua params[:article][:title] và nội dung đã gửi qua params[:article][:body]. Chúng ta có thể chuyển các giá trị này riêng lẻ cho Article.new, nhưng điều đó sẽ dài dòng và có thể dễ bị lỗi. Và nó sẽ trở nên tồi tệ hơn khi chúng ta thêm nhiều trường hơn.

Thay vào đó, chúng ta sẽ chuyển một Hash duy nhất chứa các giá trị. Tuy nhiên, chúng ta vẫn phải chỉ định giá trị nào được phép trong Hash đó. Mặt khác, người dùng độc hại (hacker) có khả năng gửi các trường biểu mẫu bổ sung và ghi đè lên dữ liệu riêng tư. Trên thực tế, nếu chúng ta chuyển trực tiếp hàm băm params[:article] chưa được lọc tới Article.new, Rails sẽ đưa ra lỗi ForbiddenAttributesError để cảnh báo chúng ta về sự cố. Vì vậy, chúng ta sẽ sử dụng một tính năng của Rails được gọi là Tham số mạnh (Strong parameter) để lọc tham số.

Hãy thêm một phương thức riêng tư (private) vào cuối app/controllers/articles_controller.rb có tên là article_params để lọc các tham số. Và thay đổi phương thức create như sau:


class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

3. Xác thực và hiển thị thông báo lỗi

Như chúng ta đã thấy, việc tạo tài nguyên là một quá trình gồm nhiều bước. Xử lý đầu vào của người dùng không hợp lệ là một bước khác của quy trình đó. Rails cung cấp một tính năng gọi là xác thực (validations) để giúp chúng tôi xử lý đầu vào không hợp lệ của người dùng. Xác thực là các quy tắc được kiểm tra trước khi lưu mô hình đối tượng. Nếu bất kỳ kiểm tra nào không thành công, quá trình lưu sẽ bị hủy bỏ và các thông báo lỗi thích hợp sẽ được thêm vào thuộc tính lỗi của đối tượng mô hình (model object).

Hãy thêm một số xác thực vào mô hình của chúng ta trong app/models/article.rb:


class Article < ApplicationRecord
  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

Xác thực đầu tiên tuyên bố rằng title (tiêu đề) phải có giá trị.  Bởi vì title là một chuỗi, điều này có nghĩa là giá trị tiêu đề phải chứa ít nhất một ký tự không phải khoảng trắng.

Xác thực thứ hai tuyên bố rằng giá trị body (nội dung) cũng phải có mặt. Ngoài ra, nó tuyên bố rằng giá trị nội dung phải dài ít nhất 10 ký tự.

Với các xác thực của chúng ta, hãy sửa đổi app/views/articles/new.html.erb để hiển thị bất kỳ thông báo lỗi nào cho title và body:


<h1> New Article</h1> 

<%= form_with model: @article do |form| %> 
  <div> 
    <%= form.label :title %> <br> 
    <%= form.text_field :title %> 
    <% @article.errors.full_messages_for(:title).each do |message| %> 
      <div><%= message %></div> 
    <% end %> 
  </div> 

  <div> 
    <%= form.label :body %><br> 
    <%= form.text_area :body %><br> 
    <% @article.errors.full_messages_for(:body).each do |message| %> 
      <div><%= message %></div> 
    <% end %> 
  </div> 

  <div> 
    <%= form.submit %> 
  </div> 
<% end %> 

Phương thức full_messages_for trả về một mảng các thông báo lỗi thân thiện với người dùng cho một thuộc tính đã chỉ định. Nếu không có lỗi cho thuộc tính đó, mảng sẽ trống.

Để hiểu cách tất cả những thứ này hoạt động cùng nhau, chúng ta hãy xem xét lại các hành động new create trong controller:


def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

Khi chúng ta truy cập http://localhost:3000/articles/new, yêu cầu GET /articles/new được ánh xạ tới hành động new. Hành động new không cố lưu @article. Do đó, xác thực không được kiểm tra và sẽ không có thông báo lỗi. 

Khi chúng tôi gửi biểu mẫu (submit form), yêu cầu POST /articles được ánh xạ tới hành động create. Hành động create cố gắng lưu @article. Do đó, xác nhận được kiểm tra. Nếu có bất kỳ xác thực nào không thành công, @article sẽ không được lưu và app/views/articles/new.html.erb sẽ được hiển thị với thông báo lỗi.

4. Hoàn thành

Bây giờ chúng ta có thể tạo một bài báo bằng cách truy cập http://localhost:3000/articles/new. Để kết thúc, hãy liên kết đến trang đó từ dưới cùng của app/views/articles/index.html.erb:


<h1>Articles</h1>
<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>
<%= link_to "New Article", new_article_path %>

Video thao tác cùng dandev