Giới thiệu thư viện Sinatra qua việc xây dựng một ứng dụng đơn giản
Sinatra là một DSL được viết bằng Ruby cho phép tạo ứng dụng web một cách nhanh chóng với chi phí thấp. Bài viết sẽ giới thiệu một số tính năng của Sinatra và sử dụng thư viện này để xây dựng một ứng dụng demo. Việc khởi tạo ứng dụng với Sinatra khá đơn giản. Chúng ta hãy cùng xem xét ví dụ sau: ...
Sinatra là một DSL được viết bằng Ruby cho phép tạo ứng dụng web một cách nhanh chóng với chi phí thấp. Bài viết sẽ giới thiệu một số tính năng của Sinatra và sử dụng thư viện này để xây dựng một ứng dụng demo.
Việc khởi tạo ứng dụng với Sinatra khá đơn giản. Chúng ta hãy cùng xem xét ví dụ sau:
- cài đặt gem sinatra:
gem install sinatra
- tạo tệp app.rb:
require "sinatra" get "/" do "Hello world!" end
- chạy server:
ruby app.rb
- mở trình duyệt với địa chỉ localhost:4567:
Như vậy, chỉ với một vài bước, chúng ta đã có thể tạo ra một ứng dụng web và sử dụng nó. Tuy nhiên, ứng dụng này vô cùng đơn giản, vậy hãy tìm hiểu xem Sinatra cung cấp những gì để ta có thể tạo ra một ứng dụng phức tạp hơn.
Route
Ở trong tệp app.rb phía trên có chứa 3 dòng:
get "/" do "Hello world!" end
Ba dòng này định nghĩa một block dùng để xử lý HTTP request với phương thức GET và URI là "/". Xâu "Hello world!" chính là nội dung phản hồi của ứng dụng trả về cho phía client. Toàn bộ block này là một route của Sinatra.
Như vậy, route của Sinatra là một block dùng để xử lý các HTTP request với phương thức và uri nào đó, nội dung trả về cho client sẽ được xử lý trong block này. Tương ứng với các HTTP method khác, Sinatra cho phép khai báo các block tương ứng:
post "/uri" do # code goes here end put "/uri" do # code goes here end patch "/uri" do # code goes here end delete "/uri" do # code goes here end ...
Trong phần uri của block có thể chứa các tham số, các tham số này được truy cập thông qua hash params. Ví dụ:
get "/posts/:id" do @post = Post.find params[:id] end
URI cũng có thể chứa ký tự * (wildcard). Phần tham số tương ứng với ký tự này được truy cập qua mảng params[:splat]. Ví dụ:
get "/posts/*/comments/*" do @post = Post.find params[:splat][0] @comment = Comment.find params[:splat][1] end
Ngoài ra còn có khá nhiều cách sử dụng uri khác có thể tham khảo tại đây.
View
Template
Phần view là phần nội dung trang web được trả về cho client. Ngoài cách trả về trực tiếp qua xâu ký tự như ở ví dụ ở phần giới thiệu, Sinatra cho phép định nghĩa các template và trả về nội dung của các template này. Các template được đặt trong thư mục views của ứng dụng. Với ví dụ đầu tiên, chúng ta có thể tạo thư mục views và tạo tệp home.erb bên trong thư mục này với nội dung như sau:
Rendered from template.
Sửa tệp app.rb như sau:
require "sinatra" get "/" do erb :home end
Khởi động lại server (chạy lại lệnh ruby app.rb), tải lại trang web, ta có kết quả:
Với ví dụ trên, ứng dụng đã sử dụng template dạng Erubis. Sinatra cũng hỗ trợ nhiều định dạng template khác, có thể xem ở đây.
Sinatra cho phép định nghĩa một template chính dùng để chứa các template khác. Template này có tên layout.erb. Trong layout.erb sẽ chứa lời gọi đến method yield dùng để render các template khác. Tiếp tục với ví dụ đầu, chúng ta tạo tệp layout.erb với nội dung sau:
<!DOCTYPE html> <html> <head> <title>Note</title> </head> <body> <%= yield %> </body> </html>
Tải lại trang web, ta thấy phần tiêu đề đổi thành "Note".
Ngoài ra Sinatra cũng cho phép chỉ định layout khác với layout.erb, ví dụ:
get "/" do erb :home, layout: :other_layout end
Trong đó :other_layout được định nghĩa bằng template other_layout.erb.
Truyền biến sang view
Các biến instance ở các block xử lý HTTP request có thể được sử dụng ngay trong các template:
# app.rb ... get "/posts/:id" do @post = Post.find params[:id] erb :show end
# show.erb ... <p><%= @post.content %></p> ...
Chúng ta có thể truyền biến vào view thông qua hash locals:
get "/" do str = "This is a string" erb :home, locals: {string: str} end
# home.erb ... <p><%= string %></p> ...
CSS và Javascript
Các tệp css và javascript được đặt trong thư mục public của ứng dụng. Các tệp này sẽ được sử dụng trong template bằng việc khai báo các thẻ <link> và <script>.
Ứng dụng demo của chúng ta có chức năng lưu trữ các ghi chú cho người dùng. Các chức năng của ứng dụng bao gồm tạo mới, sửa (nội dung, đánh dấu/hủy đánh dấu hoàn thành), xóa các ghi chú.
Mỗi ghi chú sẽ gồm các trường dữ liệu: id, nội dung, dấu hoàn thành, dấu thời gian.
Khởi tạo ứng dụng
- tạo thư mục note-app chứa mã nguồn của ứng dụng
- tạo tệp Gemfile trong thư mục note-app để quản lý các gem cần thiết:
source "https://rubygems.org" gem "sinatra" gem "shotgun"
Do Sinatra không tự động nạp lại nội dung của tệp chứa code chính của ứng dụng khi tệp này bị thay đổi nên chúng ta sử dụng gem shotgun để tránh phải khởi động lại server mỗi khi có thay đổi. Sử dụng gem này bằng cách chạy server với lệnh: shotgun app.rb thay vì ruby app.rb. Server sẽ lắng nghe ở cổng 9393, để đổi cổng thành 4567 có thể thêm tham số -p 4567 vào sau lệnh khởi động server.
Chạy bundle install để cài đặt các gem.
- tạo tệp app.rb:
require "sinatra"
- tạo thư mục views và tệp layout.erb bên trong thư mục này với nội dung:
<!DOCTYPE html> <html> <head> <title>Note</title> <link rel="stylesheet" type="text/css" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> </head> <body> <div class="container"> <div class="col-md-6 col-md-offset-3"> <a href="/"><h1>Note</h1></a> </div> <%= yield %> </div> </body> </html>
Cài đặt cơ sở dữ liệu
Chúng ta sẽ dùng sqlite3 và gem sinatra-activerecord để quản lý CSDL. Hướng dẫn sử dụng sinatra-activerecord có thể xem ở đây.
- Sửa Gemfile và cài đặt gem:
... gem "sqlite3" gem "sinatra-activerecord" gem "rake"
- Sửa app.rb
... require "sinatra/activerecord" set :database, {adapter: "sqlite3", database: "noteapp.sqlite3"}
- Tạo Rakefile:
require "sinatra/activerecord/rake" namespace :db do task :load_config do require "./app" end end
- Tạo cơ sở dữ liệu: bundle exec rake db:create
- Tạo migration mới dùng để tạo bảng notes: bundle exec rake db:create_migration NAME=create_notes. Tệp migration sẽ được sinh ra trong thư mục db/migrate/. Sửa tệp này như sau:
class CreateNotes < ActiveRecord::Migration def change create_table :notes do |t| t.text :content, null: false t.boolean :done, null: false, default: false t.timestamps null: false end end end
Chạy bundle exec rake db:migrate để thực hiện migration.
- Tạo tệp note.rb trong thư mục models để quản lý bảng notes:
class Note < ActiveRecord::Base validates :content, presence: true end
- Sửa tệp app.rb:
... require "sinatra/activerecord" require "./models/note" ...
- Tạo tệp seeds.rb trong thư mục db để khởi tạo dữ liệu ban đầu:
3.times do |n| Note.create content: "Content of note #{n}", done: n % 2 == 0 end
Chạy bundle exec rake db:seed để tạo dữ liệu.
Cài đặt các chức năng
Hiển thị các ghi chú
Việc hiển thị sẽ được xử lý bởi route có phương thức là get và uri là "/". Trong route này, danh sách của tất cả các ghi chú sẽ được lấy ra và lưu vào trong biến @notes. Biến này sẽ được sử dụng bởi template home để hiển thị ra các ghi chú.
- Sửa tệp app.rb:
... get "/" do @notes = Note.all erb :home end
- Tạo tệp "views/home.erb"
<div class="col-md-6 col-md-offset-3"> <ul class="list-group" id="list-notes"> <% @notes.each do |note| %> <li class="list-group-item clearfix"> <div class="col-md-11"> <p><%= note.content %></p> <p><i>Created at: <%= note.created_at %></i></p> </div> <div class="col-md-1 done-sign"> <% if note.done? %> <span class="glyphicon glyphicon-ok"></span> <% end %> </div> </li> <% end %> </ul> </div>
- Chúng ta sẽ sử dụng style.css để tùy biến một chút giao diện. Tạo thư mục public và tệp style.css bên trong với nội dung:
#list-notes { margin-top: 5%; } .done-sign span{ font-size: 1.5em; color: green; }
- Thêm tệp này vào layout.erb:
... <head> ... <link rel="stylesheet" type="text/css" href="style.css"> </head> ...
- Khởi động server (shotgun app.rb -p 4567) và truy cập localhost:4567:
Tạo mới ghi chú
Chức năng này sẽ cho phép người dùng nhập nội dung của ghi chú và gửi lên server để tạo ghi chú mới. Route sẽ xử lý phương thức post và uri là "/notes", lấy thông tin gửi lên của client ở biến hash params để tạo ghi chú và chuyển hướng về uri "/".
Trước hết, chúng ta sẽ sửa tệp home.erb để tạo thêm một form nhập liệu cho phép nhập nội dung của ghi chú mới và gửi lên server:
<div class="col-md-6 col-md-offset-3"> <form action="/notes" method="post" id="new-note-form"> <textarea placeholder="Write note here..." class="form-control" name="content"></textarea> <input type="submit" class="btn-primary form-control" value="Create"> </form> ...
Sau đó, tạo thêm một route mới trong app.rb. Tương ứng với thẻ <textarea> có name là content, biến params[:content] sẽ chứa nội dung của ghi chú mới:
... post "/notes" do @note = Note.new content: params[:content] @note.save redirect "/" end
Chỉnh sửa một chút css trong tệp style.css:
... #new-note-form input[type=submit] { margin-top: 2%; margin-bottom: 2%; }
Tải lại trang web và tạo một ghi chú mới:
Sửa ghi chú
Để sửa một ghi chú, ta cần hai route cho phép hiển thị trang sửa ghi chú và xử lý nội dung mới của ghi chú được gửi lên từ client.
Trước hết, thêm đường dẫn đến trang sửa ghi chú cho mỗi ghi chú được hiển thị ở template home:
... <p><i>Created at: <%= note.created_at %></i></p> <div> <a class="btn-xs" href="/notes/<%=note.id %>/edit">Edit</a> </div> ...
Tạo tiếp hai route trong app.rb. Route thứ nhất xử lý hiển thị trang sửa ghi chú có phương thức get và uri "/note/:id/edit". Route này sẽ tìm ghi chú có id bằng với id trong uri để hiển thị thông tin của ghi chú đó. Route thứ hai xử lý thông tin gửi lên của client để cập nhật ghi chú có phương thức là put và uri "/notes/:id". Route này sẽ tìm ghi chú có id tương ứng, cập nhật nội dung và dấu hoàn thành của ghi chú:
... get "/notes/:id/edit" do @note = Note.find params[:id] erb :edit end put "/notes/:id" do @note = Note.find params[:id] if @note.update_attributes(content: params[:content], done: params[:done]) redirect "/" else erb :edit end end
Tạo template edit.erb để hiển thị trang chỉnh sửa:
<div class="col-md-6 col-md-offset-3"> <form action="/notes/<%= @note.id %>" method="post"> <input type="hidden" name="_method" value="put"> <textarea placeholder="Write note here..." class="form-control" name="content"> <%= @note.content %> </textarea> <input type="hidden" name="done" value="0"> <input type="checkbox" name="done" <%= "checked='1'" if @note.done? %>> <label>Done</label> <input type="submit" value="Update" class="btn-primary form-control"> </form> </div>
Do HTML form chỉ hỗ trợ hai phương thức GET và POST nên để sử dụng phương thức PUT hoặc DELETE, ta cần thêm một thẻ <input> kiểu hidden có name là _method và value là put hoặc delete tương ứng. Sinatra sẽ sử dụng các giá trị này để biết được phương thức thực sự.
Một chú ý nữa là khi cập nhật ghi chú mà check box không được tích chọn, giá trị của check box gửi lên server sẽ là null. Nếu cập nhật giá trị này vào CSDL sẽ gây lỗi do trường done có thuộc tính not null. Để xử lý trường hợp này, ta có thể kiểm tra trên server hoặc thêm một thẻ <input> kiểu hidden có cùng name với check box và đặc trước check box. Nếu check box không được tích, giá trị của check box sẽ được thay bằng giá trị của thẻ input này.
Tải lại trang web và sửa một ghi chú bất kỳ:
Xóa ghi chú
Chức năng xóa ghi chú sẽ được xử lý bởi route có phương thức là delete và uri là "/notes/:id". Route sẽ xóa ghi chú có id tương ứng và điều hướng về uri "/". Chúng ta sửa app.rb như sau:
... delete "/notes/:id" do Note.destroy params[:id] redirect "/" end
Thêm form có phương thức delete vào các ghi chú được hiển thị ở template home:
... <a class="btn-xs" href="/notes/<%=note.id %>/edit">Edit</a> <form action="/notes/<%= note.id %>" method="post" class="delete-form"> <input type="hidden" name="_method" value="delete"> <input type="submit" value="Delete" class="btn-xs btn-link"> </form> ...
Chỉnh sửa lại giao diện (style.css):
... .delete-form { display: inline; }
Tải lại trang và xóa một ghi chú bất kỳ:
Bài viết trên đây đã giới thiệu một số tính năng của thư viện Sinatra và hướng dẫn tạo một ứng dụng đơn giản với thư viện này. Bài viết còn đơn giản và có thể còn thiếu sót, rất mong nhận được ý kiến đóng góp của mọi người.
Mã nguồn của ứng dụng có thể xem tại: https://github.com/hieuns/note-app