spreadsheet_architect 3.2.1 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +57 -69
- data/lib/spreadsheet_architect.rb +5 -0
- data/lib/spreadsheet_architect/axlsx_string_width_patch.rb +23 -0
- data/lib/spreadsheet_architect/class_methods/csv.rb +2 -0
- data/lib/spreadsheet_architect/class_methods/ods.rb +9 -9
- data/lib/spreadsheet_architect/class_methods/xlsx.rb +62 -6
- data/lib/spreadsheet_architect/utils.rb +72 -15
- data/lib/spreadsheet_architect/utils/xlsx.rb +2 -13
- data/lib/spreadsheet_architect/version.rb +1 -1
- data/test/dummy_app/app/assets/config/manifest.js +3 -0
- data/test/dummy_app/config/application.rb +2 -5
- data/test/dummy_app/db/test.sqlite3 +0 -0
- data/test/dummy_app/log/test.log +85059 -24302
- data/test/dummy_app/tmp/3.0.0.pre/integration/alt_xlsx.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/integration/csv.csv +6 -0
- data/test/dummy_app/tmp/3.0.0.pre/integration/ods.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/integration/xlsx.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/kitchen_sink.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/kitchen_sink.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/data.csv +3 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/data.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/data.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/empty.csv +1 -0
- data/test/dummy_app/tmp/{configurations/LegacyPlainRubyObject → 3.0.0.pre/models/ActiveModelObject}/empty.ods +0 -0
- data/test/dummy_app/tmp/{configurations → 3.0.0.pre/models}/ActiveModelObject/empty.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/instances.csv +6 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/instances.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/instances.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomColumnsMethodPost/data.csv +3 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomColumnsMethodPost/data.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomColumnsMethodPost/data.xlsx +0 -0
- data/test/dummy_app/tmp/{configurations/ActiveModelObject → 3.0.0.pre/models/CustomColumnsMethodPost}/empty.csv +0 -0
- data/test/dummy_app/tmp/{configurations/Post → 3.0.0.pre/models/CustomColumnsMethodPost}/empty.ods +0 -0
- data/test/dummy_app/tmp/{configurations/CustomPost → 3.0.0.pre/models/CustomColumnsMethodPost}/empty.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomColumnsMethodPost/instances.csv +6 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomColumnsMethodPost/instances.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomColumnsMethodPost/instances.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/data.csv +3 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/data.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/data.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/empty.csv +1 -0
- data/test/dummy_app/tmp/{configurations/ActiveModelObject → 3.0.0.pre/models/CustomPost}/empty.ods +0 -0
- data/test/dummy_app/tmp/{configurations/LegacyPlainRubyObject → 3.0.0.pre/models/CustomPost}/empty.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/instances.csv +6 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/instances.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/instances.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/data.csv +3 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/data.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/data.xlsx +0 -0
- data/test/dummy_app/tmp/{configurations/CustomPost → 3.0.0.pre/models/LegacyPlainRubyObject}/empty.csv +0 -0
- data/test/dummy_app/tmp/{configurations/SpreadsheetArchitect → 3.0.0.pre/models/LegacyPlainRubyObject}/empty.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/empty.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/instances.csv +6 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/instances.ods +0 -0
- data/test/dummy_app/tmp/{configurations → 3.0.0.pre/models}/LegacyPlainRubyObject/instances.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/data.csv +3 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/data.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/data.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/empty.csv +1 -0
- data/test/dummy_app/tmp/{configurations/CustomPost → 3.0.0.pre/models/PlainRubyObject}/empty.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/empty.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/instances.csv +6 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/instances.ods +0 -0
- data/test/dummy_app/tmp/{configurations → 3.0.0.pre/models}/PlainRubyObject/instances.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/Post/data.csv +3 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/Post/data.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/Post/data.xlsx +0 -0
- data/test/dummy_app/tmp/{configurations/LegacyPlainRubyObject → 3.0.0.pre/models/Post}/empty.csv +0 -0
- data/test/dummy_app/tmp/{configurations/PlainRubyObject → 3.0.0.pre/models/Post}/empty.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/Post/empty.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/Post/instances.csv +6 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/Post/instances.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/models/Post/instances.xlsx +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/multi_sheet.ods +0 -0
- data/test/dummy_app/tmp/3.0.0.pre/multi_sheet.xlsx +0 -0
- data/test/test_helper.rb +8 -8
- data/test/unit/exceptions_test.rb +9 -1
- data/test/unit/kitchen_sink_test.rb +14 -8
- data/test/unit/regressions_test.rb +11 -0
- data/test/unit/utils_test.rb +35 -1
- data/test/unit/xlsx_freeze_test.rb +44 -0
- data/test/unit/xlsx_utils_test.rb +0 -21
- metadata +166 -152
- data/lib/spreadsheet_architect/monkey_patches/axlsx_column_width.rb +0 -56
- data/test/custom_assertions.rb +0 -21
- data/test/dummy_app/log/development.log +0 -28
- data/test/dummy_app/tmp/configurations/ActiveModelObject/data.csv +0 -3
- data/test/dummy_app/tmp/configurations/ActiveModelObject/data.ods +0 -0
- data/test/dummy_app/tmp/configurations/ActiveModelObject/data.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/ActiveModelObject/instances.csv +0 -6
- data/test/dummy_app/tmp/configurations/ActiveModelObject/instances.ods +0 -0
- data/test/dummy_app/tmp/configurations/ActiveModelObject/instances.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/CustomPost/data.csv +0 -3
- data/test/dummy_app/tmp/configurations/CustomPost/data.ods +0 -0
- data/test/dummy_app/tmp/configurations/CustomPost/data.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/CustomPost/instances.csv +0 -6
- data/test/dummy_app/tmp/configurations/CustomPost/instances.ods +0 -0
- data/test/dummy_app/tmp/configurations/CustomPost/instances.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/LegacyPlainRubyObject/data.csv +0 -3
- data/test/dummy_app/tmp/configurations/LegacyPlainRubyObject/data.ods +0 -0
- data/test/dummy_app/tmp/configurations/LegacyPlainRubyObject/data.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/LegacyPlainRubyObject/instances.csv +0 -6
- data/test/dummy_app/tmp/configurations/LegacyPlainRubyObject/instances.ods +0 -0
- data/test/dummy_app/tmp/configurations/PlainRubyObject/data.csv +0 -3
- data/test/dummy_app/tmp/configurations/PlainRubyObject/data.ods +0 -0
- data/test/dummy_app/tmp/configurations/PlainRubyObject/data.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/PlainRubyObject/empty.csv +0 -0
- data/test/dummy_app/tmp/configurations/PlainRubyObject/empty.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/PlainRubyObject/instances.csv +0 -6
- data/test/dummy_app/tmp/configurations/PlainRubyObject/instances.ods +0 -0
- data/test/dummy_app/tmp/configurations/Post/data.csv +0 -3
- data/test/dummy_app/tmp/configurations/Post/data.ods +0 -0
- data/test/dummy_app/tmp/configurations/Post/data.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/Post/empty.csv +0 -0
- data/test/dummy_app/tmp/configurations/Post/empty.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/SpreadsheetArchitect/data.csv +0 -3
- data/test/dummy_app/tmp/configurations/SpreadsheetArchitect/data.ods +0 -0
- data/test/dummy_app/tmp/configurations/SpreadsheetArchitect/data.xlsx +0 -0
- data/test/dummy_app/tmp/configurations/SpreadsheetArchitect/empty.csv +0 -0
- data/test/dummy_app/tmp/configurations/SpreadsheetArchitect/empty.xlsx +0 -0
- data/test/dummy_app/tmp/integration_tests/alt_xlsx.xlsx +0 -0
- data/test/dummy_app/tmp/integration_tests/csv.csv +0 -6
- data/test/dummy_app/tmp/integration_tests/ods.ods +0 -0
- data/test/dummy_app/tmp/integration_tests/xlsx.xlsx +0 -0
- data/test/dummy_app/tmp/kitchen_sink.ods +0 -0
- data/test/dummy_app/tmp/kitchen_sink.xlsx +0 -0
- data/test/dummy_app/tmp/multi_sheet.ods +0 -0
- data/test/dummy_app/tmp/multi_sheet.xlsx +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0e103cc275bf05dabe10a86cb7f74dcc4a412daa2328e607ca70d7af22ea40b
|
4
|
+
data.tar.gz: 5799934cf9f9e177ca49ed33d749d01e535143cc18bf6990082b834fe6d9a6d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cee7b3f8dfc727d63c1f84f06f94c1bf3eaf46283b15b698724a5d0f57d3ef39728838b5295376df6e112bbb3141579c6ebc4189e8af85bd82558abe0cbd2546
|
7
|
+
data.tar.gz: 13c7e7db2193bb37be8911592e5eab725700e047dc8437c455adcad0ae8574f3187ac2d40412b82e706e90a286ec38b15be248c013b0fc5ed9db1d0c0b943e98
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,28 @@
|
|
1
1
|
CHANGELOG
|
2
2
|
---------
|
3
3
|
|
4
|
+
- **Unreleased**
|
5
|
+
- Nothing yet
|
6
|
+
- **4.1.0**
|
7
|
+
- Raise ArgumentError when invalid option names are given
|
8
|
+
- **4.0.1**
|
9
|
+
- Fix bug with `headers: false` where a blank header row is still added
|
10
|
+
- Fix Bug for older version of `caxlsx` v2.0.2
|
11
|
+
- **4.0.0**
|
12
|
+
- Switch to the `caxlsx` gem (Community Axlsx) from the legacy unmaintained `axlsx` gem. Axlsx has had a long history of being poorly maintained so this community gem improves the situation.
|
13
|
+
- Require Ruby 2.3+
|
14
|
+
- Ensure all options using Hash are automatically converted to symbol only hashes
|
15
|
+
- Add XLSX option `:freeze` to freeze custom sections of your spreadsheet
|
16
|
+
- Add XLSX option `:freeze_headers` to freeze the headers of your spreadsheet
|
17
|
+
- Remove old Axlsx patch for column width
|
18
|
+
- Backport new code for `string_width` calculations to Axlsx 3.0.1 and below.
|
19
|
+
- **3.3.1**
|
20
|
+
- [Issue #30](https://github.com/westonganger/spreadsheet_architect/issues/30) - Fix duplicate constant warning for XLSX_COLUMN_TYPES
|
21
|
+
- **3.3.0**
|
22
|
+
- Fix `:borders` option, was broken in v3.2.1
|
23
|
+
- Fix bug when passing `false` to `:headers` option
|
24
|
+
- Raise error when unsupported column type is passed
|
25
|
+
- Remove claimed support for `:currency` and `:percent` for ODS spreadsheets as they were not working. PR Wanted.
|
4
26
|
- **3.2.1** - April 10, 2019
|
5
27
|
- Fix bug when using `column_style` option with `include_header: true` & letter based column numbering
|
6
28
|
- **3.2.0** - September 14, 2018
|
data/README.md
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# Spreadsheet Architect
|
2
2
|
|
3
3
|
<a href="https://badge.fury.io/rb/spreadsheet_architect" target="_blank"><img height="21" style='border:0px;height:21px;' border='0' src="https://badge.fury.io/rb/spreadsheet_architect.svg" alt="Gem Version"></a>
|
4
|
-
<a href='https://travis-ci.
|
4
|
+
<a href='https://travis-ci.com/westonganger/spreadsheet_architect' target='_blank'><img height='21' style='border:0px;height:21px;' src='https://api.travis-ci.org/westonganger/spreadsheet_architect.svg?branch=master' border='0' alt='Build Status' /></a>
|
5
5
|
<a href='https://rubygems.org/gems/spreadsheet_architect' target='_blank'><img height='21' style='border:0px;height:21px;' src='https://ruby-gem-downloads-badge.herokuapp.com/spreadsheet_architect?label=rubygems&type=total&total_label=downloads&color=brightgreen' border='0' alt='RubyGems Downloads' /></a>
|
6
|
-
<a href='https://ko-fi.com/A5071NK' target='_blank'><img height='22' style='border:0px;height:22px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=a' border='0' alt='Buy Me a Coffee' /></a>
|
6
|
+
<a href='https://ko-fi.com/A5071NK' target='_blank'><img height='22' style='border:0px;height:22px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=a' border='0' alt='Buy Me a Coffee' /></a>
|
7
7
|
|
8
8
|
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.
|
9
9
|
|
10
10
|
Key Features:
|
11
11
|
|
12
12
|
- Dead simple custom spreadsheets with custom data
|
13
|
-
- Data Sources: ActiveRecord relations, array of plain Ruby object instances
|
13
|
+
- Data Sources: Tabular Data from an Array, ActiveRecord relations, or array of plain Ruby object instances
|
14
14
|
- Easily style and customize spreadsheets
|
15
15
|
- Create multi sheet spreadsheets
|
16
16
|
- Setting Class/Model or Project specific defaults
|
@@ -22,10 +22,12 @@ Key Features:
|
|
22
22
|
gem 'spreadsheet_architect'
|
23
23
|
```
|
24
24
|
|
25
|
-
# General Usage
|
25
|
+
# General Usage
|
26
26
|
|
27
27
|
### Tabular (Array) Data
|
28
28
|
|
29
|
+
The simplest and preffered usage is to simply create the data array yourself.
|
30
|
+
|
29
31
|
```ruby
|
30
32
|
headers = ['Col 1','Col 2','Col 3']
|
31
33
|
data = [[1,2,3], [4,5,6], [7,8,9]]
|
@@ -34,50 +36,25 @@ SpreadsheetArchitect.to_ods(headers: headers, data: data)
|
|
34
36
|
SpreadsheetArchitect.to_csv(headers: headers, data: data)
|
35
37
|
```
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
```ruby
|
40
|
-
posts = Post.order(name: :asc).where(published: true)
|
41
|
-
# OR
|
42
|
-
posts = 10.times.map{|i| Post.new(number: i)}
|
39
|
+
Using 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 things start to get slow. One of my favourites for Rails is [light_record](https://github.com/Paxa/light_record)
|
43
40
|
|
44
|
-
|
45
|
-
SpreadsheetArchitect.to_ods(instances: posts)
|
46
|
-
SpreadsheetArchitect.to_csv(instances: posts)
|
47
|
-
```
|
41
|
+
### Rails Relations or an Array of plain Ruby object instances
|
48
42
|
|
49
|
-
|
43
|
+
If you would like to add the methods `to_xlsx`, `to_ods`, `to_csv`, `to_axlsx_package`, `to_rodf_spreadsheet` to some class, you can simply include the SpreadsheetArchitect module to whichever classes you choose. For example:
|
50
44
|
|
51
45
|
```ruby
|
52
|
-
class
|
46
|
+
class Post < ApplicationRecord
|
53
47
|
include SpreadsheetArchitect
|
54
48
|
end
|
55
49
|
```
|
56
50
|
|
57
|
-
|
58
|
-
|
59
|
-
```ruby
|
60
|
-
posts = Post.order(name: :asc).where(published: true)
|
61
|
-
posts.to_xlsx
|
62
|
-
posts.to_ods
|
63
|
-
posts.to_csv
|
64
|
-
|
65
|
-
# Plain Ruby Objects
|
66
|
-
posts_array = 10.times.map{|i| Post.new(number: i)}
|
67
|
-
Post.to_xlsx(instances: posts_array)
|
68
|
-
Post.to_ods(instances: posts_array)
|
69
|
-
Post.to_csv(instances: posts_array)
|
70
|
-
```
|
71
|
-
|
72
|
-
# Usage with Instances / ActiveRecord Relations
|
73
|
-
|
74
|
-
When NOT using the `:data` option, ie. on an AR Relation or using the `:instances` option, Spreadsheet Architect requires an instance method defined on the class to generate the data. It looks for the `spreadsheet_columns` method on the class. If you are using on an ActiveRecord model and that method is not defined, it would fallback to the models `column_names` method (not recommended). If using the `:data` option this is ignored.
|
51
|
+
When 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.
|
75
52
|
|
76
53
|
```ruby
|
77
54
|
class Post
|
55
|
+
include SpreadsheetArchitect
|
78
56
|
|
79
57
|
def spreadsheet_columns
|
80
|
-
|
81
58
|
### Column format is: [Header, Cell Data / Method (if symbol) to Call on each Instance, (optional) Cell Type]
|
82
59
|
[
|
83
60
|
['Title', :title],
|
@@ -89,15 +66,30 @@ class Post
|
|
89
66
|
['Rating', :rating],
|
90
67
|
['Category/Tags', "#{category.name} - #{tags.collect(&:name).join(', ')}"]
|
91
68
|
]
|
69
|
+
end
|
70
|
+
|
92
71
|
end
|
72
|
+
```
|
73
|
+
|
74
|
+
Then use it on the class or ActiveRecord relations of the class
|
93
75
|
|
94
|
-
|
76
|
+
```ruby
|
77
|
+
posts = Post.order(name: :asc).where(published: true)
|
78
|
+
posts.to_xlsx
|
79
|
+
posts.to_ods
|
80
|
+
posts.to_csv
|
81
|
+
|
82
|
+
# Plain Ruby Objects
|
83
|
+
posts_array = 10.times.map{|i| Post.new(number: i)}
|
84
|
+
Post.to_xlsx(instances: posts_array)
|
85
|
+
Post.to_ods(instances: posts_array)
|
86
|
+
Post.to_csv(instances: posts_array)
|
95
87
|
```
|
96
88
|
|
97
|
-
If you want to use a different method name then `spreadsheet_columns` you can pass a method name
|
89
|
+
If you want to use a different method name then `spreadsheet_columns` you can pass a method name to the `:spreadsheet_columns` option.
|
98
90
|
|
99
91
|
```ruby
|
100
|
-
Post.to_xlsx(instances: posts, spreadsheet_columns: :
|
92
|
+
Post.to_xlsx(instances: posts, spreadsheet_columns: :my_special_method)
|
101
93
|
```
|
102
94
|
|
103
95
|
Alternatively, you can pass a Proc/lambda 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.
|
@@ -119,10 +111,19 @@ Post.to_xlsx(instances: posts, spreadsheet_columns: Proc.new{|instance|
|
|
119
111
|
|
120
112
|
# Sending & Saving Spreadsheets
|
121
113
|
|
122
|
-
### Method 1:
|
114
|
+
### Method 1: Save to a file manually
|
123
115
|
|
124
116
|
```ruby
|
117
|
+
file_data = SpreadsheetArchitect.to_xlsx(headers: headers, data: data)
|
125
118
|
|
119
|
+
File.open('path/to/file.xlsx', 'w+b') do |f|
|
120
|
+
f.write file_data
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
### Method 2: Send Data via Rails Controller
|
125
|
+
|
126
|
+
```ruby
|
126
127
|
class PostsController < ActionController::Base
|
127
128
|
respond_to :html, :xlsx, :ods, :csv
|
128
129
|
|
@@ -176,44 +177,27 @@ class PostsController < ActionController::Base
|
|
176
177
|
end
|
177
178
|
```
|
178
179
|
|
179
|
-
|
180
|
+
# Multi Sheet Spreadsheets
|
180
181
|
|
181
|
-
|
182
|
-
### Ex. with ActiveRecord relation
|
183
|
-
file_data = Post.order(published_at: :asc).to_xlsx
|
184
|
-
File.open('path/to/file.xlsx', 'w+b') do |f|
|
185
|
-
f.write file_data
|
186
|
-
end
|
182
|
+
### XLSX
|
187
183
|
|
188
|
-
file_data = Post.order(published_at: :asc).to_ods
|
189
|
-
File.open('path/to/file.ods', 'w+b') do |f|
|
190
|
-
f.write file_data
|
191
|
-
end
|
192
|
-
|
193
|
-
file_data = Post.order(published_at: :asc).to_csv
|
194
|
-
File.open('path/to/file.csv', 'w+b') do |f|
|
195
|
-
f.write file_data
|
196
|
-
end
|
197
|
-
```
|
198
|
-
|
199
|
-
# Multi Sheet XLSX Spreadsheets
|
200
184
|
```ruby
|
201
185
|
axlsx_package = SpreadsheetArchitect.to_axlsx_package({headers: headers, data: data})
|
202
|
-
axlsx_package = SpreadsheetArchitect.to_axlsx_package({headers: headers, data: data},
|
186
|
+
axlsx_package = SpreadsheetArchitect.to_axlsx_package({headers: headers, data: data}, axlsx_package)
|
203
187
|
|
204
|
-
File.open('path/to/
|
188
|
+
File.open('path/to/multi_sheet_file.xlsx', 'w+b') do |f|
|
205
189
|
f.write axlsx_package.to_stream.read
|
206
190
|
end
|
207
191
|
```
|
208
192
|
|
209
193
|
See this file for more details: https://github.com/westonganger/spreadsheet_architect/blob/master/test/spreadsheet_architect/multi_sheet_test.rb
|
210
194
|
|
211
|
-
###
|
195
|
+
### ODS
|
212
196
|
```ruby
|
213
197
|
ods_spreadsheet = SpreadsheetArchitect.to_rodf_spreadsheet({headers: headers, data: data})
|
214
|
-
ods_spreadsheet = SpreadsheetArchitect.to_rodf_spreadsheet({headers: headers, data: data},
|
198
|
+
ods_spreadsheet = SpreadsheetArchitect.to_rodf_spreadsheet({headers: headers, data: data}, ods_spreadsheet)
|
215
199
|
|
216
|
-
File.open('path/to/
|
200
|
+
File.open('path/to/multi_sheet_file.ods', 'w+b') do |f|
|
217
201
|
f.write ods_spreadsheet
|
218
202
|
end
|
219
203
|
```
|
@@ -231,15 +215,17 @@ See this file for more details: https://github.com/westonganger/spreadsheet_arch
|
|
231
215
|
|**spreadsheet_columns**<br>*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.<br>If 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.<br><br>If a Proc value is passed it will be evaluated on the instance object.<br><br>If a Symbol or String value is passed then it will search the instance for a method name that matches and call it. |
|
232
216
|
|**headers**<br>*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. |
|
233
217
|
|**sheet_name**<br>*String*|`Sheet1`||
|
234
|
-
|**header_style**<br>*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](https://github.com/westonganger/spreadsheet_architect/blob/master/docs/
|
235
|
-
|**row_style**<br>*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](https://github.com/westonganger/spreadsheet_architect/blob/master/docs/
|
218
|
+
|**header_style**<br>*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](https://github.com/westonganger/spreadsheet_architect/blob/master/docs/axlsx_style_reference.md)|
|
219
|
+
|**row_style**<br>*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](https://github.com/westonganger/spreadsheet_architect/blob/master/docs/axlsx_style_reference.md)|
|
236
220
|
|**column_styles**<br>*Array*||[See this example for usage](https://github.com/westonganger/spreadsheet_architect/blob/master/test/unit/kitchen_sink_test.rb)|
|
237
221
|
|**range_styles**<br>*Array*||[See this example for usage](https://github.com/westonganger/spreadsheet_architect/blob/master/test/unit/kitchen_sink_test.rb)|
|
238
222
|
|**conditional_row_styles**<br>*Array*||[See this example for usage](https://github.com/westonganger/spreadsheet_architect/blob/master/test/unit/kitchen_sink_test.rb). The if/unless proc will called with the following args: `row_index`, `row_data`|
|
239
223
|
|**merges**<br>*Array*||Merge cells. [See this example for usage](https://github.com/westonganger/spreadsheet_architect/blob/master/test/unit/kitchen_sink_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.|
|
240
224
|
|**borders**<br>*Array*||[See this example for usage](https://github.com/westonganger/spreadsheet_architect/blob/master/test/unit/kitchen_sink_test.rb)|
|
241
|
-
|**column_types**<br>*Array*||Valid types for XLSX are :string, :integer, :float, :boolean, nil = auto determine.|
|
225
|
+
|**column_types**<br>*Array*||Valid types for XLSX are :string, :integer, :float, :date, :time, :boolean, nil = auto determine.|
|
242
226
|
|**column_widths**<br>*Array*||Sometimes you may want explicit column widths. Use nil if you want a column to autofit again.|
|
227
|
+
|**freeze_headers**<br>*Boolean*||Make all header rows frozen/fixed so they do not scroll.|
|
228
|
+
|**freeze**<br>* Hash*|`{rows: (1..4), columns: :all}`|Make all specified rows and columns frozen/fixed so they do not scroll.|
|
243
229
|
|
244
230
|
## `to_axlsx_spreadsheet(options={}, axlsx_package_to_join=nil)`
|
245
231
|
Same options as `to_xlsx`
|
@@ -255,7 +241,7 @@ Same options as `to_xlsx`
|
|
255
241
|
|**sheet_name**<br>*String*|`Sheet1`||
|
256
242
|
|**header_style**<br>*Hash*|`{background_color: "AAAAAA", color: "FFFFFF", align: :center, font_size: 10, bold: true}`|Note: Currently ODS only supports these options|
|
257
243
|
|**row_style**<br>*Hash*|`{background_color: nil, color: "000000", align: :left, font_size: 10, bold: false}`|Styles for non-header rows. Currently ODS only supports these options|
|
258
|
-
|**column_types**<br>*Array*||Valid types for ODS are :string, :float :
|
244
|
+
|**column_types**<br>*Array*||Valid types for ODS are :string, :float, :date, :time, :boolean, nil = auto determine. Due to [RODF Issue #19](https://github.com/thiagoarrais/rodf/issues/19), :date/:time will be converted to :string |
|
259
245
|
|
260
246
|
## `to_rodf_spreadsheet(options={}, spreadsheet_to_join=nil)`
|
261
247
|
Same options as `to_ods`
|
@@ -273,7 +259,9 @@ Same options as `to_ods`
|
|
273
259
|
# Change class-wide default method options
|
274
260
|
|
275
261
|
```ruby
|
276
|
-
class Post
|
262
|
+
class Post < ApplicationRecord
|
263
|
+
include SpreadsheetArchitect
|
264
|
+
|
277
265
|
def spreadsheet_columns
|
278
266
|
[:name, :content]
|
279
267
|
end
|
@@ -335,7 +323,7 @@ We use the `appraisal` gem for testing multiple versions of `axlsx`. Please use
|
|
335
323
|
1. `bundle exec appraisal install`
|
336
324
|
2. `bundle exec appraisal rake test`
|
337
325
|
|
338
|
-
At this time the spreadsheets generated by the test suite are manually inspected. After running the tests, the test output can be viewed at `
|
326
|
+
At this time the spreadsheets generated by the test suite are manually inspected. After running the tests, the test output can be viewed at `tmp/#{alxsx_version}/*`
|
339
327
|
|
340
328
|
# Credits
|
341
329
|
|
@@ -10,6 +10,7 @@ require 'spreadsheet_architect/class_methods/ods'
|
|
10
10
|
require 'spreadsheet_architect/class_methods/xlsx'
|
11
11
|
|
12
12
|
module SpreadsheetArchitect
|
13
|
+
|
13
14
|
def self.included(base)
|
14
15
|
base.send(:extend, ClassMethods)
|
15
16
|
end
|
@@ -33,4 +34,8 @@ module SpreadsheetArchitect
|
|
33
34
|
def self.default_options
|
34
35
|
@default_options
|
35
36
|
end
|
37
|
+
|
38
|
+
XLSX_COLUMN_TYPES = [:string, :integer, :float, :date, :time, :boolean].freeze
|
39
|
+
ODS_COLUMN_TYPES = [:string, :float, :date, :time, :boolean].freeze
|
40
|
+
|
36
41
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
if Axlsx::VERSION.to_f < 3.1
|
2
|
+
|
3
|
+
Axlsx::Cell.class_eval do
|
4
|
+
private
|
5
|
+
|
6
|
+
def string_width(string, font_size)
|
7
|
+
font_scale = font_size / 10.0
|
8
|
+
(string.to_s.size + 3) * font_scale
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
if defined?(Axlsx::RichTextRun)
|
13
|
+
Axlsx::RichTextRun.class_eval do
|
14
|
+
private
|
15
|
+
|
16
|
+
def string_width(string, font_size)
|
17
|
+
font_scale = font_size / 10.0
|
18
|
+
string.to_s.size * font_scale
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -2,6 +2,7 @@ require 'csv'
|
|
2
2
|
|
3
3
|
module SpreadsheetArchitect
|
4
4
|
module ClassMethods
|
5
|
+
|
5
6
|
def to_csv(opts={})
|
6
7
|
opts = SpreadsheetArchitect::Utils.get_options(opts, self)
|
7
8
|
options = SpreadsheetArchitect::Utils.get_cell_data(opts, self)
|
@@ -18,5 +19,6 @@ module SpreadsheetArchitect
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
end
|
22
|
+
|
21
23
|
end
|
22
24
|
end
|
@@ -2,6 +2,7 @@ require 'rodf'
|
|
2
2
|
|
3
3
|
module SpreadsheetArchitect
|
4
4
|
module ClassMethods
|
5
|
+
|
5
6
|
def to_ods(opts={})
|
6
7
|
return to_rodf_spreadsheet(opts).bytes
|
7
8
|
end
|
@@ -10,6 +11,10 @@ module SpreadsheetArchitect
|
|
10
11
|
opts = SpreadsheetArchitect::Utils.get_options(opts, self)
|
11
12
|
options = SpreadsheetArchitect::Utils.get_cell_data(opts, self)
|
12
13
|
|
14
|
+
if options[:column_types] && !(options[:column_types].compact.collect(&:to_sym) - SpreadsheetArchitect::ODS_COLUMN_TYPES).empty?
|
15
|
+
raise SpreadsheetArchitect::Exceptions::ArgumentError.new("Invalid column type. Valid ODS values are #{SpreadsheetArchitect::ODS_COLUMN_TYPES}")
|
16
|
+
end
|
17
|
+
|
13
18
|
if !spreadsheet
|
14
19
|
spreadsheet = RODF::Spreadsheet.new
|
15
20
|
end
|
@@ -48,21 +53,16 @@ module SpreadsheetArchitect
|
|
48
53
|
options[:data].each_with_index do |row_data, index|
|
49
54
|
row do
|
50
55
|
row_data.each_with_index do |val, i|
|
51
|
-
if options[:
|
52
|
-
type = options[:
|
56
|
+
if options[:column_types]
|
57
|
+
type = options[:column_types][i]
|
53
58
|
end
|
54
59
|
|
55
|
-
if type
|
56
|
-
if [:date, :time].include?(type)
|
57
|
-
type = :string
|
58
|
-
val = val.to_s
|
59
|
-
end
|
60
|
-
elsif val.respond_to?(:strftime)
|
60
|
+
if (type && [:date, :time].include?(type)) || val.respond_to?(:strftime)
|
61
61
|
type = :string
|
62
62
|
val = val.to_s
|
63
63
|
end
|
64
64
|
|
65
|
-
cell val, style: :row_style, type:
|
65
|
+
cell val, style: :row_style, type: type
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'axlsx'
|
2
2
|
require 'axlsx_styler'
|
3
3
|
|
4
|
-
require 'spreadsheet_architect/
|
4
|
+
require 'spreadsheet_architect/axlsx_string_width_patch'
|
5
5
|
|
6
6
|
module SpreadsheetArchitect
|
7
7
|
module ClassMethods
|
8
|
+
|
8
9
|
def to_xlsx(opts={})
|
9
10
|
return to_axlsx_package(opts).to_stream.read
|
10
11
|
end
|
@@ -12,7 +13,11 @@ module SpreadsheetArchitect
|
|
12
13
|
def to_axlsx_package(opts={}, package=nil)
|
13
14
|
opts = SpreadsheetArchitect::Utils.get_options(opts, self)
|
14
15
|
options = SpreadsheetArchitect::Utils.get_cell_data(opts, self)
|
15
|
-
|
16
|
+
|
17
|
+
if options[:column_types] && !(options[:column_types].compact.collect(&:to_sym) - SpreadsheetArchitect::XLSX_COLUMN_TYPES).empty?
|
18
|
+
raise SpreadsheetArchitect::Exceptions::ArgumentError.new("Invalid column type. Valid XLSX values are #{SpreadsheetArchitect::XLSX_COLUMN_TYPES}")
|
19
|
+
end
|
20
|
+
|
16
21
|
header_style = SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(options[:header_style])
|
17
22
|
row_style = SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(options[:row_style])
|
18
23
|
|
@@ -42,10 +47,10 @@ module SpreadsheetArchitect
|
|
42
47
|
|
43
48
|
if options[:conditional_row_styles]
|
44
49
|
conditional_styles_for_row = SpreadsheetArchitect::Utils::XLSX.conditional_styles_for_row(options[:conditional_row_styles], row_index, header_row)
|
45
|
-
|
50
|
+
|
46
51
|
unless conditional_styles_for_row.empty?
|
47
52
|
sheet.add_style(
|
48
|
-
"#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES.first}#{row_index+1}:#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES[max_row_length-1]}#{row_index+1}",
|
53
|
+
"#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES.first}#{row_index+1}:#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES[max_row_length-1]}#{row_index+1}",
|
49
54
|
SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(conditional_styles_for_row)
|
50
55
|
)
|
51
56
|
end
|
@@ -102,11 +107,13 @@ module SpreadsheetArchitect
|
|
102
107
|
sheet.add_row row_data, style: styles, types: types
|
103
108
|
|
104
109
|
if options[:conditional_row_styles]
|
110
|
+
options[:conditional_row_styles] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:conditional_row_styles])
|
111
|
+
|
105
112
|
conditional_styles_for_row = SpreadsheetArchitect::Utils::XLSX.conditional_styles_for_row(options[:conditional_row_styles], row_index, row_data)
|
106
|
-
|
113
|
+
|
107
114
|
unless conditional_styles_for_row.empty?
|
108
115
|
sheet.add_style(
|
109
|
-
"#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES.first}#{row_index+1}:#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES[max_row_length-1]}#{row_index+1}",
|
116
|
+
"#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES.first}#{row_index+1}:#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES[max_row_length-1]}#{row_index+1}",
|
110
117
|
SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(conditional_styles_for_row)
|
111
118
|
)
|
112
119
|
end
|
@@ -122,16 +129,22 @@ module SpreadsheetArchitect
|
|
122
129
|
end
|
123
130
|
|
124
131
|
if options[:borders]
|
132
|
+
options[:borders] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:borders])
|
133
|
+
|
125
134
|
options[:borders].each do |x|
|
126
135
|
if x[:range].is_a?(Hash)
|
127
136
|
x[:range] = SpreadsheetArchitect::Utils::XLSX.range_hash_to_str(x[:range], max_row_length, num_rows)
|
128
137
|
else
|
129
138
|
SpreadsheetArchitect::Utils::XLSX.verify_range(x[:range], num_rows)
|
130
139
|
end
|
140
|
+
|
141
|
+
sheet.add_border x[:range], (x[:border_styles] || x[:styles])
|
131
142
|
end
|
132
143
|
end
|
133
144
|
|
134
145
|
if options[:column_styles]
|
146
|
+
options[:column_styles] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:column_styles])
|
147
|
+
|
135
148
|
options[:column_styles].each do |x|
|
136
149
|
start_row = (options[:headers] ? options[:headers].count : 0) + 1
|
137
150
|
|
@@ -163,6 +176,8 @@ module SpreadsheetArchitect
|
|
163
176
|
end
|
164
177
|
|
165
178
|
if options[:range_styles]
|
179
|
+
options[:range_styles] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:range_styles])
|
180
|
+
|
166
181
|
options[:range_styles].each do |x|
|
167
182
|
styles = SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(x[:styles])
|
168
183
|
|
@@ -177,6 +192,8 @@ module SpreadsheetArchitect
|
|
177
192
|
end
|
178
193
|
|
179
194
|
if options[:merges]
|
195
|
+
options[:merges] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:merges])
|
196
|
+
|
180
197
|
options[:merges].each do |x|
|
181
198
|
if x[:range].is_a?(Hash)
|
182
199
|
x[:range] = SpreadsheetArchitect::Utils::XLSX.range_hash_to_str(x[:range], max_row_length, num_rows)
|
@@ -187,9 +204,48 @@ module SpreadsheetArchitect
|
|
187
204
|
sheet.merge_cells x[:range]
|
188
205
|
end
|
189
206
|
end
|
207
|
+
|
208
|
+
if options[:freeze_headers]
|
209
|
+
sheet.sheet_view.pane do |pane|
|
210
|
+
pane.state = :frozen
|
211
|
+
pane.y_split = options[:headers].count
|
212
|
+
end
|
213
|
+
|
214
|
+
elsif options[:freeze]
|
215
|
+
options[:freeze] = SpreadsheetArchitect::Utils.symbolize_keys(options[:freeze])
|
216
|
+
|
217
|
+
sheet.sheet_view.pane do |pane|
|
218
|
+
pane.state = :frozen
|
219
|
+
|
220
|
+
### Currently not working
|
221
|
+
#if options[:freeze][:active_pane]
|
222
|
+
# Axlsx.validate_pane_type(options[:freeze][:active_pane])
|
223
|
+
# pane.active_pane = options[:freeze][:active_pane]
|
224
|
+
#else
|
225
|
+
# pane.active_pane = :bottom_right
|
226
|
+
#end
|
227
|
+
|
228
|
+
if !options[:freeze][:rows]
|
229
|
+
raise SpreadsheetArchitect::Exceptions::ArgumentError.new("The :rows key must be specified in the :freeze option hash")
|
230
|
+
elsif options[:freeze][:rows].is_a?(Range)
|
231
|
+
pane.y_split = options[:freeze][:rows].count
|
232
|
+
else
|
233
|
+
pane.y_split = 1
|
234
|
+
end
|
235
|
+
|
236
|
+
if options[:freeze][:columns] && options[:freeze][:columns] != :all
|
237
|
+
if options[:freeze][:columns].is_a?(Range)
|
238
|
+
pane.x_split = options[:freeze][:columns].count
|
239
|
+
else
|
240
|
+
pane.x_split = 1
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
190
245
|
end
|
191
246
|
|
192
247
|
return package
|
193
248
|
end
|
249
|
+
|
194
250
|
end
|
195
251
|
end
|