Download file pdf in Rails with gem pdfkit
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 ...
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 { awidth: 700px; max-awidth: 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; awidth: 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