You have probably generated CSV files in your Rails applications based on some data. Those files can then be converted to Excel format in order to apply some styling, formatting, add graphs, etc. However, wouldn’t it be more convenient to automate all these tasks and generate .xlsx files instead? With the axlsx gem created by Randy Morgan that it is totally possible!
This gem provides a huge list of features allowing you to do nearly anything from adding simple formatting rules to generating graphs and setting options for printing. Many different examples can be found on GitHub. What’s more, there are other additional gems available:
- axlsx_rails – provides Rails templates for axlsx. A really nice solution that we will be using in this article
- acts_as_axlsx – special ActiveRecord mixin equipping your models with the
to_xlsx
method - activeadmin-xlsx – plugin for ActiveAdmin, which was mentioned in a SitePoint article on ActiveAdmin.
Today we are going to see how to generate and customize Excel files in Rails apps with the help of axlsx gem.
The source code is available on GitHub.
Creating an App
For this demo I will be using Rails 5 (it is still in beta though), but axlsx_rails works with both Rails 3 and 4. axlsx itself works with Ruby 1.9.2 and higher.
Let’s call our demo app “ExcelNinja”:
$ rails new ExcelNinja -T
We will store some sample data in the database and then generate Excel files based on it, so add a new Product
model:
$ rails g model Product title:string price:decimal
$ rake db:migrate
I’m going to take advantage of seeds.rb to populate this new table with sample data. However, I also want my products to look more or less realistic, so let’s include the faker gem. It can generate nearly anything from product prices to book titles and pseudo-IT phrases.
Gemfile
[...]
gem 'faker'
[...]
Run
$ bundle install
and tweak the seeds.rb file:
db/seeds.rb
30.times do
Product.create({title: Faker::Commerce.product_name, price: Faker::Commerce.price})
end
Now populate the table:
$ rake db:seed
Okay, the last thing to do is to set up some routes and create a controller and view.
config/routes.rb
[...]
resources :products, only: [:index]
root 'products#index'
[...]
products_controller.rb
class ProductsController < ApplicationController
def index
@products = Product.order('created_at DESC')
end
end
views/products/index.html.erb
<h1>List of products</h1>
I won’t really list anything in this view – instead we will present users with a link to download a formatted Excel file, so proceed to the next section.
Generating Excel File
Add a new gem into your Gemfile:
Gemfile
[...]
gem 'axlsx_rails'
[...]
and run
$ bundle install
Having this gem in place, you can introduce a new response format like this:
products_controller.rb
[...]
def index
@products = Product.order('created_at DESC')
respond_to do |format|
format.html
format.xlsx
end
end
[...]
Present a new link with the corresponding format:
views/products/index.html.erb
<h1>List of products</h1>
<%= link_to 'Download as .xlsx', products_path(format: :xlsx) %>
Now create a new template called index.xlsx.axlsx (yeah, the extension’s name is complex so make sure you type it correctly). This will be a plain Ruby file with instructions to generate the .xlsx. Inside the view, a xlsx_package
local variable is available:
views/products/index.xlsx.axlsx
wb = xlsx_package.workbook
This way we simply instantiate a new Excel workbook. Each workbook consists of sheets, so let’s add one:
views/products/index.xlsx.axlsx
wb = xlsx_package.workbook
wb.add_worksheet(name: "Products") do |sheet|
end
Inside this block you define the contents of this sheet:
views/products/index.xlsx.axlsx
wb = xlsx_package.workbook
wb.add_worksheet(name: "Products") do |sheet|
@products.each do |product|
sheet.add_row [product.title, product.price]
end
end
So with add_row
we create a new row that contains product’s title and price. name: "Products"
sets the sheet’s title.
Boot up the server, navigate to the root page, and click the “Download as .xlsx” link. That was simple, wasn’t it?
To rename the generated file, use the following code:
products_controller.rb
[...]
respond_to do |format|
format.html
format.xlsx {
response.headers['Content-Disposition'] = 'attachment; filename="all_products.xlsx"'
}
end
[...]
Alternatively, you may use a render
method with various options:
render xlsx: 'products', template: 'my/template', filename: "my_products.xlsx", disposition: 'inline',
xlsx_created_at: 3.days.ago, xlsx_author: "Elmer Fudd"
What’s more, Excel files can be generated inside mailers – read more here.
Styling
Currently our sheet looks a bit boring, so let’s add some background color for the prices. To do this, initialize a new variable with styles inside a block:
index.xlsx.axlsx
wb = xlsx_package.workbook
wb.styles do |style|
highlight_cell = style.add_style(bg_color: "EFC376")
wb.add_worksheet(name: "Products") do |sheet|
@products.each do |product|
sheet.add_row [product.title, product.price], style: [nil, highlight_cell]
end
end
end
Using the add_style
method, we introduce styling rules that can later be applied to one or more cells. When calling the add_row
we simply pass the :style
option that accept styles for every cell. As long as I do not want to style the first column (that contains product’s title), I simply specify nil
as the first array’s element. The second element contains our custom style that sets orange background for the product’s price.
There are much more styles that can be set (refer to the examples for more information). For instance, let’s add a thin border for price and center the contents horizontally:
index.xlsx.axlsx
[...]
highlight_cell = style.add_style(bg_color: "EFC376",
border: Axlsx::STYLE_THIN_BORDER,
alignment: { horizontal: :center })
[...]
You can easily override styles for any cell:
sheet.rows.last.cells.first.u = :single
# Or
sheet.rows.last.cells[0].u = :single
Basically, rows and cells are organized as array’s elements. The u
method makes the cell’s contents underlined (for some reason this does not work in LibreOffice though – find other known issues here).
Formatting
Okay, now I also want to display the creation date for every product. Modify the add_row
method call:
index.xlsx.axlsx
sheet.add_row [product.title, product.price, product.created_at], style: [nil, highlight_cell]
However, as you remember, the created_at
column stores information in a pretty ugly and non-friendly format. How about adding a formatting rule for this cell? It also can be done via the styles:
date_cell = style.add_style(format_code: "yyyy-mm-dd", border: Axlsx::STYLE_THIN_BORDER)
I’ve also added a thin border just like we did for prices. Styles and formatting rules can be mixed together without any problems.
Now apply the new styling rules:
index.xlsx.axlsx
sheet.add_row [product.title, product.price, product.created_at], style: [nil, highlight_cell, date_cell]
Let’s also add formatting rules for the prices:
index.xlsx.axlsx
highlight_cell = style.add_style(bg_color: "EFC376",
border: Axlsx::STYLE_THIN_BORDER,
alignment: { horizontal: :center },
:format_code => '#.##')
Instead you may use one of the pre-defined formats:
highlight_cell = style.add_style(bg_color: "EFC376",
border: Axlsx::STYLE_THIN_BORDER,
alignment: { horizontal: :center },
num_fmt: 8)
Adding Hyperlinks
You can add a hyperlink to any cell by using the add_hyperlink
method and providing a reference to the desired cell. Suppose we want product titles to lead to the show
action of the ProductsController
. Let’s quickly add the new method, view, and route:
config/routes.rb
[...]
resources :products, only: [:index, :show]
[...]
products_controller.rb
[...]
def show
@product = Product.find(params[:id])
end
[...]
views/products/show.html.erb
<h1><%= @product.title %></h1>
<p>Price: <%= @product.price %></p>
Now add the hyperlink:
index.xlsx.axlsx
[...]
@products.each do |product|
sheet.add_row [product.title, product.price, product.created_at],
style: [nil, highlight_cell, date_cell]
sheet.add_hyperlink location: url_for(action: "show", id: product.id, only_path: false),
ref: sheet.rows.last.cells.first
[...]
end
[...]
There are couple things to note here. The url_for
method generates the proper link. However, we must set the :only_path
option to false
, otherwise this method will generate a relative path that’s obviously incorrect. The :ref
option specifies the address of the hyperlink. We already learned that cells can be accessed just like array’s elements. As long as we call add_hyperlink
inside the cycle, the sheet.rows.last
will always point to the newly added row and sheet.rows.last.cells.first
, in turn, points to the cell with the product’s title.
Hyperlinks can also point to other cells and sheets. Suppose you add some other sheet to your workbook:
wb.add_worksheet(name: 'Another Sheet') do |sheet|
sheet.add_row ['a cell']
end
You want links to point to the B2 cell of this new sheet:
sheet.add_hyperlink location: "'Another Sheet'!B2", ref: sheet.rows.last.cells.first, target: :sheet
Just provide the sheet’s name, the cell’s number, and set the :target
option to :sheet
.
Adding Formulas
You can easily add Excel formulas with axlsx. Suppose we want to calculate the total price of all our products. Of course, this can be done directly inside the index.xlsx.axlsx file but that’s not really optimal. Instead, let’s employ the SUM
function.
Add a new line of code just after the cycle:
index.xlsx.axlsx
[...]
@products.each do |product|
[...]
end
sheet.add_row ['Total', "=SUM(B1:B#{@products.length})"]
[...]
We don’t know how many products will be stored inside the database, so use interpolation to generate the proper row number.
Adding Charts
axlsx supports all major chart types: bar, line, pie, and scatter. For this demo, let’s create a 3D bar chart illustrating product price. Use the add_chart
method:
index.xlsx.axlsx
[...]
sheet.add_chart(Axlsx::Bar3DChart, start_at: "D1", end_at: "N40") do |chart|
end
[...]
:start_at
specifies the top-left corner of the chart whereas :end_at
– the bottom-right one. If you have many products in your database, these values will have to be tweaked accordingly – otherwise the chart will be too small. You can also introduce a simple formula like this one:
index.xlsx.axlsx
sheet.add_chart(Axlsx::Bar3DChart, start_at: "D1", end_at: "N#{(@products.length * 1.5).floor}") do |chart|
end
Inside the block simply describe the chart by adding data series, providing labels, and styling rules:
index.xlsx.axlsx
[...]
sheet.add_chart(Axlsx::Bar3DChart, start_at: "D1", end_at: "N#{(@products.length * 1.5).floor}") do |chart|
chart.add_series data: sheet["B1:B#{@products.length}"],
labels: sheet["A1:A#{@products.length}"],
title: "Products by price",
colors: @products.map { "00FF00" }
end
[...]
:data
says where to get the chart’s data.:labels
specifies where to load labels for each series.:title
specifies the heading for the chart itself.:colors
specifies which color to use for each series.
I found out that for LibreOffice you have to provide colors for all series, otherwise they won’t be displayed at all.
You can also hide grid lines easily:
index.xlsx.axlsx
sheet.add_chart(Axlsx::Bar3DChart, start_at: "D1", end_at: "N#{(@products.length * 1.5).floor}") do |chart|
chart.add_series data: sheet["B1:B#{@products.length}"],
labels: sheet["A1:A#{@products.length}"],
title: "Products by price",
colors: @products.map { "00FF00" }
chart.valAxis.gridlines = false
chart.catAxis.gridlines = false
end
Apparently, these settings do not have any effect in LibreOffice, as it does not display grid lines in any case.
Conclusion
In this article, we’ve discussed the axlsx gem that allows you to easily generate Excel files. We’ve also taken advantage of axlsx_rails to integrate with Rails. As I already said, there is much more you can do with with axlsx: merge columns, add conditional formatting, add headers and footers, hide or protect sheets, etc. Many use-cases can be found in the examples file. The gem is also documented with YARD, so you can follow instructions here to be able to read it.
Unfortunately, axlsx is not ideal and does have some known interoperability issues with LibreOffice and Google Docs (we’ve actually stumbled upon some of them in this demo). The list of these issues can be found here.
Still, those issues are pretty minor and all in all axlsx is a really great solution and I do recommend trying it out. Feel free to post your questions in the comments if something is not working for you. Thanks for staying with me and see you soon!
Frequently Asked Questions (FAQs) about Generating Excel Spreadsheets with Rails Axlsx Gem
What is the Rails Axlsx Gem and how does it work?
The Rails Axlsx Gem is a Ruby library that allows developers to create Excel files with ease. It provides a simple and intuitive API that enables you to write data in rows and columns, apply styles, and add charts, images, and formulas. The gem works by converting Ruby code into an Excel-readable format, which can then be opened and edited in Excel.
How do I install the Axlsx Gem in my Rails application?
To install the Axlsx Gem, you need to add it to your Gemfile. Open your Gemfile and add the following line: gem 'axlsx'
. Then, run bundle install
in your terminal. This will install the gem and make it available for use in your Rails application.
How can I generate an Excel spreadsheet using the Axlsx Gem?
To generate an Excel spreadsheet, you first need to require the Axlsx Gem in your controller or model. Then, you can use the Axlsx::Package.new
method to create a new Excel file. You can add data to the file using the add_row
method, and save the file using the serialize
method.
Can I apply styles to cells using the Axlsx Gem?
Yes, the Axlsx Gem allows you to apply styles to cells. You can define a style using the styles.add_style
method, and apply it to a cell using the add_row
method with the :style
option.
How can I add charts to my Excel file using the Axlsx Gem?
The Axlsx Gem provides a add_chart
method that allows you to add charts to your Excel file. You can specify the type of chart, the data for the chart, and the position of the chart in the spreadsheet.
Can I generate Excel files with multiple worksheets using the Axlsx Gem?
Yes, the Axlsx Gem allows you to create Excel files with multiple worksheets. You can use the add_worksheet
method to add a new worksheet to your Excel file.
How can I add formulas to cells using the Axlsx Gem?
The Axlsx Gem allows you to add formulas to cells. You can use the add_row
method with the :formula
option to add a formula to a cell.
Can I generate Excel files with images using the Axlsx Gem?
Yes, the Axlsx Gem allows you to add images to your Excel file. You can use the add_image
method to add an image to a worksheet.
How can I generate Excel files with custom formats using the Axlsx Gem?
The Axlsx Gem allows you to create Excel files with custom formats. You can define a custom format using the styles.add_style
method with the :format_code
option.
Can I generate Excel files with hyperlinks using the Axlsx Gem?
Yes, the Axlsx Gem allows you to add hyperlinks to your Excel file. You can use the add_hyperlink
method to add a hyperlink to a cell.
Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website, participates in OpenSource projects, goes in for sports and plays music.