├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Guardfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── caxlsx_rails.gemspec ├── lib ├── axlsx_rails.rb ├── axlsx_rails │ ├── action_controller.rb │ ├── railtie.rb │ ├── template_handler.rb │ └── version.rb ├── caxlsx_rails.rb └── tasks │ └── axlsx_rails_tasks.rake └── spec ├── axlsx_builder_spec.rb ├── axlsx_mailer_spec.rb ├── axlsx_renderer_spec.rb ├── axlsx_request_spec.rb ├── gemfiles ├── Gemfile.rails-4.2 ├── Gemfile.rails-5.0 ├── Gemfile.rails-5.1 ├── Gemfile.rails-5.2 ├── Gemfile.rails-6.0 ├── Gemfile.rails-6.1 ├── Gemfile.rails-7.0 ├── Gemfile.rails-7.1 └── Gemfile.rails-7.2 ├── rails_app ├── Rakefile ├── app │ ├── assets │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ ├── application_controller.rb │ │ ├── home_controller.rb │ │ ├── likes_controller.rb │ │ └── users_controller.rb │ ├── mailers │ │ └── notifier.rb │ ├── models │ │ ├── like.rb │ │ └── user.rb │ └── views │ │ ├── home │ │ ├── _cover_sheet.xlsx.axlsx │ │ ├── index.html.erb │ │ ├── index.xlsx.axlsx │ │ ├── only_html.html.erb │ │ ├── useheader.xlsx.axlsx │ │ └── withpartial.xlsx.axlsx │ │ ├── layouts │ │ ├── application.html.erb │ │ └── users.html.erb │ │ ├── likes │ │ ├── index.html.erb │ │ └── index.xlsx.axlsx │ │ ├── notifier │ │ ├── instructions.html.erb │ │ └── instructions.txt.erb │ │ └── users │ │ ├── export.xlsx.axlsx │ │ ├── index.html.erb │ │ ├── index.xlsx.axlsx │ │ ├── noaction.xlsx.axlsx │ │ ├── respond_with.xlsx.axlsx │ │ └── send_instructions.xlsx.axlsx ├── bin │ ├── bundle │ ├── rails │ └── rake ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── routes.rb │ └── secrets.yml ├── db │ ├── migrate │ │ ├── 20120717192452_create_users.rb │ │ └── 20121206210955_create_likes.rb │ └── schema.rb ├── log │ └── .keep └── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── favicon.ico ├── spec_helper.rb └── support └── mime_type_helper.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | 8 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: ['*'] 5 | pull_request: 6 | branches: ['*'] 7 | 8 | jobs: 9 | test_ruby_and_rails_versions: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | rails: 16 | - "4.2" 17 | - "5.0" 18 | - "5.1" 19 | - "5.2" 20 | - "6.0" 21 | - "6.1" 22 | - "7.0" 23 | - "7.1" 24 | - "7.2" 25 | 26 | ruby: 27 | - "2.4" 28 | - "2.5" 29 | - "2.6" 30 | - "2.7" 31 | - "3.0" 32 | - "3.1" 33 | - "3.2" 34 | - "3.3" 35 | 36 | include: 37 | - rails: "4.2" 38 | bundler: "1.17.3" 39 | 40 | exclude: 41 | - rails: "4.2" 42 | ruby: "2.7" 43 | 44 | - rails: "4.2" 45 | ruby: "3.0" 46 | 47 | - rails: "4.2" 48 | ruby: "3.1" 49 | 50 | - rails: "4.2" 51 | ruby: "3.2" 52 | 53 | - rails: "4.2" 54 | ruby: "3.3" 55 | 56 | - rails: "5.0" 57 | ruby: "3.0" 58 | 59 | - rails: "5.0" 60 | ruby: "3.1" 61 | 62 | - rails: "5.0" 63 | ruby: "3.2" 64 | 65 | - rails: "5.0" 66 | ruby: "3.3" 67 | 68 | - rails: "5.1" 69 | ruby: "3.0" 70 | 71 | - rails: "5.1" 72 | ruby: "3.1" 73 | 74 | - rails: "5.1" 75 | ruby: "3.2" 76 | 77 | - rails: "5.1" 78 | ruby: "3.3" 79 | 80 | - rails: "5.2" 81 | ruby: "3.0" 82 | 83 | - rails: "5.2" 84 | ruby: "3.1" 85 | 86 | - rails: "5.2" 87 | ruby: "3.2" 88 | 89 | - rails: "5.2" 90 | ruby: "3.3" 91 | 92 | - rails: "6.0" 93 | ruby: "2.4" 94 | 95 | - rails: "6.0" 96 | ruby: "3.1" 97 | 98 | - rails: "6.0" 99 | ruby: "3.2" 100 | 101 | - rails: "6.0" 102 | ruby: "3.3" 103 | 104 | - rails: "6.1" 105 | ruby: "2.4" 106 | 107 | - rails: "7.0" 108 | ruby: "2.4" 109 | 110 | - rails: "7.0" 111 | ruby: "2.5" 112 | 113 | - rails: "7.0" 114 | ruby: "2.6" 115 | 116 | - rails: "7.1" 117 | ruby: "2.4" 118 | 119 | - rails: "7.1" 120 | ruby: "2.5" 121 | 122 | - rails: "7.1" 123 | ruby: "2.6" 124 | 125 | - rails: "7.2" 126 | ruby: "2.4" 127 | 128 | - rails: "7.2" 129 | ruby: "2.5" 130 | 131 | - rails: "7.2" 132 | ruby: "2.6" 133 | 134 | - rails: "7.2" 135 | ruby: "2.7" 136 | 137 | - rails: "7.2" 138 | ruby: "3.0" 139 | 140 | env: 141 | BUNDLE_GEMFILE: ${{ github.workspace }}/spec/gemfiles/Gemfile.rails-${{ matrix.rails }} 142 | 143 | steps: 144 | - uses: actions/checkout@v4 145 | 146 | - name: Install ruby 147 | uses: ruby/setup-ruby@v1 148 | with: 149 | bundler: "${{ matrix.bundler }}" 150 | ruby-version: "${{ matrix.ruby }}" 151 | bundler-cache: true 152 | 153 | - name: Run tests 154 | run: bundle exec rspec 155 | continue-on-error: ${{ matrix.allow_failures == 'true' }} 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | Gemfile.lock.* 3 | .bundle/ 4 | log/*.log 5 | pkg/ 6 | spec/rails_app/db/*.sqlite3 7 | spec/rails_app/db/*.sqlite3-* 8 | spec/rails_app/log/*.log 9 | spec/rails_app/tmp/ 10 | oo_*/ 11 | tags 12 | .DS_Store 13 | tmp/ 14 | log/.keep 15 | 16 | coverage/* 17 | /.project 18 | 19 | *.sqlite3 20 | 21 | *.log 22 | 23 | *.sqlite3 24 | 25 | .ruby-version 26 | .tool-versions 27 | 28 | spec/gemfiles/*.lock 29 | .byebug_history 30 | *.gem 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | **June 18, 2024**: 0.6.4 release 4 | 5 | - Convert to Github actions 6 | - Improve tests 7 | - Fix broken templates with comments as their last line 8 | 9 | **March 8, 2022**: 0.6.3 release 10 | 11 | - Exclude rspec directory (#149)[https://github.com/caxlsx/caxlsx_rails/pull/149] 12 | 13 | **December 18, 2019**: 0.6.2 release 14 | 15 | - Release under caxlsx_rails 16 | 17 | **December 18, 2019**: 0.6.1 release 18 | 19 | - Deprecate axlsx_rails name, release under caxlsx_rails 20 | - Switch to using caxlsx 3.0 gem 21 | 22 | **September 5, 2019**: 0.6.0 release 23 | 24 | - Improved Rails 6.0 compatibility re MIME type 25 | 26 | **May 1st, 2018**: 0.5.2 release 27 | 28 | - Improved Rails 5 compatibility re MIME type 29 | 30 | **March 29th, 2017**: 0.5.1 release 31 | 32 | - Fix stack trace line numbers 33 | - Thanks to [BenoitHiller](https://github.com/BenoitHiller) 34 | 35 | **July 26st, 2016**: 0.5.0 release 36 | 37 | - Support for Rails 5 38 | - **Tested on on Rails 4.0, 4.1, 4.2, and 5.0** 39 | - Bug fixes for unreadable files and UTF-8 errors 40 | 41 | **July 13th, 2015**: 0.4.0 release 42 | 43 | - Support for Rails 4.2 44 | - **Removal of forced default_formats** (url format must match) 45 | - **Tested only on Rails 4.1 and 4.2** 46 | - **For Rails 3.2 or below, use 0.3.0** 47 | 48 | **November 20th, 2014**: 0.3.0 release 49 | 50 | - Support for Rails 4.2.beta4. 51 | - **Removal of shorthand template syntax** (`render xlsx: 'another/directory'`) 52 | 53 | **September 4, 2014**: 0.2.1 release 54 | 55 | - Rails 4.2.beta1 no longer includes responder. This release checks for the existence of responder before configuring a default responder. 56 | - Rails 4.2 testing, though not yet on Travis CI 57 | - Author, created_at, and use_shared_strings parameters for Axlsx::Package.new 58 | 59 | **April 9, 2014**: 0.2.0 release 60 | 61 | - Require Axlsx 2.0.1, which requires rubyzip 1.0.0 62 | - Better render handling and testing, which might break former usage 63 | - Rails 4.1 testing 64 | - Mailer example update (**use render_to_string not render**) 65 | 66 | **October 11, 2013** 67 | 68 | - Handle (and test) respond_to override 69 | 70 | **October 4, 2013** 71 | 72 | - Added coveralls 73 | - Raised testing to axlsx 2.0.1, roo 1.12.2, and rubyzip 1.0.0 74 | 75 | **July 25, 2013** 76 | 77 | - Documentation improved 78 | - Testing for generating partial in mailer 79 | 80 | **January 18, 2013**: 0.1.4 release 81 | 82 | - Now supports Rails 4 (thanks [Envek](https://github.com/Envek)) 83 | - If you call render :xlsx on a request without :xlsx format, it should force the :xlsx format. Works on Rails 3.2+. 84 | 85 | **December 6, 2012**: 0.1.3 release 86 | 87 | - Fix for absolute template paths 88 | 89 | **July 25, 2012**: 0.1.2 release 90 | 91 | - Partials tested 92 | 93 | **July 19, 2012**: 0.1.1 release 94 | 95 | - Travis-ci added (thanks [randym](https://github.com/randym)) 96 | - render statements and filename tests fixes (thanks [engwan](https://github.com/engwan)) 97 | 98 | **July 17, 2012**: 0.1.0 release 99 | 100 | - Tests completed 101 | - Acts_as_xlsx tested, example in docs 102 | 103 | **July 12, 2012**: 0.0.1 release 104 | 105 | - Initial posting. 106 | - It works, but there are no tests! Bad programmer! 107 | 108 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard 'rspec', :version => 2 do 5 | watch(%r{spec/.+_spec\.rb$}) 6 | watch('spec/spec_helper.rb') { "spec" } 7 | watch(%r{spec/support/.+\.rb$}) { "spec" } 8 | 9 | watch(%r{spec/dummy/app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 10 | watch(%r{spec/dummy/app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 11 | watch(%r{spec/dummy/app/controllers/.+\.rb$}) { ["spec/axlsx_renderer_spec.rb", "spec/axlsx_builder_spec.rb", "axlsx_request_spec.rb"] } 12 | watch('lib/axlsx_rails/action_controller.rb') { ["spec/axlsx_renderer_spec.rb", "spec/axlsx_request_spec.rb"] } 13 | watch('lib/axlsx_rails/template_handler.rb') { "spec/axlsx_builder_spec.rb" } 14 | watch(%r{spec/dummy/app/mailers/.+\.rb$}) { "spec/axlsx_mailer_spec.rb" } 15 | watch(%r{spec/dummy/app/views/.+\.erb}) { "spec/axlsx_request_spec.rb" } 16 | end 17 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 YOURNAME 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Notice: axlsx_rails renamed to caxlsx_rails 2 | =================================================== 3 | This gem has been renamed to match other gems in the Axlsx community organization: https://github.com/caxlsx 4 | 5 | Axlsx-Rails — Spreadsheet templates for Rails 6 | =================================================== 7 | 8 | [](https://badge.fury.io/rb/caxlsx_rails) 10 | [](https://coveralls.io/r/caxlsx/caxlsx_rails) 12 | [](https://github.com/caxlsx/caxlsx_rails/actions) 13 |  14 | 15 | ## Notice: Community Axlsx Organization 16 | 17 | To better maintain the Axlsx ecosystem, all related gems have been forked or moved to the following community organization: 18 | 19 | https://github.com/caxlsx 20 | 21 | [Join the Caxlsx Slack channel](https://join.slack.com/t/caxlsx/shared_invite/enQtOTI5OTM0MzI1Njk5LTBlMDQzNDk2YzkwODMxMmVkODMyYzJiZGU5NTQ3YTg5NTBlN2IwZTlmNTRjNzhiY2E0MDY2OTEyYmFlODI5NjA) 22 | 23 | ## Installation 24 | 25 | In your Gemfile: 26 | 27 | ```ruby 28 | gem 'caxlsx' 29 | gem 'caxlsx_rails' 30 | ``` 31 | 32 | See [previous installations](#previous-installations) if needed. 33 | 34 | ## Requirements 35 | 36 | * Tested on Rails 4.2, 5.x, 6.x, and 7.x. 37 | * For Rails 3.1 or 3.2 use version 3.0 38 | * **As of 0.5.0 requires Axlsx 2.0.1, but strongly suggests 2.1.0.pre, which requires rubyzip 1.1.0** 39 | * As of Rails 4.1 you must use `render_to_string` to render a mail attachment. 40 | 41 | ## FYI 42 | 43 | * This gem depends on [caxlsx](https://github.com/caxlsx/caxlsx). See [the blog](http://axlsx.blog.randym.net/) or the [examples page](https://github.com/caxlsx/caxlsx/tree/master/examples) for usage. 44 | * Check out [axlsx_styler](https://github.com/sakovias/axlsx_styler) by [sakovias](https://github.com/sakovias) for easier styles and borders! 45 | 46 | ## Usage 47 | 48 | Axlsx-Rails provides a renderer and a template handler. It adds the `:xlsx` format and parses `.xlsx.axlsx` templates. This lets you take all the [caxlsx](https://github.com/caxlsx/caxlsx) code out of your controller or model and place it inside the template, where view code belongs! **See [this blog post](http://axlsx.blog.randym.net/2012/08/excel-on-rails-like-pro-with-axlsxrails.html) for a more complete walkthrough.** 49 | 50 | ### Controller 51 | 52 | To use Axlsx-Rails set your instance variables in your controller and configure the response if needed: 53 | 54 | ```ruby 55 | class ButtonController < ApplicationController 56 | def action_name 57 | @buttons = Button.all 58 | respond_to do |format| 59 | format.xlsx 60 | end 61 | end 62 | end 63 | ``` 64 | 65 | ### Template 66 | 67 | Create the template with the `.xlsx.axlsx` extension (`action_name.xlsx.axlsx` for example.) [**Watch out for typos!**](#troubleshooting) In the template, use xlsx_package variable to create your spreadsheet: 68 | 69 | ```ruby 70 | wb = xlsx_package.workbook 71 | wb.add_worksheet(name: "Buttons") do |sheet| 72 | @buttons.each do |button| 73 | sheet.add_row [button.name, button.category, button.price] 74 | end 75 | end 76 | ``` 77 | 78 | This is where you place all your [caxlsx](https://github.com/caxlsx/caxlsx) specific markup. Add worksheets, fill content, merge cells, add styles. See the [caxlsx examples](https://github.com/caxlsx/caxlsx/tree/master/examples) page to see what you can do. 79 | 80 | Remember, like in `erb` templates, view helpers are available to use the `.xlsx.axlsx` template. 81 | 82 | That's it. Call your action and your spreadsheet will be delivered. 83 | 84 | ### Rendering Options 85 | 86 | You can call render in any of the following ways: 87 | 88 | ```ruby 89 | # rendered, no disposition/filename header 90 | render 'buttons' 91 | # rendered from another controller, no disposition/filename header 92 | render 'featured/latest' 93 | # template and filename of 'buttons' 94 | render xlsx: 'buttons' 95 | # template from another controller, filename of 'latest_buttons' 96 | render xlsx: 'latest_buttons', template: 'featured/latest' 97 | ``` 98 | 99 | ### Disposition 100 | 101 | To specify a disposition (such as `inline` so the spreadsheet is opened inside the browser), use the `disposition` option: 102 | 103 | ```ruby 104 | render xlsx: "buttons", disposition: 'inline' 105 | ``` 106 | 107 | If `render xlsx:` is called, the disposition defaults to `attachment`. 108 | 109 | ### File name 110 | 111 | If Rails calls Axlsx through default channels (because you use `format.xlsx {}` for example) you must set the filename using the response header: 112 | 113 | ```ruby 114 | format.xlsx { 115 | response.headers['Content-Disposition'] = 'attachment; filename="my_new_filename.xlsx"' 116 | } 117 | ``` 118 | 119 | If you use `render xlsx:` the gem will try to guess the file name: 120 | 121 | ```ruby 122 | # filename of 'buttons' 123 | render xlsx: 'buttons' 124 | # filename of 'latest_buttons' 125 | render xlsx: 'latest_buttons', template: 'featured/latest' 126 | ``` 127 | 128 | If that fails, pass the `:filename` parameter: 129 | 130 | ```ruby 131 | render xlsx: "action_or_template", filename: "my_new_filename.xlsx" 132 | ``` 133 | 134 | ### Acts As Xlsx 135 | 136 | If you use [acts_as_xlsx](https://github.com/caxlsx/acts_as_xlsx), configure the active record normally, but specify the package in the template: 137 | 138 | ```ruby 139 | User.to_xlsx package: xlsx_package, (other options) 140 | ``` 141 | 142 | **Note:** As of 4/1/2014 Acts As Xlsx is not compatible with Rails 4.1, and generates a warning on 4.0. You may use [my patched fork](https://github.com/caxlsx/acts_as_caxlsx) until it is remedied. 143 | 144 | ### Axlsx Package Options 145 | 146 | Axlsx provides three options for initializing a spreadsheet: 147 | 148 | - **:xlsx_author** (String) - The author of the document 149 | - **:xlsx_created_at** (Time) - Timestamp in the document properties (defaults to current time) 150 | - **:xlsx_use_shared_strings** (Boolean) - This is passed to the workbook to specify that shared strings should be used when serializing the package. 151 | 152 | To pass these to the new package, pass them to `render :xlsx` _or_ pass them as local variables. 153 | 154 | For example, to set the author name, pass the `:xlsx_author` parameter to `render :xlsx` _or_ as a local variable: 155 | 156 | ```ruby 157 | render xlsx: "index", xlsx_author: "Elmer Fudd" 158 | render "index", locals: {xlsx_author: "Elmer Fudd"} 159 | ``` 160 | 161 | Other examples: 162 | 163 | ```ruby 164 | render xlsx: "index", xlsx_created_at: 3.days.ago 165 | render "index", locals: {xlsx_use_shared_strings: true} 166 | ``` 167 | 168 | ### Partials 169 | 170 | Partials work as expected, but you must pass in relevant spreadsheet variables: 171 | 172 | ```ruby 173 | wb = xlsx_package.workbook 174 | render :partial => 'cover_sheet', :locals => {:wb => wb} 175 | wb.add_worksheet(name: "Content") do |sheet| 176 | sheet.add_row ['Content'] 177 | end 178 | ``` 179 | 180 | With the partial simply using the passed variables: 181 | 182 | ```ruby 183 | wb.add_worksheet(name: "Cover Sheet") do |sheet| 184 | sheet.add_row ['Cover', 'Sheet'] 185 | end 186 | ``` 187 | 188 | ### Mailers 189 | 190 | To use an xlsx template to render a mail attachment, use the following syntax: 191 | 192 | ```ruby 193 | class UserMailer < ActionMailer::Base 194 | def export(users) 195 | xlsx = render_to_string layout: false, handlers: [:axlsx], formats: [:xlsx], template: "users/export", locals: {users: users} 196 | attachment = Base64.encode64(xlsx) 197 | attachments["Users.xlsx"] = {mime_type: Mime[:xlsx], content: attachment, encoding: 'base64'} 198 | # For rails 4 use Mime::XLSX 199 | # attachments["Users.xlsx"] = {mime_type: Mime::XLSX, content: attachment, encoding: 'base64'} 200 | # self.instance_variable_set(:@_lookup_context, nil) # If attachments are rendered as content, try this and open an issue 201 | ... 202 | end 203 | end 204 | ``` 205 | 206 | * If the route specifies or suggests the `:xlsx` format you do not need to specify `formats` or `handlers`. 207 | * If the template (`users/export`) can refer to only one file (the xlsx.axlsx template), you do not need to specify `handlers`, provided the `formats` includes `:xlsx`. 208 | * Specifying the encoding as 'base64' can avoid UTF-8 errors. 209 | 210 | ### Scripts 211 | 212 | To generate a template within a script, you need to instantiate an ActionView context. Here are two gists showing how to perform this: 213 | 214 | * [Using rails runner](https://gist.github.com/straydogstudio/323139591f2cc5d48fbc) 215 | * [Without rails runner](https://gist.github.com/straydogstudio/dceb775ead81470cea70) 216 | 217 | ### Testing 218 | 219 | There is no built-in way to test your resulting workbooks / templates. But here is a piece of code that may help you to find a way. 220 | 221 | #### First, create a shared context 222 | 223 | ```ruby 224 | RSpec.shared_context 'axlsx' do 225 | 226 | # all xlsx specs describe must be normalized 227 | # "folder/view_name.xlsx.axlsx" 228 | # allow to infer the template path 229 | template_name = description 230 | 231 | let(:template_path) do 232 | ['app', 'views', template_name] 233 | end 234 | 235 | # This helper will be used in tests 236 | def render_template(locals = {}) 237 | axlsx_binding = Kernel.binding 238 | locals.each do |key, value| 239 | axlsx_binding.local_variable_set key, value 240 | end 241 | # define a default workbook and a default sheet useful when testing partial in isolation 242 | wb = Axlsx::Package.new.workbook 243 | axlsx_binding.local_variable_set(:wb, wb) 244 | axlsx_binding.local_variable_set(:sheet, wb.add_worksheet) 245 | 246 | # mimics an ActionView::Template class, presenting a 'source' method 247 | # to retrieve the content of the template 248 | filename = Rails.root.join(*template_path).to_s 249 | template = Struct.new(:source).new(File.read(filename)) 250 | axlsx_binding.eval(AxlsxRails::TemplateHandler.new.call(template), filename) 251 | axlsx_binding.local_variable_get(:wb) 252 | end 253 | end 254 | ``` 255 | 256 | #### Include it in your spec files: 257 | 258 | ```ruby 259 | require 'spec_helper' 260 | require 'helpers/axlsx_context' 261 | 262 | describe 'shared/_total_request.xlsx.axlsx' do 263 | include_context 'axlsx' 264 | 265 | before :each do 266 | # all the instance variables here are the one used in 'shared/_total_request.xlsx.axlsx' 267 | @widget = mock_model(Widget, name: 'My widget') 268 | @message_counts = Struct.new(:count_all, :positives, :negatives, :neutrals).new(42, 23, 15, 25) 269 | end 270 | 271 | it 'has a title line mentioning the widget' do 272 | wb = render_template 273 | sheet = wb.sheet_by_name('Réf. Requête') 274 | expect(sheet).to have_header_cells ['My widget : Messages de la requête'] 275 | end 276 | 277 | it 'exports the message counts' do 278 | wb = render_template 279 | sheet = wb.sheet_by_name('Réf. Requête') 280 | expect(sheet).to have_cells(['Toutes tonalités', 'Tonalité positive', 'Tonalité négative', 'Tonalité neutre']).in_row(2) 281 | expect(sheet).to have_cells([42, 23, 15, 25]).in_row(3) 282 | end 283 | 284 | end 285 | ``` 286 | 287 | #### Matchers used 288 | 289 | ```ruby 290 | 291 | # encoding: UTF-8 292 | 293 | require 'rspec/expectations' 294 | 295 | module XlsxMatchers 296 | 297 | RSpec::Matchers.define :have_header_cells do |cell_values| 298 | match do |worksheet| 299 | worksheet.rows[0].cells.map(&:value) == cell_values 300 | end 301 | 302 | failure_message do |actual| 303 | "Expected #{actual.rows[0].cells.map(&:value)} to be #{expected}" 304 | end 305 | end 306 | 307 | RSpec::Matchers.define :have_cells do |expected| 308 | match do |worksheet| 309 | worksheet.rows[@index].cells.map(&:value) == expected 310 | end 311 | 312 | chain :in_row do |index| 313 | @index = index 314 | end 315 | 316 | failure_message do |actual| 317 | "Expected #{actual.rows[@index].cells.map(&:value)} to include #{expected} at row #{@index}." 318 | end 319 | end 320 | end 321 | 322 | ``` 323 | 324 | 325 | ## Troubleshooting 326 | 327 | ### Mispellings 328 | 329 | **It is easy to get the spelling wrong in the extension name, the format.xlsx statement, or in a render call.** Here are some possibilities: 330 | 331 | * If it says your template is missing, check that its extension is `.xlsx.axlsx`. 332 | * If you get the error `uninitialized constant Mime::XSLX` you have used `format.xslx` instead of `format.xlsx`, or something similar. 333 | 334 | ### Using axlsx_rails in API mode 335 | 336 | In API mode Rails does not include ActionView, so axlsx_rails will not work. To render axlsx_rails templates you must include ActionView::Rendering in your controller and override render_to_body: 337 | 338 | ```ruby 339 | class MyController < ActionController::API 340 | include ActionView::Rendering 341 | 342 | def show 343 | respond_to do |format| 344 | format.xlsx 345 | end 346 | end 347 | 348 | private 349 | 350 | def render_to_body(options) 351 | _render_to_body_with_renderer(options) || super 352 | end 353 | end 354 | ``` 355 | 356 | See [issue 107](https://github.com/caxlsx/caxlsx_rails/issues/107) 357 | 358 | ### Mailer Attachments: No content, cannot read, Invalid Byte Sequence in UTF-8 359 | 360 | If you are having problems with rendering a template and attaching it to a template, try a few options: 361 | 362 | * Make sure the attachment template does not have the same name as the mailer. 363 | * After you have rendered the template to string, and before you call the mailer, execute `self.instance_variable_set(:@_lookup_context, nil)`. If you must do this, please open an issue. 364 | * If you get Invalid Byte Sequence in UTF-8, pass `encoding: 'base64'` with the attachment: 365 | 366 | ```ruby 367 | class UserMailer < ActionMailer::Base 368 | def export(users) 369 | xlsx = render_to_string handlers: [:axlsx], formats: [:xlsx], template: "users/export", locals: {users: users} 370 | attachments["Users.xlsx"] = {mime_type: Mime[:xlsx], content: xlsx, encoding: 'base64'} 371 | # For Rails 4 use Mime::XLSX 372 | # attachments["Users.xlsx"] = {mime_type: Mime::XLSX, content: xlsx, encoding: 'base64'} 373 | # self.instance_variable_set(:@_lookup_context, nil) # If attachments are rendered as content, try this and open an issue 374 | ... 375 | end 376 | end 377 | ``` 378 | 379 | If you get these errors, please open an issue and share code so the bug can be isolated. Or comment on issue [#29](https://github.com/caxlsx/caxlsx_rails/issues/29) or [#25](https://github.com/caxlsx/caxlsx_rails/issues/25). 380 | 381 | ### The unparsed template is returned, or something similar 382 | 383 | Have you followed other tutorials for serving Excel from Rails? Have you declared the MIME type already? These may be incompatible with caxlsx_rails (which declares the MIME type for you.) Remove any vestiges of other tutorials from your code and try again. 384 | 385 | ### Generated Files Can't Be Opened or Invalid Byte Sequence in UTF-8 386 | 387 | Both these errors *appear* to be caused by Rails applying a layout to the template. Passing `layout: false` to `render :xlsx` should fix this issue. Version 0.5.0 attempts to fix this issue. 388 | 389 | If you get this error, please open an issue and share code so the bug can be isolated. 390 | 391 | ### Rails 4.2 changes 392 | 393 | Before Rails 4.2 you could call: 394 | 395 | ```ruby 396 | render xlsx: "users/index" 397 | ``` 398 | 399 | And caxlsx_rails could adjust the paths and make sure the template was loaded from the right directory. This is no longer possible because the paths are cached between requests for a given controller. As a result, to display a template in another directory you must use the `:template` parameter (which is normal Rails behavior anyway): 400 | 401 | ```ruby 402 | render xlsx: "index", template: "users/index" 403 | ``` 404 | 405 | If the request format matches you should be able to call: 406 | 407 | ```ruby 408 | render "users/index" 409 | ``` 410 | 411 | This is a breaking change if you have the old syntax! 412 | 413 | ### Turbolinks 414 | 415 | If you are using turbolinks, you may need to disable turbolinks when you link to your spreadsheet: 416 | 417 | ```ruby 418 | # turbolinks 5: 419 | link_to 'Download spreadsheet', path_to_sheet, data: {turbolinks: false} 420 | ``` 421 | 422 | ### Rails 7 Unknown Format 423 | 424 | In Rails 7, if you get an error of "Unknown Format" you may need to add `(format: "xlsx")` to the named route. 425 | 426 | A clue to the "format" that Rails is responding with is to look at your log file or console after you click the HTML link or submit your form. You should see `Processing by Controller#action as XLSX`. 427 | 428 | #### Code Examples 429 | 430 | Scenario 1 - HTML Link within index.html.erb 431 | 432 | ```ruby 433 | link_to "Download as Excel", my_named_route_path(format: "xlsx") 434 | ``` 435 | 436 | Scenario 2 - Using form_with() 437 | 438 | ```ruby 439 | form_with method: :post, url: my_named_route_path(format: :xlsx) do |f| 440 | ``` 441 | 442 | ### What to do 443 | 444 | If you are having problems, try to isolate the issue. Use the console or a script to make sure your data is good. Then create the spreadsheet line by line without Axlsx-Rails to see if you are having caxlsx problems. If you can manually create the spreadsheet, create an issue and we will work it out. 445 | 446 | ## Previous Installations 447 | 448 | In your Gemfile: 449 | 450 | ```ruby 451 | gem 'rubyzip', '>= 1.2.1' 452 | gem 'axlsx', git: 'https://github.com/randym/axlsx.git', ref: 'c8ac844' 453 | gem 'axlsx_rails' 454 | ``` 455 | 456 | If `rubyzip 1.0.0` is needed: 457 | 458 | ```ruby 459 | gem 'rubyzip', '= 1.0.0' 460 | gem 'axlsx', '= 2.0.1' 461 | gem 'axlsx_rails' 462 | ``` 463 | 464 | If `rubyzip >= 1.1.0` is needed: 465 | 466 | ```ruby 467 | gem 'rubyzip', '~> 1.1.0' 468 | gem 'axlsx', '2.1.0.pre' 469 | gem 'axlsx_rails' 470 | ``` 471 | 472 | ## Dependencies 473 | 474 | - [Rails](https://github.com/rails/rails) 475 | - [caxlsx](https://github.com/caxlsx/caxlsx) 476 | 477 | ## Authors 478 | 479 | * [Noel Peden](https://github.com/straydogstudio) 480 | 481 | ## Contributors 482 | 483 | Many thanks to [contributors](https://github.com/caxlsx/caxlsx_rails/graphs/contributors): 484 | 485 | * [randym](https://github.com/randym) 486 | * [sugi](https://github.com/sugi) 487 | * [envek](https://github.com/envek) 488 | * [engwan](https://github.com/engwan) 489 | * [maxd](https://github.com/maxd) 490 | * [firien](https://github.com/firien) 491 | * [kaluzny](https://github.com/kaluznyo) 492 | * [sly7-7](https://github.com/sly7-7) 493 | * [kodram](https://github.com/kodram) 494 | * [JohnSmall](https://github.com/JohnSmall) 495 | * [BenoitHiller](https://github.com/BenoitHiller) 496 | 497 | ## Change log 498 | 499 | **June 18, 2024**: 0.6.4 release 500 | 501 | - Convert to Github actions 502 | - Improve tests 503 | - Fix broken templates with comments as their last line 504 | 505 | **March 8, 2022**: 0.6.3 release 506 | 507 | - Exclude rspec directory (#149)[https://github.com/caxlsx/caxlsx_rails/pull/149] 508 | 509 | **December 18, 2019**: 0.6.2 release 510 | 511 | - Release under caxlsx_rails 512 | 513 | **December 18, 2019**: 0.6.1 release 514 | 515 | - Deprecate axlsx_rails name, release under caxlsx_rails 516 | - Switch to using caxlsx 3.0 gem 517 | 518 | **September 5, 2019**: 0.6.0 release 519 | 520 | - Improved Rails 6.0 compatibility re MIME type 521 | 522 | **May 1st, 2018**: 0.5.2 release 523 | 524 | - Improved Rails 5 compatibility re MIME type 525 | 526 | **March 29th, 2017**: 0.5.1 release 527 | 528 | - Fix stack trace line numbers 529 | - Thanks to [BenoitHiller](https://github.com/BenoitHiller) 530 | 531 | **July 26st, 2016**: 0.5.0 release 532 | 533 | - Support for Rails 5 534 | - **Tested on on Rails 4.0, 4.1, 4.2, and 5.0** 535 | - Bug fixes for unreadable files and UTF-8 errors 536 | 537 | **July 13th, 2015**: 0.4.0 release 538 | 539 | - Support for Rails 4.2 540 | - **Removal of forced default_formats** (url format must match) 541 | - **Tested only on Rails 4.1 and 4.2** 542 | - **For Rails 3.2 or below, use 0.3.0** 543 | 544 | **November 20th, 2014**: 0.3.0 release 545 | 546 | - Support for Rails 4.2.beta4. 547 | - **Removal of shorthand template syntax** (`render xlsx: 'another/directory'`) 548 | 549 | **September 4, 2014**: 0.2.1 release 550 | 551 | - Rails 4.2.beta1 no longer includes responder. This release checks for the existence of responder before configuring a default responder. 552 | - Rails 4.2 testing, though not yet on Travis CI 553 | - Author, created_at, and use_shared_strings parameters for Axlsx::Package.new 554 | 555 | **April 9, 2014**: 0.2.0 release 556 | 557 | - Require Axlsx 2.0.1, which requires rubyzip 1.0.0 558 | - Better render handling and testing, which might break former usage 559 | - Rails 4.1 testing 560 | - Mailer example update (**use render_to_string not render**) 561 | 562 | **October 11, 2013** 563 | 564 | - Handle (and test) respond_to override 565 | 566 | **October 4, 2013** 567 | 568 | - Added coveralls 569 | - Raised testing to axlsx 2.0.1, roo 1.12.2, and rubyzip 1.0.0 570 | 571 | **July 25, 2013** 572 | 573 | - Documentation improved 574 | - Testing for generating partial in mailer 575 | 576 | **January 18, 2013**: 0.1.4 release 577 | 578 | - Now supports Rails 4 (thanks [Envek](https://github.com/Envek)) 579 | - If you call render :xlsx on a request without :xlsx format, it should force the :xlsx format. Works on Rails 3.2+. 580 | 581 | **December 6, 2012**: 0.1.3 release 582 | 583 | - Fix for absolute template paths 584 | 585 | **July 25, 2012**: 0.1.2 release 586 | 587 | - Partials tested 588 | 589 | **July 19, 2012**: 0.1.1 release 590 | 591 | - Travis-ci added (thanks [randym](https://github.com/randym)) 592 | - render statements and filename tests fixes (thanks [engwan](https://github.com/engwan)) 593 | 594 | **July 17, 2012**: 0.1.0 release 595 | 596 | - Tests completed 597 | - Acts_as_xlsx tested, example in docs 598 | 599 | **July 12, 2012**: 0.0.1 release 600 | 601 | - Initial posting. 602 | - It works, but there are no tests! Bad programmer! 603 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | begin 3 | require 'bundler/setup' 4 | rescue LoadError 5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 6 | end 7 | begin 8 | require 'rdoc/task' 9 | rescue LoadError 10 | require 'rdoc/rdoc' 11 | require 'rake/rdoctask' 12 | RDoc::Task = Rake::RDocTask 13 | end 14 | 15 | RDoc::Task.new(:rdoc) do |rdoc| 16 | rdoc.rdoc_dir = 'rdoc' 17 | rdoc.title = 'CaxlsxRails' 18 | rdoc.options << '--line-numbers' 19 | rdoc.rdoc_files.include('README.rdoc') 20 | rdoc.rdoc_files.include('lib/**/*.rb') 21 | end 22 | 23 | require "rspec/core/rake_task" 24 | RSpec::Core::RakeTask.new('spec') 25 | 26 | task :default => :spec 27 | 28 | Bundler::GemHelper.install_tasks 29 | 30 | -------------------------------------------------------------------------------- /caxlsx_rails.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/axlsx_rails/version' 4 | 5 | # Describe your gem and declare its dependencies: 6 | Gem::Specification.new do |s| 7 | s.name = 'caxlsx_rails' 8 | s.version = AxlsxRails::VERSION 9 | s.licenses = ['MIT'] 10 | s.authors = ['Noel Peden'] 11 | s.email = ['noel@peden.biz'] 12 | s.homepage = 'https://github.com/caxlsx/caxlsx_rails' 13 | s.summary = 'A simple rails plugin to provide an xlsx renderer using the caxlsx gem.' 14 | s.description = "Caxlsx_Rails provides an Caxlsx renderer so you can move all your spreadsheet code from your controller into view files. Partials are supported so you can organize any code into reusable chunks (e.g. cover sheets, common styling, etc.) You can use it with acts_as_caxlsx, placing the to_xlsx call in a view and adding ':package => xlsx_package' to the parameter list. Now you can keep your controllers thin!" 15 | 16 | s.metadata = { 17 | "changelog_uri" => "https://github.com/caxlsx/caxlsx_rails/blob/master/CHANGELOG.md" 18 | } 19 | 20 | s.files = Dir['lib/**/*', 'CHANGELOG.md', 'README.md', 'MIT-LICENSE', 'caxlsx_rails.gemspec'] 21 | 22 | s.add_dependency 'actionpack', '>= 3.1' 23 | s.add_dependency 'caxlsx', '>= 3.0' 24 | 25 | s.add_development_dependency 'bundler' 26 | s.add_development_dependency 'rake' 27 | s.add_development_dependency 'rspec-rails' 28 | s.add_development_dependency 'guard-rspec' 29 | s.add_development_dependency 'capybara' 30 | s.add_development_dependency 'roo' 31 | s.add_development_dependency 'rubyzip' 32 | s.add_development_dependency 'growl' 33 | s.add_development_dependency 'rb-fsevent' 34 | s.add_development_dependency 'coveralls' 35 | s.add_development_dependency 'pry' 36 | s.add_development_dependency 'pry-nav' 37 | end 38 | -------------------------------------------------------------------------------- /lib/axlsx_rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | warn "DEPRECATION WARNING: axlsx_rails has been renamed to caxlsx_rails. See http://github.com/caxlsx" 4 | 5 | require 'axlsx_rails/railtie' if defined?(Rails) 6 | -------------------------------------------------------------------------------- /lib/axlsx_rails/action_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'action_controller' 4 | 5 | if Rails.version.to_f >= 5 6 | unless Mime[:xlsx] 7 | Mime::Type.register 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :xlsx 8 | end 9 | else 10 | unless defined? Mime::XLSX 11 | Mime::Type.register 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :xlsx 12 | end 13 | end 14 | 15 | ActionController::Renderers.add :xlsx do |filename, options| 16 | # 17 | # You can always specify a template: 18 | # 19 | # def called_action 20 | # render xlsx: 'filename', template: 'controller/diff_action' 21 | # end 22 | # 23 | # And the normal use case works: 24 | # 25 | # def called_action 26 | # render 'diff_action' 27 | # # or 28 | # render 'controller/diff_action' 29 | # end 30 | # 31 | options[:template] = filename.gsub(/^.*\//,'') if options[:template] == action_name 32 | 33 | # force layout false 34 | options[:layout] = false 35 | 36 | # disposition / filename 37 | disposition = options.delete(:disposition) || 'attachment' 38 | file_name = options.delete(:filename) || "#{filename.gsub(/^.*\//,'')}.xlsx" 39 | file_name = "#{file_name}.xlsx" unless file_name =~ /\.xlsx$/ 40 | 41 | # alternate settings 42 | options[:locals] ||= {} 43 | options[:locals][:xlsx_author] ||= options.delete(:xlsx_author) 44 | options[:locals][:xlsx_created_at] ||= options.delete(:xlsx_created_at) 45 | unless options[:locals][:xlsx_use_shared_strings] 46 | options[:locals][:xlsx_use_shared_strings] = options.delete(:xlsx_use_shared_strings) 47 | end 48 | 49 | mime = Rails.version.to_f >= 5 ? Mime[:xlsx] : Mime::XLSX 50 | send_data render_to_string(options), filename: file_name, type: mime, disposition: disposition 51 | end 52 | 53 | # For respond_to default 54 | begin 55 | ActionController::Responder 56 | rescue 57 | else 58 | class ActionController::Responder 59 | def to_xlsx 60 | @_action_has_layout = false 61 | if @default_response 62 | @default_response.call(options) 63 | else 64 | controller.render({xlsx: controller.action_name}.merge(options)) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/axlsx_rails/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'axlsx_rails/template_handler' 4 | 5 | module AxlsxRails 6 | class Railtie < Rails::Railtie 7 | initializer 'axlsx_rails.initialization' do 8 | ActiveSupport.on_load(:action_view) do 9 | ActionView::Template.register_template_handler :axlsx, AxlsxRails::TemplateHandler.new 10 | end 11 | 12 | ActiveSupport.on_load(:action_controller) do 13 | require 'axlsx_rails/action_controller' 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/axlsx_rails/template_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'stringio' 4 | 5 | module AxlsxRails 6 | class TemplateHandler 7 | def default_format 8 | case 9 | when Rails.version.to_f >= 6 10 | Mime[:xlsx].symbol 11 | when Rails.version.to_f >= 5 12 | Mime[:xlsx] 13 | else 14 | Mime::XLSX 15 | end 16 | end 17 | 18 | def call(template, source = nil) 19 | builder = StringIO.new 20 | builder << "require 'axlsx';" 21 | builder << "xlsx_author = defined?(xlsx_author).nil? ? nil : xlsx_author;" 22 | builder << "xlsx_created_at = defined?(xlsx_created_at).nil? ? nil : xlsx_created_at;" 23 | builder << "xlsx_use_shared_strings = defined?(xlsx_use_shared_strings).nil? ? nil : xlsx_use_shared_strings;" 24 | builder << "xlsx_package = Axlsx::Package.new(" 25 | builder << ":author => xlsx_author," 26 | builder << ":created_at => xlsx_created_at," 27 | builder << ":use_shared_strings => xlsx_use_shared_strings);" 28 | builder << (source || template.source) 29 | builder << "\n;xlsx_package.to_stream.string;" 30 | builder.string 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/axlsx_rails/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AxlsxRails 4 | VERSION = '0.6.4' 5 | end 6 | -------------------------------------------------------------------------------- /lib/caxlsx_rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'axlsx_rails/railtie' if defined?(Rails) 4 | -------------------------------------------------------------------------------- /lib/tasks/axlsx_rails_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :axlsx_rails do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /spec/axlsx_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AxlsxRails::TemplateHandler do 4 | subject(:handler) { described_class.new } 5 | 6 | describe '#default_format' do 7 | it "has xlsx format" do 8 | if Rails::VERSION::MAJOR >= 6 9 | expect(handler.default_format).to eq(Mime[:xlsx].symbol) 10 | elsif Rails::VERSION::MAJOR >= 5 11 | expect(handler.default_format).to eq(Mime[:xlsx]) 12 | else 13 | expect(handler.default_format).to eq(Mime::XLSX) 14 | end 15 | end 16 | end 17 | 18 | describe 'how to compile it to an excel spreadsheet' do 19 | let(:template) { Struct.new(:source).new(template_string) } 20 | 21 | let(:template_string) { <<~RUBY } 22 | wb = xlsx_package.workbook 23 | wb.add_worksheet(name: 'Test') do |sheet| 24 | sheet.add_row ['one', 'two', 'three'] 25 | sheet.add_row ['a', 'b', 'c'] 26 | end 27 | RUBY 28 | 29 | context 'when passing in a source' do 30 | let(:source_string) { <<~RUBY } 31 | wb = xlsx_package.workbook 32 | wb.add_worksheet(name: 'Test') do |sheet| 33 | sheet.add_row ['four', 'five', 'six'] 34 | sheet.add_row ['d', 'e', 'f'] 35 | end 36 | RUBY 37 | 38 | it "compiles to an excel spreadsheet when passing in a source" do 39 | wb = nil 40 | 41 | file = Tempfile.new(['caxlsx', '.xlsx']) 42 | file.binmode 43 | file.write(eval(described_class.new.call(template, source_string))) 44 | file.close 45 | 46 | expect { wb = Roo::Excelx.new(file.path) }.to_not raise_error 47 | expect(wb.cell(2,3)).to eq('f') 48 | end 49 | end 50 | 51 | context 'when not passing in a source' do 52 | it "compiles to an excel spreadsheet when inferring source from template" do 53 | wb = nil 54 | 55 | file = Tempfile.new(['caxlsx', '.xlsx']) 56 | file.binmode 57 | file.write(eval(described_class.new.call(template))) 58 | file.close 59 | 60 | expect { wb = Roo::Excelx.new(file.path) }.to_not raise_error 61 | expect(wb.cell(2,3)).to eq('c') 62 | end 63 | 64 | context 'when template ends with a comment line' do 65 | let(:template_string) { <<~RUBY.strip } 66 | wb = xlsx_package.workbook 67 | wb.add_worksheet(name: 'Test') do |sheet| 68 | sheet.add_row ['one', 'two', 'three'] 69 | sheet.add_row ['a', 'b', 'c'] 70 | end 71 | # Extra comment 72 | RUBY 73 | 74 | it "compiles to an excel spreadsheet when inferring source from template" do 75 | wb = nil 76 | 77 | file = Tempfile.new(['caxlsx', '.xlsx']) 78 | file.binmode 79 | file.write(eval(described_class.new.call(template))) 80 | file.close 81 | 82 | expect { wb = Roo::Excelx.new(file.path) }.to_not raise_error 83 | expect(wb.cell(2,3)).to eq('c') 84 | end 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/axlsx_mailer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Mailer", type: :request do 4 | before :each do 5 | @user = User.create name: 'Elmer', last_name: 'Fudd', address: '1234 Somewhere, Over NY 11111', email: 'elmer@fudd.com' 6 | end 7 | 8 | it "attaches an xlsx file" do 9 | visit "/users/#{@user.id}/send_instructions" 10 | last_email = ActionMailer::Base.deliveries.last 11 | expect(last_email.to).to eq([@user.email]) 12 | expect(last_email.attachments.first).to be 13 | expect(last_email.attachments.first.content_type).to match(/#{mime_type}/) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/axlsx_renderer_spec.rb: -------------------------------------------------------------------------------- 1 | require'spec_helper' 2 | 3 | describe 'Axlsx renderer' do 4 | 5 | it "is registered" do 6 | ActionController::Renderers::RENDERERS.include?(:xlsx) 7 | end 8 | 9 | it "has mime type" do 10 | mime = mime_type 11 | expect(mime).to be 12 | expect(mime.to_sym).to eq(:xlsx) 13 | expect(mime.to_s).to eq("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 14 | end 15 | 16 | end -------------------------------------------------------------------------------- /spec/axlsx_request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe 'Caxlsx request', :type => :request do 3 | 4 | after(:each) do 5 | if File.exist? '/tmp/caxlsx_temp.xlsx' 6 | File.unlink '/tmp/caxlsx_temp.xlsx' 7 | end 8 | end 9 | 10 | it "has a working dummy app" do 11 | User.create name: 'Elmer', last_name: 'Fudd', address: '1234 Somewhere, Over NY 11111', email: 'elmer@fudd.com' 12 | visit '/' 13 | expect(page).to have_content("Hey, you") 14 | end 15 | 16 | it "downloads an excel file from default respond_to" do 17 | visit '/home.xlsx' 18 | expect(page.response_headers['Content-Type']).to eq(mime_type.to_s + "; charset=utf-8") 19 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 20 | wb = nil 21 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 22 | expect(wb.cell(2,1)).to eq('Untie!') 23 | end 24 | 25 | it "downloads an excel file from respond_to while specifying filename" do 26 | visit '/useheader.xlsx' 27 | 28 | expect(page.response_headers['Content-Type']).to eq(mime_type.to_s) 29 | expect(page.response_headers['Content-Disposition']).to include("filename=\"filename_test.xlsx\"") 30 | 31 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 32 | wb = nil 33 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 34 | expect(wb.cell(2,1)).to eq('Untie!') 35 | end 36 | 37 | it "downloads an excel file from respond_to while specifying filename in direct format" do 38 | visit '/useheader.xlsx?set_direct=true' 39 | 40 | expect(page.response_headers['Content-Type']).to eq(mime_type.to_s + "; charset=utf-8") 41 | expect(page.response_headers['Content-Disposition']).to include("filename=\"filename_test.xlsx\"") 42 | 43 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 44 | wb = nil 45 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 46 | expect(wb.cell(2,1)).to eq('Untie!') 47 | end 48 | 49 | it "downloads an excel file from render statement with filename" do 50 | visit '/another.xlsx' 51 | 52 | expect(page.response_headers['Content-Type']).to eq(mime_type) 53 | expect(page.response_headers['Content-Disposition']).to include("filename=\"filename_test.xlsx\"") 54 | 55 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 56 | wb = nil 57 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 58 | expect(wb.cell(2,1)).to eq('Untie!') 59 | end 60 | 61 | it "downloads an excel file from acts_as_xlsx model" do 62 | User.destroy_all 63 | User.create name: 'Elmer', last_name: 'Fudd', address: '1234 Somewhere, Over NY 11111', email: 'elmer@fudd.com' 64 | User.create name: 'Bugs', last_name: 'Bunny', address: '1234 Left Turn, Albuquerque NM 22222', email: 'bugs@bunny.com' 65 | visit '/users.xlsx' 66 | expect(page.response_headers['Content-Type']).to eq(mime_type.to_s + "; charset=utf-8") 67 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 68 | wb = nil 69 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 70 | expect(wb.cell(3,2)).to eq('Bugs') 71 | end 72 | 73 | it "downloads an excel file with partial" do 74 | visit '/withpartial.xlsx' 75 | expect(page.response_headers['Content-Type']).to eq(mime_type.to_s + "; charset=utf-8") 76 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 77 | wb = nil 78 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 79 | expect(wb.cell(1,1,wb.sheets[0])).to eq('Cover') 80 | expect(wb.cell(2,1,wb.sheets[1])).to eq("Untie!") 81 | end 82 | 83 | it "handles nested resources" do 84 | User.destroy_all 85 | @user = User.create name: 'Bugs', last_name: 'Bunny', address: '1234 Left Turn, Albuquerque NM 22222', email: 'bugs@bunny.com' 86 | @user.likes.create(:name => 'Carrots') 87 | @user.likes.create(:name => 'Celery') 88 | visit "/users/#{@user.id}/likes.xlsx" 89 | expect(page.response_headers['Content-Type']).to eq(mime_type.to_s + "; charset=utf-8") 90 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 91 | wb = nil 92 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 93 | expect(wb.cell(1,1)).to eq('Bugs') 94 | expect(wb.cell(2,1)).to eq('Carrots') 95 | expect(wb.cell(3,1)).to eq('Celery') 96 | end 97 | 98 | it "handles reference to absolute paths" do 99 | User.destroy_all 100 | @user = User.create name: 'Bugs', last_name: 'Bunny', address: '1234 Left Turn, Albuquerque NM 22222', email: 'bugs@bunny.com' 101 | visit "/users/#{@user.id}/render_elsewhere.xlsx" 102 | expect(page.response_headers['Content-Type']).to eq(mime_type.to_s) 103 | [[1,false],[3,true],[4,true],[5,false]].reverse.each do |s| 104 | visit "/home/render_elsewhere.xlsx?type=#{s[0]}" 105 | expect(page.response_headers['Content-Type']).to eq(mime_type.to_s + (s[1] ? "; charset=utf-8" : '')) 106 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 107 | wb = nil 108 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 109 | if s[0] == 5 110 | expect(wb.cell(1,1)).to eq('Bad') 111 | else 112 | expect(wb.cell(2,2)).to eq('Bugs') 113 | end 114 | end 115 | end 116 | 117 | it "uses respond_with" do 118 | User.destroy_all 119 | @user = User.create name: 'Responder', last_name: 'Bunny', address: '1234 Right Turn, Albuquerque NM 22222', email: 'bugs@bunny.com' 120 | visit "/users/#{@user.id}.xlsx" 121 | expect { 122 | visit "/users/#{@user.id}.xlsx" 123 | }.to_not raise_error 124 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 125 | wb = nil 126 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 127 | expect(wb.cell(2,1)).to eq('Untie!') 128 | end 129 | 130 | it "ignores layout" do 131 | User.destroy_all 132 | @user = User.create name: 'Responder', last_name: 'Bunny', address: '1234 Right Turn, Albuquerque NM 22222', email: 'bugs@bunny.com' 133 | expect { 134 | visit "/users/export/#{@user.id}.xlsx" 135 | }.to_not raise_error 136 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 137 | wb = nil 138 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 139 | expect(wb.cell(2,1)).to eq('Untie!') 140 | end 141 | 142 | it "handles missing format with render :xlsx" do 143 | visit '/another' 144 | 145 | expect(page.response_headers['Content-Type']).to eq(mime_type) 146 | expect(page.response_headers['Content-Disposition']).to include("filename=\"filename_test.xlsx\"") 147 | 148 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 149 | wb = nil 150 | # wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') 151 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to raise_error(Zip::ZipError) 152 | # wb.cell(2,1).should == 'Untie!' 153 | end 154 | 155 | if Rails.version > '4.0' && Rails.version < '5.0' 156 | Capybara.register_driver :mime_all do |app| 157 | Capybara::RackTest::Driver.new(app, headers: { 'HTTP_ACCEPT' => '*/*' }) 158 | end 159 | 160 | def puts_def_formats(title) 161 | puts "default formats #{title.ljust(30)}: #{ActionView::Base.default_formats}" 162 | end 163 | 164 | it "mime all with render :xlsx and then :html" do 165 | # puts_def_formats 'before' 166 | ActionView::Base.default_formats.delete :xlsx # see notes 167 | # puts_def_formats 'in my project' 168 | Capybara.current_driver = :mime_all 169 | visit '/another' 170 | # puts_def_formats 'after render xlsx with */*' 171 | expect{ 172 | visit '/home/only_html' 173 | }.to_not raise_error 174 | ActionView::Base.default_formats.push :xlsx # see notes 175 | 176 | # Output: 177 | # default formats before : [:html, :text, :js, :css, :ics, :csv, :vcf, :png, :jpeg, :gif, :bmp, :tiff, :mpeg, :xml, :rss, :atom, :yaml, :multipart_form, :url_encoded_form, :json, :pdf, :zip, :xlsx] 178 | # default formats in my project : [:html, :text, :js, :css, :ics, :csv, :vcf, :png, :jpeg, :gif, :bmp, :tiff, :mpeg, :xml, :rss, :atom, :yaml, :multipart_form, :url_encoded_form, :json, :pdf, :zip] 179 | # default formats after render xlsx with */* : [:xlsx, :text, :js, :css, :ics, :csv, :vcf, :png, :jpeg, :gif, :bmp, :tiff, :mpeg, :xml, :rss, :atom, :yaml, :multipart_form, :url_encoded_form, :json, :pdf, :zip] 180 | 181 | # Failure/Error: visit '/home/only_html' 182 | # ActionView::MissingTemplate: 183 | # Missing template home/only_html, application/only_html with {:locale=>[:en], :formats=>[:xlsx, :text, :js, :css, :ics, :csv, :vcf, :png, :jpeg, :gif, :bmp, :tiff, :mpeg, :xml, :rss, :atom, :yaml, :multipart_form, :url_encoded_form, :json, :pdf, :zip], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :axlsx]}. 184 | end 185 | end 186 | 187 | if Rails::VERSION::MAJOR < 6 188 | it "downloads an excel file when there is no action" do 189 | User.destroy_all 190 | User.create name: 'Elmer', last_name: 'Fudd', address: '1234 Somewhere, Over NY 11111', email: 'elmer@fudd.com' 191 | User.create name: 'Bugs', last_name: 'Bunny', address: '1234 Left Turn, Albuquerque NM 22222', email: 'bugs@bunny.com' 192 | visit '/users/noaction.xlsx' 193 | expect(page.response_headers['Content-Type']).to eq(mime_type.to_s + "; charset=utf-8") 194 | File.open('/tmp/caxlsx_temp.xlsx', 'wb') {|f| f.write(page.source) } 195 | wb = nil 196 | expect{ wb = Roo::Excelx.new('/tmp/caxlsx_temp.xlsx') }.to_not raise_error 197 | expect(wb.cell(3,2)).to eq('Bugs') 198 | end 199 | end 200 | end 201 | -------------------------------------------------------------------------------- /spec/gemfiles/Gemfile.rails-4.2: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec path: '../../' 4 | 5 | gem 'rails', "~> 4.2.0" 6 | gem 'responders', '~> 2.0' 7 | gem 'sqlite3', '1.3.13' 8 | gem 'sprockets', '~> 3.0' 9 | gem "jquery-rails" 10 | gem "thin" 11 | gem 'capybara', '~> 2.1' 12 | gem 'acts_as_caxlsx', git: 'https://github.com/caxlsx/acts_as_caxlsx.git' 13 | 14 | gem 'byebug' 15 | 16 | if RUBY_VERSION < '2.5' 17 | gem 'loofah', '< 2.21.0' 18 | end 19 | 20 | gem 'bigdecimal', '~> 1.4' 21 | gem 'ffi', '< 1.17.0' 22 | -------------------------------------------------------------------------------- /spec/gemfiles/Gemfile.rails-5.0: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec path: '../../' 4 | 5 | gem 'rails', "~> 5.0.0" 6 | gem 'responders', '~> 2.0' 7 | gem 'sqlite3', '~> 1.3.6' 8 | gem 'sprockets', '~> 3.0' 9 | gem "jquery-rails" 10 | gem "thin" 11 | gem 'capybara', '~> 2.1' 12 | gem 'acts_as_caxlsx', git: 'https://github.com/caxlsx/acts_as_caxlsx.git' 13 | 14 | gem 'byebug' 15 | 16 | if RUBY_VERSION < '2.5' 17 | gem 'loofah', '< 2.21.0' 18 | end 19 | -------------------------------------------------------------------------------- /spec/gemfiles/Gemfile.rails-5.1: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec path: '../../' 4 | 5 | gem 'rails', "~> 5.1.0" 6 | gem 'responders', '~> 3.0' 7 | gem 'sqlite3', '~> 1.4.4' 8 | gem 'sprockets', '~> 3.0' 9 | gem "jquery-rails" 10 | gem "thin" 11 | gem 'capybara', '~> 2.1' 12 | gem 'acts_as_caxlsx', git: 'https://github.com/caxlsx/acts_as_caxlsx.git' 13 | 14 | gem 'byebug' 15 | 16 | if RUBY_VERSION < '2.5' 17 | gem 'loofah', '< 2.21.0' 18 | end 19 | -------------------------------------------------------------------------------- /spec/gemfiles/Gemfile.rails-5.2: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec path: '../../' 4 | 5 | gem 'rails', "~> 5.2.0" 6 | gem 'responders', '~> 3.0' 7 | gem 'sqlite3', '~> 1.4.4' 8 | gem 'sprockets', '~> 3.0' 9 | gem "jquery-rails" 10 | gem "thin" 11 | gem 'capybara', '~> 2.1' 12 | gem 'acts_as_caxlsx', git: 'https://github.com/caxlsx/acts_as_caxlsx.git' 13 | 14 | gem 'byebug' 15 | 16 | if RUBY_VERSION < '2.5' 17 | gem 'loofah', '< 2.21.0' 18 | end 19 | -------------------------------------------------------------------------------- /spec/gemfiles/Gemfile.rails-6.0: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec path: '../../' 4 | 5 | gem 'rails', "~> 6.0.0" 6 | gem 'responders', '~> 3.0' 7 | gem 'sqlite3', '~> 1.4' 8 | gem 'sprockets', '~> 3.0' 9 | gem "jquery-rails" 10 | gem "thin" 11 | gem 'capybara', '~> 2.1' 12 | gem 'acts_as_caxlsx', git: 'https://github.com/caxlsx/acts_as_caxlsx.git' 13 | 14 | gem 'byebug' 15 | 16 | gem 'concurrent-ruby', '< 1.3.5' 17 | -------------------------------------------------------------------------------- /spec/gemfiles/Gemfile.rails-6.1: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec path: '../../' 4 | 5 | gem 'rails', "~> 6.1.0" 6 | gem 'responders', '~> 3.0' 7 | gem 'sqlite3', '~> 1.4' 8 | gem 'sprockets', '~> 3.0' 9 | gem "jquery-rails" 10 | gem "thin" 11 | gem 'capybara', '~> 2.1' 12 | gem 'acts_as_caxlsx', git: 'https://github.com/caxlsx/acts_as_caxlsx.git' 13 | 14 | gem 'byebug' 15 | 16 | gem 'concurrent-ruby', '< 1.3.5' 17 | -------------------------------------------------------------------------------- /spec/gemfiles/Gemfile.rails-7.0: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec path: '../../' 4 | 5 | gem 'rails', "~> 7.0.0" 6 | gem 'responders', '~> 3.0' 7 | gem 'sqlite3', '~> 1.4' 8 | gem 'sprockets', '~> 3.0' 9 | gem "jquery-rails" 10 | gem "thin" 11 | gem 'capybara', '~> 2.1' 12 | gem 'acts_as_caxlsx', git: 'https://github.com/caxlsx/acts_as_caxlsx.git' 13 | 14 | gem 'byebug' 15 | 16 | gem 'concurrent-ruby', '< 1.3.5' 17 | -------------------------------------------------------------------------------- /spec/gemfiles/Gemfile.rails-7.1: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec path: '../../' 4 | 5 | gem 'rails', '~> 7.1.0' 6 | gem 'responders', '~> 3.0' 7 | gem 'sqlite3' 8 | gem 'sprockets', '~> 4.0' 9 | gem 'jquery-rails' 10 | gem 'thin' 11 | gem 'capybara', '~> 3.0' 12 | gem 'acts_as_caxlsx', git: 'https://github.com/caxlsx/acts_as_caxlsx.git' 13 | 14 | gem 'byebug' 15 | -------------------------------------------------------------------------------- /spec/gemfiles/Gemfile.rails-7.2: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec path: '../../' 4 | 5 | gem 'rails', '~> 7.2.0' 6 | gem 'responders', '~> 3.0' 7 | gem 'sqlite3' 8 | gem 'sprockets', '~> 4.0' 9 | gem 'jquery-rails' 10 | gem 'thin' 11 | gem 'capybara', '~> 3.0' 12 | gem 'acts_as_caxlsx', git: 'https://github.com/caxlsx/acts_as_caxlsx.git' 13 | 14 | gem 'byebug' 15 | -------------------------------------------------------------------------------- /spec/rails_app/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/rails_app/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require_tree . 14 | -------------------------------------------------------------------------------- /spec/rails_app/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /spec/rails_app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /spec/rails_app/app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | #--- 2 | # Excerpted from "Crafting Rails Applications", 3 | # published by The Pragmatic Bookshelf. 4 | # Copyrights apply to this code. It may not be used to create training material, 5 | # courses, books, articles, and the like. Contact us if you are in doubt. 6 | # We make no guarantees that this code is fit for any purpose. 7 | # Visit http://www.pragmaticprogrammer.com/titles/jvrails for more book information. 8 | #--- 9 | class HomeController < ApplicationController 10 | def index 11 | respond_to do |format| 12 | format.html 13 | format.xlsx 14 | end 15 | end 16 | 17 | def only_html; end 18 | 19 | def another 20 | render :xlsx => "index", :filename => "filename_test.xlsx" 21 | end 22 | 23 | def render_elsewhere 24 | case params[:type] 25 | when '1' 26 | render :xlsx => "home/index", :template => 'users/index' 27 | when '2' 28 | render :xlsx => "users/index", :template => 'users/index' 29 | when '3' 30 | render template: "users/index" 31 | when '4' 32 | render "users/index" 33 | else 34 | render :xlsx => "index" 35 | end 36 | end 37 | 38 | def render_file_path 39 | render :xlsx => Rails.root.join('app','views','users','index') 40 | end 41 | 42 | def withpartial 43 | end 44 | 45 | def useheader 46 | respond_to do |format| 47 | format.xlsx { 48 | if params[:set_direct] 49 | response.headers['Content-Disposition'] = "attachment; filename=\"filename_test.xlsx\"" 50 | else 51 | render xlsx: "useheader", filename: "filename_test.xlsx" 52 | end 53 | } 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/rails_app/app/controllers/likes_controller.rb: -------------------------------------------------------------------------------- 1 | class LikesController < ApplicationController 2 | # GET /likes 3 | # GET /likes.json 4 | def index 5 | @user = User.find(params[:user_id]) 6 | @likes = @user.likes 7 | 8 | respond_to do |format| 9 | format.html # index.html.erb 10 | format.xlsx 11 | end 12 | end 13 | 14 | def render_elsewhere 15 | @user = User.find(params[:user_id]) 16 | render :xlsx => "index", :template => 'users/index' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/rails_app/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | respond_to :xlsx, :html 3 | 4 | if Gem::Version.new("5.0") <= Rails.gem_version 5 | layout Proc.new { |c| return (c.request.format.symbol == :xlsx ? false : :default )} 6 | end 7 | 8 | # GET /users 9 | # GET /users.json 10 | def index 11 | @users = User.all 12 | 13 | respond_to do |format| 14 | format.html # index.html.erb 15 | format.xlsx 16 | end 17 | end 18 | 19 | def show 20 | @user = User.find(params[:id]) 21 | respond_with(@user) do |format| 22 | if Gem::Version.new("7.0") <= Rails.gem_version 23 | format.xlsx { render "respond_with" } 24 | else 25 | format.xlsx { render "respond_with.xlsx.axlsx" } 26 | end 27 | end 28 | end 29 | 30 | def send_instructions 31 | @user = User.find(params[:user_id]) 32 | @user.send_instructions 33 | if Rails.gem_version < Gem::Version.new("5.0") 34 | render text: "Email sent" 35 | else 36 | render plain: "Email sent" 37 | end 38 | end 39 | 40 | def export 41 | @user = User.find(params[:id]) 42 | respond_to do |format| 43 | format.xlsx do 44 | render xlsx: "export", filename: "export_#{@user.id}" 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/rails_app/app/mailers/notifier.rb: -------------------------------------------------------------------------------- 1 | class Notifier < ActionMailer::Base 2 | default :from => 'noreply@company.com' 3 | 4 | def instructions(user) 5 | @user = user 6 | 7 | # normal syntax 8 | xlsx = render_to_string handlers: [:axlsx], template: 'users/send_instructions', layout: false, formats: [:xlsx] 9 | if Rails.gem_version < Gem::Version.new("5.0") 10 | attachments["user_#{user.id}.xlsx"] = {mime_type: Mime::XLSX, content: xlsx} 11 | else 12 | attachments["user_#{user.id}.xlsx"] = {mime_type: Mime[:xlsx], content: xlsx} 13 | end 14 | 15 | mail :to => user.email, :subject => 'Instructions' 16 | end 17 | 18 | end -------------------------------------------------------------------------------- /spec/rails_app/app/models/like.rb: -------------------------------------------------------------------------------- 1 | class Like < ActiveRecord::Base 2 | belongs_to :user 3 | end 4 | -------------------------------------------------------------------------------- /spec/rails_app/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | acts_as_xlsx columns: [:id, :name, :last_name, :address, :email] 3 | 4 | has_many :likes 5 | 6 | def send_instructions 7 | Notifier.instructions(self).deliver 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/rails_app/app/views/home/_cover_sheet.xlsx.axlsx: -------------------------------------------------------------------------------- 1 | wb.add_worksheet(name: "Cover Sheet") do |sheet| 2 | sheet.add_row ['Cover', 'Sheet'] 3 | end -------------------------------------------------------------------------------- /spec/rails_app/app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 |
Hey, you can download the xlsx for this page by clicking the link below:
2 |<%= link_to "XLSX", home_path("xlsx") %>
3 |<%= link_to "Another", '/another.xlsx' %>
4 |<%= link_to "User header", '/useheader.xlsx' %>
5 | -------------------------------------------------------------------------------- /spec/rails_app/app/views/home/index.xlsx.axlsx: -------------------------------------------------------------------------------- 1 | wb = xlsx_package.workbook 2 | style_shout = wb.styles.add_style sz: 16, b: true, alignment: { horizontal: :center } 3 | wb.add_worksheet(name: "Foobar") do |sheet| 4 | sheet.add_row ['Bad', 'spellers', 'of', 'the', 'world', '...'] 5 | sheet.add_row ['Untie!'] 6 | sheet.merge_cells("A2:E2") 7 | sheet["A2"].style = style_shout 8 | end -------------------------------------------------------------------------------- /spec/rails_app/app/views/home/only_html.html.erb: -------------------------------------------------------------------------------- 1 |Foo, bar
2 | -------------------------------------------------------------------------------- /spec/rails_app/app/views/home/useheader.xlsx.axlsx: -------------------------------------------------------------------------------- 1 | wb = xlsx_package.workbook 2 | style_shout = wb.styles.add_style sz: 16, b: true, alignment: { horizontal: :center } 3 | wb.add_worksheet(name: "Foobar") do |sheet| 4 | sheet.add_row ['Bad', 'spellers', 'of', 'the', 'world', '...'] 5 | sheet.add_row ['Untie!'] 6 | sheet.merge_cells("A2:E2") 7 | sheet["A2"].style = style_shout 8 | end -------------------------------------------------------------------------------- /spec/rails_app/app/views/home/withpartial.xlsx.axlsx: -------------------------------------------------------------------------------- 1 | wb = xlsx_package.workbook 2 | render :partial => 'cover_sheet', :locals => {:wb => wb} 3 | style_shout = wb.styles.add_style sz: 16, b: true, alignment: { horizontal: :center } 4 | wb.add_worksheet(name: "Foobar") do |sheet| 5 | sheet.add_row ['Bad', 'spellers', 'of', 'the', 'world', '...'] 6 | sheet.add_row ['Untie!'] 7 | sheet.merge_cells("A2:E2") 8 | sheet["A2"].style = style_shout 9 | end -------------------------------------------------------------------------------- /spec/rails_app/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |Name | 6 |
---|
<%= like.name %> | 11 |
9 | You have successfully signed up to example.com,
10 | your username is: <%= @user.email %>.
11 |
Thanks for joining and have a great day!
13 | 14 | 15 | -------------------------------------------------------------------------------- /spec/rails_app/app/views/notifier/instructions.txt.erb: -------------------------------------------------------------------------------- 1 | Instructions 2 | 3 | You have successfully signed up to example.com, 4 | your username is: <%= @user.email %>. 5 | 6 | Thanks for joining and have a great day! 7 | -------------------------------------------------------------------------------- /spec/rails_app/app/views/users/export.xlsx.axlsx: -------------------------------------------------------------------------------- 1 | wb = xlsx_package.workbook 2 | style_shout = wb.styles.add_style sz: 16, b: true, alignment: { horizontal: :center } 3 | wb.add_worksheet(name: "Foobar") do |sheet| 4 | sheet.add_row ['Bad', 'spellers', 'of', 'the', 'world', '...'] 5 | sheet.add_row ['Untie!'] 6 | sheet.merge_cells("A2:E2") 7 | sheet["A2"].style = style_shout 8 | end -------------------------------------------------------------------------------- /spec/rails_app/app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 |Name | 6 |Last name | 7 |Address | 8 ||
---|---|---|---|
<%= user.name %> | 14 |<%= user.last_name %> | 15 |<%= user.address %> | 16 |<%= user.email %> | 17 |
You may have mistyped the address or the page may have moved.
63 |If you are the application owner check the logs for more information.
65 |Maybe you tried to change something you didn't have access to.
63 |If you are the application owner check the logs for more information.
65 |If you are the application owner check the logs for more information.
64 |