Download file pdf in Rails with gem pdfkit
Bài đăng này đã không được cập nhật trong 3 năm
1. Giới thiệu
Download file pdf là 1 tính năng được nhiều người ưa chuộng, nó thuận tiện để in các biên lai, hay các thông tin về order,.. Hôm nay mình sẽ giới thiệu cách export ra file pdf bằng gem PDFKit trong rails. Đây là link github của của gem PDFKit
2. PDFKit
Add gem PDFKit in rails app
gem "pdfkit"
Và sử dụng gem wkhtmltopdf tren back-end để renders html using Webkit.
gem "wkhtmltopdf-binary"
Sau đó chạy lệnh bundle để cài đặt gem
Middleware Setup Rails apps
module DemoPdfkit
class Application < Rails::Application
config.middleware.use PDFKit::Middleware
.....
end
end
Usage
# PDFKit.new takes the HTML and any options for wkhtmltopdf
# run `wkhtmltopdf --extended-help` for a full list of options
kit = PDFKit.new(html, :page_size => 'Letter')
kit.stylesheets << '/path/to/css/file'
# Get an inline PDF
pdf = kit.to_pdf
# Save the PDF to a file
file = kit.to_file('/path/to/save/pdf')
# PDFKit.new can optionally accept a URL or a File.
# Stylesheets can not be added when source is provided as a URL of File.
kit = PDFKit.new('http://google.com')
kit = PDFKit.new(File.new('/path/to/html'))
# Add any kind of option through meta tags
PDFKit.new('<html><head><meta name="pdfkit-page_size" content="Letter"')
PDFKit.new('<html><head><meta name="pdfkit-cookie cookie_name1" content="cookie_value1"')
PDFKit.new('<html><head><meta name="pdfkit-cookie cookie_name2" content="cookie_value2"')
Bạn có thể xem thêm trong doc cùa gem. Đến đây ta bắt đầu có thể sử dụng gem pdfkit. Tạo Model, Data, HTML
rails generate model user name
rails generate model payment order_id amount:float user:references
rails db:migrate
User sẽ có nhiều payment
class User < ApplicationRecord
has_many :payments
end
class Payment < ApplicationRecord
belongs_to :user
end
Tạo data seed để demo db/seeds.rb
User.create!([{name: "name1"},{name: "names"}])
10.times do |n|
Payment.create!(
{ order_id: "#{Time.zone.now.strftime('%y%m%d')}-test#{n}", amount: rand(1..100), user_id: rand(1..2) })
end
Tạo User Controller
rails generate controller Users index show
app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
@users = User.all
end
def show
@user = User.find_by id: params[:id]
end
end
Cài đặt routes trong file config/routes
Rails.application.routes.draw do
root "users#index"
resources :users, only: [:index, :show]
end
Bây giờ đến phần xử lý download file pdf payment của user Tách biết phần xử lý pdf này ra khỏi controller, mình tạo 1 services là generate_user_payment_as_pdf.rb
class GenerateUserPaymentAsPDF
def initialize user
@user = user
end
def to_pdf
kit = PDFKit.new(as_html, page_size: 'A4')
kit.to_file("#{Rails.root}/public/invoice.pdf")
end
def filename
"User #{user.id}.pdf"
end
private
attr_reader :user
def as_html
render template: "downloads/show", layout: "invoice_pdf", locals: { user: user }
end
end
Phương thức as_html dùng để tạo ra file HTML Phương thức to_pdf sử dụng PDFKit để lưu file PDF trong thư mục public của rails
** Xây dựng phần layout của file PDF**
app/views/layouts/invoice_pdf.erb
<!DOCTYPE html>
<html>
<head>
<title>Download</title>
<style>
<%= Rails.application.assets.find_asset('application.scss').to_s %>
</style>
</head>
<body>
<%= yield %>
</body>
</html>
app/views/users/_template.html.erb
<div class="invoice">
<h1>User Payments</h1>
<h3>User Name: <%= @user.name %></h3>
<h3>Date: <%= DateTime.now.to_time %></h3>
<div class="receipt__details">
<h2 class="payment-title">Danh sach Payments</h2>
<table class="base-table base-table--standard">
<thead>
<tr>
<th class="base-table__col-name">STT</th>
<th class="base-table__col-name">OrderID</th>
<th class="base-table__col-name">Amount</th>
</tr>
</thead>
<tbody>
<% total = 0 %>
<% @user.payments.each_with_index do |payment, index| %>
<tr>
<td class="base-table__content">
<p><%= index + 1 %></p>
</td>
<td class="base-table__content">
<p><%= payment.order_id %></p>
</td>
<td class="base-table__content">
<p class="base-table__text--right"><%= number_to_currency payment.amount %></p>
</td>
<% total += payment.amount %>
</tr>
<% end %>
<tr class="total">
<td style="text-align: right">Total: </td>
<td><%= number_to_currency total %></span></td>
</tr>
</tbody>
</table>
</div>
app/views/users/index.html.erb
<h2>Danh sach users </h2>
<ul>
<% @users.each do |user| %>
<li>
<%= link_to "#{user.id} - #{user.name} >>", user_path(user) %>
</li>
<% end %>
</ul>
app/views/users/show.html.erb
<%= render "template", user: @user %>
<%= link_to "Download", user_downloads_path(@user, format: "pdf"), target: :_blank %>
app/views/downloads/user_payment.html.erb
<%= render "users/template", user: @user %>
app/assets/stylesheets/users.scss
.invoice {
width: 700px;
max-width: 700px;
border: 1px solid black;
margin: 50px;
padding: 50px;
h1 {
text-align: center;
margin-bottom: 100px;
}
.notes {
margin-top: 100px;
}
.total td {
padding-top: 25px;
padding-left: 100px;
}
p {
margin: 0;
}
.payment-title {
margin-bottom: 10px;
text-align: center;
font-size: 2.2rem;
padding: 5px 15px;
font-weight: 400;
}
@media print {
body * {
visibility: hidden;
}
#receipt * {
visibility: visible;
}
}
.base-table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
background-color: #fff;
border: 2px solid #dedede;
}
.base-table__col-name {
padding: 15px;
font-weight: 400;
font-size: 1.6rem;
text-align: left;
border: 2px solid #dedede;
}
.base-table__content {
padding: 10px;
border: 2px solid #dedede;
vertical-align: middle;
}
.base-table__text--right {
text-align: right;
}
.base-table--standard .base-table__col-name {
padding: 8px;
font-size: 1.4rem;
border: 1px solid #000;
background: #eee;
}
}
Tiếp theo, mình xây dựng downloads_controller.rb để có thể xuất ra file PDF
app/controllers/downloads_controller.rb
class DownloadsController < ApplicationController
before_action :load_user, only: [:index, :show]
def show
respond_to do |format|
format.pdf { send_user_pdf }
format.html { render_sample_html } if Rails.env.development?
end
end
private
def load_user
@user = User.find_by id: params[:user_id]
end
def create_user_pdf
GenerateUserPaymentAsPDF.new user
end
def send_user_pdf
send_file create_user_pdf.to_pdf,
filename: user_pdf.filename,
type: "application/pdf",
disposition: "inline"
end
def render_sample_html
render template: "downloads/user_payment", layout: "invoice_pdf",
locals: { uesr: @user }
end
end
Update lại app/config/routes.rb
Rails.application.routes.draw do
root "users#index"
resources :users, only: [:index, :show] do
resource :downloads, only: :show
end
end
Bây giờ chạy rails s để kiểm tra kết quả: Kết quả show 1 user http://localhost:3000/users/1
kết quả download pdf
All rights reserved