Spreadsheet Architect is a library that allows you to create XLSX, ODS, or CSV spreadsheets super easily from ActiveRecord relations, plain Ruby objects, or tabular data.
Key Features:
- Dead simple custom spreadsheets with custom data
- Data Sources: ActiveRecord relations, array of plain Ruby object instances, or tabular 2D Array Data
- Easily style and customize spreadsheets
- Create multi sheet spreadsheets
- Setting Class/Model or Project specific defaults
- Simple to use ActionController renderers for Rails
- Plain Ruby (without Rails) completely supported
gem 'spreadsheet_architect'
headers = ['Col 1','Col 2','Col 3']
data = [[1,2,3], [4,5,6], [7,8,9]]
SpreadsheetArchitect.to_xlsx(headers: headers, data: data)
SpreadsheetArchitect.to_ods(headers: headers, data: data)
SpreadsheetArchitect.to_csv(headers: headers, data: data)
posts = Post.order(name: :asc).where(published: true)
# OR
posts = 10.times.map{|i| Post.new(number: i)}
SpreadsheetArchitect.to_xlsx(instances: posts)
SpreadsheetArchitect.to_ods(instances: posts)
SpreadsheetArchitect.to_csv(instances: posts)
(Optional) If you would like to add the methods to_xlsx
, to_ods
, to_csv
, to_axlsx_package
, to_rodf_spreadsheet
to some class, you can simply include the SpreadsheetArchitect module to whichever classes you choose. A good default strategy is to simply add it to the ApplicationRecord or another parent class to have it available on all appropriate classes. For example:
class ApplicationRecord < ActiveRecord::Base
include SpreadsheetArchitect
end
Then use it on the class or ActiveRecord relations of the class
posts = Post.order(name: :asc).where(published: true)
posts.to_xlsx
posts.to_ods
posts.to_csv
# Plain Ruby Objects
posts_array = 10.times.map{|i| Post.new(number: i)}
Post.to_xlsx(instances: posts_array)
Post.to_ods(instances: posts_array)
Post.to_csv(instances: posts_array)
When NOT using the :data
option, ie. on an AR Relation or using the :instances
option, Spreadsheet Architect requires an instance method defined on the class to generate the data. It looks for the spreadsheet_columns
method on the class. If you are using on an ActiveRecord model and that method is not defined, it would fallback to the models column_names
method (not recommended). If using the :data
option this is ignored.
class Post
def spreadsheet_columns
### Column format is: [Header, Cell Data / Method (if symbol) to Call on each Instance, (optional) Cell Type]
[
['Title', :title],
['Content', content.strip],
['Author', (author.name if author)],
['Published?', (published ? 'Yes' : 'No')],
:published_at, # uses the method name as header title Ex. 'Published At'
['# of Views', :number_of_views, :float],
['Rating', :rating],
['Category/Tags', "#{category.name} - #{tags.collect(&:name).join(', ')}"]
]
end
Alternatively, if spreadsheet_columns
is passed as an option, this instance method does not need to be defined on the class. If defined on the class then naturally this will override it.
Post.to_xlsx(instances: posts, spreadsheet_columns: Proc.new{|instance|
[
['Title', :title],
['Content', instance.content.strip],
['Author', (instance.author.name if instance.author)],
['Published?', (instance.published ? 'Yes' : 'No')],
:published_at, # uses the method name as header title Einstance. 'Published At'
['# of Views', :number_of_views, :float],
['Rating', :rating],
['Category/Tags', "#{instance.category.name} - #{instance.tags.collect(&:name).join(', ')}"]
]
})
class PostsController < ActionController::Base
respond_to :html, :xlsx, :ods, :csv
def index
@posts = Post.order(published_at: :asc)
render xlsx: @posts
end
# Using respond_with
def index
@posts = Post.order(published_at: :asc)
respond_with @posts
end
# OR Using respond_with with custom options
def index
@posts = Post.order(published_at: :asc)
if ['xlsx','ods','csv'].include?(request.format)
respond_with @posts.to_xlsx(row_style: {bold: true}), filename: 'Posts'
else
respond_with @posts
end
end
# OR Using responders
def index
@posts = Post.order(published_at: :asc)
respond_to do |format|
format.html
format.xlsx { render xlsx: @posts }
format.ods { render ods: @posts }
format.csv{ render csv: @posts }
end
end
# OR Using responders with custom options
def index
@posts = Post.order(published_at: :asc)
respond_to do |format|
format.html
format.xlsx { render xlsx: @posts.to_xlsx(headers: false) }
format.ods { render ods: Post.to_ods(instances: @posts) }
format.csv{ render csv: @posts.to_csv(headers: false), file_name: 'articles' }
end
end
end
### Ex. with ActiveRecord relation
file_data = Post.order(published_at: :asc).to_xlsx
File.open('path/to/file.xlsx', 'w+b') do |f|
f.write file_data
end
file_data = Post.order(published_at: :asc).to_ods
File.open('path/to/file.ods', 'w+b') do |f|
f.write file_data
end
file_data = Post.order(published_at: :asc).to_csv
File.open('path/to/file.csv', 'w+b') do |f|
f.write file_data
end
axlsx_package = SpreadsheetArchitect.to_axlsx_package({headers: headers, data: data})
axlsx_package = SpreadsheetArchitect.to_axlsx_package({headers: headers, data: data}, package)
File.open('path/to/file.xlsx', 'w+b') do |f|
f.write axlsx_package.to_stream.read
end
See this file for more details: https://github.com/westonganger/spreadsheet_architect/blob/master/test/spreadsheet_architect/multi_sheet_test.rb
ods_spreadsheet = SpreadsheetArchitect.to_rodf_spreadsheet({headers: headers, data: data})
ods_spreadsheet = SpreadsheetArchitect.to_rodf_spreadsheet({headers: headers, data: data}, spreadsheet)
File.open('path/to/file.ods', 'w+b') do |f|
f.write ods_spreadsheet
end
See this file for more details: https://github.com/westonganger/spreadsheet_architect/blob/master/test/spreadsheet_architect/multi_sheet_test.rb
Option | Default | Notes |
---|---|---|
data 2D Array |
Cannot be used with the :instances option.Tabular data for the non-header row cells. |
|
instances Array |
Cannot be used with the :data option.Array of class/model instances to be used as row data. Cannot be used with :data option |
|
spreadsheet_columns Array |
If using the instances option or on a ActiveRecord relation, this defaults to the classes custom spreadsheet_columns method or any custom defaults defined.If none of those then falls back to self.column_names for ActiveRecord models. |
Cannot be used with the :data option.Use this option to override or define the spreadsheet columns. |
headers Array / 2D Array |
Data for the header row cells. If using on a class/relation, this defaults to the ones provided via spreadsheet_columns . Pass false to skip the header row. |
|
sheet_name String |
Sheet1 |
|
header_style Hash |
{background_color: "AAAAAA", color: "FFFFFF", align: :center, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false} |
See all available style options here |
row_style Hash |
{background_color: nil, color: "000000", align: :left, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false, format_code: nil} |
Styles for non-header rows. See all available style options here |
column_styles Array |
See this example for usage | |
range_styles Array |
See this example for usage | |
merges Array |
Merge cells. See this example for usage. Warning merges cannot overlap eachother, if you attempt to do so Excel will claim your spreadsheet is corrupt and refuse to open your spreadsheet. | |
borders Array |
See this example for usage | |
column_types Array |
Valid types for XLSX are :string, :integer, :float, :boolean, nil = auto determine. | |
column_widths Array |
Sometimes you may want explicit column widths. Use nil if you want a column to autofit again. |
Same options as to_xlsx
Option | Default | Notes |
---|---|---|
data 2D Array |
Cannot be used with the :instances option.Tabular data for the non-header row cells. |
|
instances Array |
Cannot be used with the :data option.Array of class/model instances to be used as row data. Cannot be used with :data option |
|
spreadsheet_columns Array |
If using the instances option or on a ActiveRecord relation, this defaults to the classes custom spreadsheet_columns method or any custom defaults defined.If none of those then falls back to self.column_names for ActiveRecord models. |
Cannot be used with the :data option.Use this option to override or define the spreadsheet columns. |
headers Array / 2D Array |
Data for the header row cells. If using on a class/relation, this defaults to the ones provided via spreadsheet_columns . Pass false to skip the header row. |
|
sheet_name String |
Sheet1 |
|
header_style Hash |
{background_color: "AAAAAA", color: "FFFFFF", align: :center, font_size: 10, bold: true} |
Note: Currently ODS only supports these options |
row_style Hash |
{background_color: nil, color: "000000", align: :left, font_size: 10, bold: false} |
Styles for non-header rows. Currently ODS only supports these options |
column_types Array |
Valid types for ODS are :string, :float :percent, :currency, :date, :time,, nil = auto determine. Due to RODF Issue #19, :date/:time will be converted to :string |
Same options as to_ods
Option | Default | Notes |
---|---|---|
data 2D Array |
Cannot be used with the :instances option.Tabular data for the non-header row cells. |
|
instances Array |
Cannot be used with the :data option.Array of class/model instances to be used as row data. Cannot be used with :data option |
|
spreadsheet_columns Array |
If using the instances option or on a ActiveRecord relation, this defaults to the classes custom spreadsheet_columns method or any custom defaults defined.If none of those then falls back to self.column_names for ActiveRecord models. |
Cannot be used with the :data option.Use this option to override or define the spreadsheet columns. |
headers Array / 2D Array |
Data for the header row cells. If using on a class/relation, this defaults to the ones provided via spreadsheet_columns . Pass false to skip the header row. |
class Post
def spreadsheet_columns
[:name, :content]
end
SPREADSHEET_OPTIONS = {
headers: [
['My Post Report'],
self.column_names.map{|x| x.titleize}
],
header_style: {background_color: 'AAAAAA', color: 'FFFFFF', align: :center, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false},
row_style: {background_color: nil, color: '000000', align: :left, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false},
sheet_name: self.name,
column_styles: [],
range_styles: [],
merges: [],
borders: [],
column_types: [],
}
end
# config/initializers/spreadsheet_architect.rb
SpreadsheetArchitect.default_options = {
headers: true,
header_style: {background_color: 'AAAAAA', color: 'FFFFFF', align: :center, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false},
row_style: {background_color: nil, color: '000000', align: :left, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false},
sheet_name: 'My Project Export',
column_styles: [],
range_styles: [],
merges: [],
borders: [],
column_types: [],
}
See this example: https://github.com/westonganger/spreadsheet_architect/blob/master/test/spreadsheet_architect/kitchen_sink_test.rb
I have compiled a list of all available style options for axlsx here: https://github.com/westonganger/spreadsheet_architect/blob/master/docs/axlsx_style_reference.md
A wise word of advice, when testing your spreadsheets I recommend to use Excel instead of LibreOffice. This is because I have seen through testing, that where LibreOffice seems to just let most incorrect things just slide on through, Excel will not even open the spreadsheet as apparently it is much more strict about the spreadsheet validations. This will help you better identify any incorrect styling or customization issues.
We use the appraisal
gem for testing multiple versions of axlsx
. Please use the following steps to test using appraisal
.
bundle exec appraisal install
bundle exec appraisal rake test
Created & Maintained by Weston Ganger - @westonganger
For any consulting or contract work please contact me via my company website: Solid Foundation Web Development