{"id":13411828,"url":"https://github.com/westonganger/spreadsheet_architect","last_synced_at":"2025-04-25T14:49:27.016Z","repository":{"id":43178416,"uuid":"52624124","full_name":"westonganger/spreadsheet_architect","owner":"westonganger","description":"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.","archived":false,"fork":false,"pushed_at":"2025-01-18T01:04:19.000Z","size":1139,"stargazers_count":1345,"open_issues_count":5,"forks_count":43,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-04-09T01:11:04.309Z","etag":null,"topics":["activerecord","csv","excel","excel-export","export","ods","rails","ruby","spreadsheet","xlsx"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/westonganger.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-02-26T18:25:58.000Z","updated_at":"2025-04-03T12:40:35.000Z","dependencies_parsed_at":"2024-06-18T15:26:50.271Z","dependency_job_id":"5fc0c5fd-3168-4658-bd1c-72c8b741bf53","html_url":"https://github.com/westonganger/spreadsheet_architect","commit_stats":{"total_commits":231,"total_committers":12,"mean_commits":19.25,"dds":0.4242424242424242,"last_synced_commit":"4aed34aecc2a8bf38c628755720540a67fd6581e"},"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fspreadsheet_architect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fspreadsheet_architect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fspreadsheet_architect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fspreadsheet_architect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/westonganger","download_url":"https://codeload.github.com/westonganger/spreadsheet_architect/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250562371,"owners_count":21450611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","csv","excel","excel-export","export","ods","rails","ruby","spreadsheet","xlsx"],"created_at":"2024-07-30T20:01:17.319Z","updated_at":"2025-04-24T04:25:20.333Z","avatar_url":"https://github.com/westonganger.png","language":"Ruby","readme":"# Spreadsheet Architect\n\n\u003ca href=\"https://badge.fury.io/rb/spreadsheet_architect\" target=\"_blank\"\u003e\u003cimg height=\"21\" style='border:0px;height:21px;' border='0' src=\"https://badge.fury.io/rb/spreadsheet_architect.svg\" alt=\"Gem Version\"\u003e\u003c/a\u003e\n\u003ca href='https://github.com/westonganger/spreadsheet_architect/actions' target='_blank'\u003e\u003cimg src=\"https://github.com/westonganger/spreadsheet_architect/actions/workflows/test.yml/badge.svg?branch=master\" style=\"max-width:100%;\" height='21' style='border:0px;height:21px;' border='0' alt=\"CI Status\"\u003e\u003c/a\u003e\n\u003ca href='https://rubygems.org/gems/spreadsheet_architect' target='_blank'\u003e\u003cimg height='21' style='border:0px;height:21px;' src='https://img.shields.io/gem/dt/spreadsheet_architect?color=brightgreen\u0026label=Rubygems%20Downloads' border='0' alt='RubyGems Downloads' /\u003e\u003c/a\u003e\n\nSpreadsheet 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.\n\nKey Features:\n\n- Dead simple custom spreadsheets with custom data\n- Data Sources: Tabular Data from an Array, ActiveRecord relations, or array of plain Ruby object instances\n- Easily style and customize spreadsheets\n- Create multi sheet spreadsheets\n- Setting Class/Model or Project specific defaults\n- Simple to use ActionController renderers for Rails\n- Plain Ruby (without Rails) completely supported\n\n# Install\n```ruby\ngem 'spreadsheet_architect'\n```\n\n# General Usage\n\n### Tabular (Array) Data\n\nThe simplest and preferred usage is to simply create the data array yourself.\n\n```ruby\nheaders = ['Col 1','Col 2','Col 3']\ndata = [[1,2,3], [4,5,6], [7,8,9]]\nSpreadsheetArchitect.to_xlsx(headers: headers, data: data)\nSpreadsheetArchitect.to_ods(headers: headers, data: data)\nSpreadsheetArchitect.to_csv(headers: headers, data: data)\n```\n\nUsing this style will allow you to utilize any custom performance optimizations during your data generation process. This will come in handy when the spreadsheets get large and any loss in performance starts to matter.\n\n### Rails Relations or an Array of plain Ruby object instances\n\nIf 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. For example:\n\n```ruby\nclass Post \u003c ApplicationRecord\n  include SpreadsheetArchitect\nend\n```\n\nWhen using on an AR Relation or using the `:instances` option, SpreadsheetArchitect requires an instance method to be defined on the class to generate the data. By default 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. If using the `:data` option this is completely ignored.\n\n```ruby\nclass Post\n  include SpreadsheetArchitect\n\n  def spreadsheet_columns\n    ### Column format is: [Header, Cell Data / Method (if symbol) to Call on each Instance, (optional) Cell Type]\n    [\n      ['Title', :title],\n      ['Content', content.strip],\n      ['Author', (author.name if author)],\n      ['Published?', (published ? 'Yes' : 'No')],\n      :published_at, # uses the method name as header title Ex. 'Published At'\n      ['# of Views', :number_of_views, :float],\n      ['Rating', :rating],\n      ['Category/Tags', \"#{category.name} - #{tags.collect(\u0026:name).join(', ')}\"]\n    ]\n  end\n\nend\n```\n\nThen use it on the class or ActiveRecord relations of the class\n\n```ruby\nposts = Post.order(name: :asc).where(published: true)\nposts.to_xlsx\nposts.to_ods\nposts.to_csv\n\n# Plain Ruby Objects\nposts_array = 10.times.map{|i| Post.new(number: i)}\nPost.to_xlsx(instances: posts_array)\nPost.to_ods(instances: posts_array)\nPost.to_csv(instances: posts_array)\n```\n\nIf you want to use a different method name then `spreadsheet_columns` you can pass a method name to the `:spreadsheet_columns` option.\n\n```ruby\nPost.to_xlsx(instances: posts, spreadsheet_columns: :my_special_method)\n```\n\nAlternatively, you can pass a proc to the `spreadsheet_columns` option. For those purists that really dont want to define any extra `spreadsheet_columns` instance method on your model, this option can help you work with that methodology.\n\n```ruby\nPost.to_xlsx(instances: posts, spreadsheet_columns: -\u003e(instance){\n  [\n    ['Title', :title],\n    ['Content', instance.content.strip],\n    ['Author', (instance.author.name if instance.author)],\n    ['Published?', (instance.published ? 'Yes' : 'No')],\n    :published_at, # uses the method name as header title Ex. 'Published At'\n    ['# of Views', :number_of_views, :float],\n    ['Rating', :rating],\n    ['Category/Tags', \"#{instance.category.name} - #{instance.tags.collect(\u0026:name).join(', ')}\"],\n    ['URL', :url, (val.start_with?(\"http\") ? :hyperlink : :string)],\n  ]\n})\n```\n\n# Sending \u0026 Saving Spreadsheets\n\n### Method 1: Save to a file manually\n\n```ruby\nfile_data = SpreadsheetArchitect.to_xlsx(headers: headers, data: data)\n\nFile.open('path/to/file.xlsx', 'w+b') do |f|\n  f.write file_data\nend\n```\n\n### Method 2: Send Data via Rails Controller\n\n```ruby\nclass PostsController \u003c ActionController::Base\n  respond_to :html, :xlsx, :ods, :csv\n\n  def index\n    @posts = Post.order(published_at: :asc)\n\n    render xlsx: @posts\n  end\n\n  # Using respond_with\n  def index\n    @posts = Post.order(published_at: :asc)\n\n    respond_with @posts\n  end\n\n  # OR Using respond_with with custom options\n  def index\n    @posts = Post.order(published_at: :asc)\n\n    if ['xlsx','ods','csv'].include?(request.format)\n      respond_with @posts.to_xlsx(row_style: {bold: true}), filename: 'Posts'\n    else\n      respond_with @posts\n    end\n  end\n\n  # OR Using responders\n  def index\n    @posts = Post.order(published_at: :asc)\n\n    respond_to do |format|\n      format.html\n      format.xlsx { render xlsx: @posts }\n      format.ods { render ods: @posts }\n      format.csv{ render csv: @posts }\n    end\n  end\n\n  # OR Using responders with custom options\n  def index\n    @posts = Post.order(published_at: :asc)\n\n    respond_to do |format|\n      format.html\n      format.xlsx { render xlsx: @posts.to_xlsx(headers: false) }\n      format.ods { render ods: Post.to_ods(instances: @posts) }\n      format.csv{ render csv: @posts.to_csv(headers: false), filename: 'articles' }\n    end\n  end\nend\n```\n\n# Multi Sheet Spreadsheets\n\n### XLSX\n\n```ruby\naxlsx_package = SpreadsheetArchitect.to_axlsx_package({headers: headers, data: data})\naxlsx_package = SpreadsheetArchitect.to_axlsx_package({headers: headers, data: data}, axlsx_package)\n\nFile.open('path/to/multi_sheet_file.xlsx', 'w+b') do |f|\n  f.write axlsx_package.to_stream.read\nend\n```\n\n### ODS\n```ruby\nods_spreadsheet = SpreadsheetArchitect.to_rodf_spreadsheet({headers: headers, data: data})\nods_spreadsheet = SpreadsheetArchitect.to_rodf_spreadsheet({headers: headers, data: data}, ods_spreadsheet)\n\nFile.open('path/to/multi_sheet_file.ods', 'w+b') do |f|\n  f.write ods_spreadsheet.bytes\nend\n```\n\n# Methods\n\n## `to_xlsx(options={})`\n\n|Option|Default|Notes|\n|---|---|---|\n|**data**\u003cbr\u003e*2D Array*| |Cannot be used with the `:instances` option.\u003cbr\u003e\u003cbr\u003eTabular data for the non-header row cells.  |\n|**instances**\u003cbr\u003e*Array*| |Cannot be used with the `:data` option.\u003cbr\u003e\u003cbr\u003eArray of class/model instances to be used as row data. Cannot be used with :data option|\n|**spreadsheet_columns**\u003cbr\u003e*Proc/Symbol/String*| Use this option to override or define the spreadsheet columns. Normally, if this option is not specified and are using the instances option/ActiveRecord relation, it uses the classes custom `spreadsheet_columns` method or any custom defaults defined.\u003cbr\u003eIf neither of those and is an ActiveRecord model, then it will falls back to the models `self.column_names` | Cannot be used with the `:data` option.\u003cbr\u003e\u003cbr\u003eIf a Proc value is passed it will be evaluated on the instance object.\u003cbr\u003e\u003cbr\u003eIf a Symbol or String value is passed then it will search the instance for a method name that matches and call it. |\n|**headers**\u003cbr\u003e*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. |\n|**sheet_name**\u003cbr\u003e*String*|`Sheet1`||\n|**header_style**\u003cbr\u003e*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](./docs/axlsx_style_reference.md)|\n|**row_style**\u003cbr\u003e*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](./docs/axlsx_style_reference.md)|\n|**column_styles**\u003cbr\u003e*Array*||[See the kitchen sink example for usage](./test/unit/xlsx/general_test.rb)|\n|**range_styles**\u003cbr\u003e*Array*||[See the kitchen sink example for usage](./test/unit/xlsx/general_test.rb)|\n|**conditional_row_styles**\u003cbr\u003e*Array*||[See the kitchen sink example for usage](./test/unit/xlsx/general_test.rb). The if/unless proc will called with the following args: `row_index`, `row_data`|\n|**merges**\u003cbr\u003e*Array*||Merge cells. [See the kitchen sink example for usage](./test/unit/xlsx/general_test.rb). Warning merges cannot overlap eachother, if you attempt to do so Excel will claim your spreadsheet is corrupt and refuse to open your spreadsheet.|\n|**borders**\u003cbr\u003e*Array*||[See the kitchen sink example for usage](./test/unit/xlsx/general_test.rb)|\n|**column_types**\u003cbr\u003e*Array*||Valid types for XLSX are :string, :integer, :float, :date, :time, :boolean, :hyperlink, nil = auto determine. You may also pass a Proc which evaluates to any of the valid types, for example `-\u003e(cell_val){ cell_val.start_with?('http') ? :hyperlink : :string }`|\n|**column_widths**\u003cbr\u003e*Array*||Sometimes you may want explicit column widths. Use nil if you want a column to autofit again.|\n|**freeze_headers**\u003cbr\u003e*Boolean*||Make all header rows frozen/fixed so they do not scroll.|\n|**freeze**\u003cbr\u003e*Hash*||Make all specified row and/or column frozen/fixed so they do not scroll. See [example usage](./test/unit/xlsx_freeze_test.rb)|\n|**skip_defaults**\u003cbr\u003e*Boolean*|`false`|Removes defaults and default styles. Particularily useful for heavily customized spreadsheets where the default styles get in the way.|\n|**escape_formulas**\u003cbr\u003e*Boolean* or *Array*|`true`|Pass a single boolean to apply to all cells, or an array of booleans to control column-by-column. Advisable to be set true when involved with untrusted user input. See [an example of the underlying functionality](https://github.com/caxlsx/caxlsx/blob/master/examples/escape_formula_example.md). NOTE: Header row cells are not escaped. |\n|**use_zero_based_row_index**\u003cbr\u003e*Boolean*|`false`|Allows you to use zero-based row indexes when defining `range_styles`, `merges`, etc. Recommended to set this option for the whole project rather than per call. The original reason it was designed to be 1-based is because spreadsheet row numbers actually start with 1.|\n\n## `to_axlsx_spreadsheet(options={}, axlsx_package_to_join=nil)`\nSame options as `to_xlsx`\n\n## `to_ods(options={})`\n\n|Option|Default|Notes|\n|---|---|---|\n|**data**\u003cbr\u003e*2D Array*| |Cannot be used with the `:instances` option.\u003cbr\u003e\u003cbr\u003eTabular data for the non-header row cells.  |\n|**instances**\u003cbr\u003e*Array*| |Cannot be used with the `:data` option.\u003cbr\u003e\u003cbr\u003eArray of class/model instances to be used as row data. Cannot be used with :data option|\n|**spreadsheet_columns**\u003cbr\u003e*Proc/Symbol/String*| Use this option to override or define the spreadsheet columns. Normally, if this option is not specified and are using the instances option/ActiveRecord relation, it uses the classes custom `spreadsheet_columns` method or any custom defaults defined.\u003cbr\u003eIf neither of those and is an ActiveRecord model, then it will falls back to the models `self.column_names` | Cannot be used with the `:data` option.\u003cbr\u003e\u003cbr\u003eIf a Proc value is passed it will be evaluated on the instance object.\u003cbr\u003e\u003cbr\u003eIf a Symbol or String value is passed then it will search the instance for a method name that matches and call it. |\n|**headers**\u003cbr\u003e*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. |\n|**sheet_name**\u003cbr\u003e*String*|`Sheet1`||\n|**header_style**\u003cbr\u003e*Hash*|`{background_color: \"AAAAAA\", color: \"FFFFFF\", align: :center, font_size: 10, bold: true}`|Note: Currently ODS only supports these options|\n|**row_style**\u003cbr\u003e*Hash*|`{background_color: nil, color: \"000000\", align: :left, font_size: 10, bold: false}`|Styles for non-header rows. Currently ODS only supports these options|\n|**column_types**\u003cbr\u003e*Array*||Valid types for ODS are :string, :float, :date, :time, :boolean, :hyperlink, nil = auto determine. Due to [RODF Issue #19](https://github.com/thiagoarrais/rodf/issues/19), :date/:time will be converted to :string. You may also pass a Proc which evaluates to any of the valid types, for example `-\u003e(cell_val){ cell_val.start_with?('http') ? :hyperlink : :string }` |\n|**skip_defaults**\u003cbr\u003e*Boolean*|`false`|Skip defaults and default styles. Particularly useful for heavily customized spreadsheets where the default styles get in the way.|\n\n## `to_rodf_spreadsheet(options={}, spreadsheet_to_join=nil)`\nSame options as `to_ods`\n\n## `to_csv(options={})`\n\n|Option|Default|Notes|\n|---|---|---|\n|**data**\u003cbr\u003e*2D Array*| |Cannot be used with the `:instances` option.\u003cbr\u003e\u003cbr\u003eTabular data for the non-header row cells.  |\n|**instances**\u003cbr\u003e*Array*| |Cannot be used with the `:data` option.\u003cbr\u003e\u003cbr\u003eArray of class/model instances to be used as row data. Cannot be used with :data option|\n|**spreadsheet_columns**\u003cbr\u003e*Proc/Symbol/String*| Use this option to override or define the spreadsheet columns. Normally, if this option is not specified and are using the instances option/ActiveRecord relation, it uses the classes custom `spreadsheet_columns` method or any custom defaults defined.\u003cbr\u003eIf neither of those and is an ActiveRecord model, then it will falls back to the models `self.column_names` | Cannot be used with the `:data` option.\u003cbr\u003e\u003cbr\u003eIf a Proc value is passed it will be evaluated on the instance object.\u003cbr\u003e\u003cbr\u003eIf a Symbol or String value is passed then it will search the instance for a method name that matches and call it. |\n|**headers**\u003cbr\u003e*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. |\n\n\n# Change class-wide default method options\n\n```ruby\nclass Post \u003c ApplicationRecord\n  include SpreadsheetArchitect\n\n  def spreadsheet_columns\n    [:name, :content]\n  end\n\n  SPREADSHEET_OPTIONS = {\n    headers: [\n      ['My Post Report'],\n      self.column_names.map{|x| x.titleize}\n    ],\n     spreadsheet_columns: :spreadsheet_columns,\n    header_style: {background_color: 'AAAAAA', color: 'FFFFFF', align: :center, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false},\n    row_style: {background_color: nil, color: '000000', align: :left, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false},\n    sheet_name: self.name,\n    column_styles: [],\n    range_styles: [],\n    conditional_row_styles: [],\n    merges: [],\n    borders: [],\n    column_types: [],\n  }\nend\n```\n\n# Change project-wide default method options\n\n```ruby\n# config/initializers/spreadsheet_architect.rb\n\nSpreadsheetArchitect.default_options = {\n  headers: true,\n  spreadsheet_columns: :spreadsheet_columns,\n  header_style: {background_color: 'AAAAAA', color: 'FFFFFF', align: :center, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false},\n  row_style: {background_color: nil, color: '000000', align: :left, font_name: 'Arial', font_size: 10, bold: false, italic: false, underline: false},\n  sheet_name: 'My Project Export',\n  column_styles: [],\n  range_styles: [],\n  conditional_row_styles: [],\n  merges: [],\n  borders: [],\n  column_types: [],\n  use_zero_based_row_index: false,\n}\n```\n\n# Kitchen Sink Examples with Styling for XLSX and ODS\n\nSee `test \"kitchen sink\"` for [XLSX](./test/unit/xlsx/general_test.rb) and [ODS](./test/unit/ods/general_test.rb)\n\n# Axlsx Style Reference\n\nI have compiled a list of all available style options for axlsx here: [docs/axlsx_style_reference.md](./docs/axlsx_style_reference.md)\n\n# Tips for Reducing Memory Usage\n- Use the `:data` option instead of active record relations\n- Utilize the [`light_record`](https://github.com/Paxa/light_record) gem\n\n# Testing / Validating your Spreadsheets\n\nA 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.\n\n# Contributing\n\nWe use the `appraisal` gem for testing multiple versions of `axlsx`. Please use the following steps to test using `appraisal`.\n\n1. `bundle exec appraisal install`\n2. `bundle exec appraisal rake test`\n\nAt this time the spreadsheets generated by the test suite are manually inspected. After running the tests, the test output can be viewed in `tmp/`\n\n# Credits\n\nCreated \u0026 Maintained by [Weston Ganger](https://westonganger.com) - [@westonganger](https://github.com/westonganger)\n","funding_links":[],"categories":["Ruby","Spreadsheets and Documents"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwestonganger%2Fspreadsheet_architect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwestonganger%2Fspreadsheet_architect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwestonganger%2Fspreadsheet_architect/lists"}