export_to 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +8 -0
- data/.github/workflows/publish.yml +121 -0
- data/CHANGELOG.md +113 -0
- data/CLAUDE.md +21 -0
- data/Gemfile.lock +7 -5
- data/README.md +123 -31
- data/TESTING.md +20 -78
- data/bin/test_all +18 -8
- data/export_to.gemspec +3 -2
- data/gemfiles/ruby_3.1_rails_7.0.gemfile.lock +11 -9
- data/gemfiles/ruby_3.3_rails_8.0.gemfile +10 -0
- data/gemfiles/ruby_3.3_rails_8.0.gemfile.lock +171 -0
- data/gemfiles/ruby_3.3_rails_8.1.gemfile +10 -0
- data/gemfiles/ruby_3.3_rails_8.1.gemfile.lock +171 -0
- data/gemfiles/ruby_3.4_rails_8.0.gemfile +10 -0
- data/gemfiles/ruby_3.4_rails_8.0.gemfile.lock +238 -0
- data/gemfiles/ruby_3.4_rails_8.1.gemfile +10 -0
- data/gemfiles/ruby_3.4_rails_8.1.gemfile.lock +238 -0
- data/lib/export_to/base.rb +22 -4
- data/lib/export_to/version.rb +1 -1
- metadata +30 -16
- data/gemfiles/ruby_2.7_rails_6.1.gemfile.lock +0 -97
- data/gemfiles/ruby_3.0_rails_6.1.gemfile.lock +0 -97
- data/gemfiles/ruby_3.0_rails_7.0.gemfile.lock +0 -110
- data/gemfiles/ruby_3.1_rails_6.1.gemfile.lock +0 -98
- data/gemfiles/ruby_3.1_rails_7.1.gemfile.lock +0 -147
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48ade8d2d2cc83728bcbbebf003187ac72d515f42e65645e67a91ba4021936ce
|
|
4
|
+
data.tar.gz: ea3754d3619c1d7fcf73c26c7eb2205f6de0caf43e9fd9731b155a4feda6f595
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a63b654da5d5bcf77bb87853e3c0f8c3cd7b0d985a40204e6ef259ad12da528e96f8077523464a5d7ba202a450b08c8f483431d0ffe022901e0421399de1e72
|
|
7
|
+
data.tar.gz: f311a3013ffac1fd95eca922865e9df4c427db2a9dd7c0dca8f8c3bf2188b2716aca16dc869d2d9909a5a2d6afaa237edfd429912aef1accbd62b6329e93f586
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -31,6 +31,14 @@ jobs:
|
|
|
31
31
|
rails: '7.1'
|
|
32
32
|
- ruby: '3.3'
|
|
33
33
|
rails: '7.1'
|
|
34
|
+
- ruby: '3.3'
|
|
35
|
+
rails: '8.0'
|
|
36
|
+
- ruby: '3.3'
|
|
37
|
+
rails: '8.1'
|
|
38
|
+
- ruby: '3.4'
|
|
39
|
+
rails: '8.0'
|
|
40
|
+
- ruby: '3.4'
|
|
41
|
+
rails: '8.1'
|
|
34
42
|
|
|
35
43
|
env:
|
|
36
44
|
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/ruby_${{ matrix.ruby }}_rails_${{ matrix.rails }}.gemfile
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
name: Publish gem
|
|
2
|
+
|
|
3
|
+
# master 上 gemspec 版本號尚未發佈到 rubygems.org 時,自動以 Trusted Publishing (OIDC) 發佈。
|
|
4
|
+
# 版本已存在則跳過;不使用任何儲存的 RubyGems API key。
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- master
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: publish-gem-${{ github.repository }}
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
env:
|
|
19
|
+
# 與 ci.yml 矩陣中最新的 Ruby 3.4 / Rails 8.1 組合一致
|
|
20
|
+
RUBY_VERSION: '3.4.11'
|
|
21
|
+
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/ruby_3.4_rails_8.1.gemfile
|
|
22
|
+
GEM_NAME: export_to
|
|
23
|
+
GEMSPEC: export_to.gemspec
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
check:
|
|
27
|
+
name: Check version on rubygems.org
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
outputs:
|
|
30
|
+
should_publish: ${{ steps.check.outputs.should_publish }}
|
|
31
|
+
gem_name: ${{ steps.spec.outputs.name }}
|
|
32
|
+
gem_version: ${{ steps.spec.outputs.version }}
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
35
|
+
with:
|
|
36
|
+
persist-credentials: false
|
|
37
|
+
|
|
38
|
+
- uses: ruby/setup-ruby@b1da4809b005e0178fe08a1d57317bffc00b7b4c # v1.332.0
|
|
39
|
+
with:
|
|
40
|
+
ruby-version: ${{ env.RUBY_VERSION }}
|
|
41
|
+
|
|
42
|
+
- name: Read gem name and version from gemspec
|
|
43
|
+
id: spec
|
|
44
|
+
run: |
|
|
45
|
+
ruby -e 'spec = Gem::Specification.load(ENV.fetch("GEMSPEC")) or abort("cannot load #{ENV["GEMSPEC"]}"); abort("gemspec name #{spec.name} != #{ENV["GEM_NAME"]}") unless spec.name == ENV["GEM_NAME"]; puts "name=#{spec.name}"; puts "version=#{spec.version}"' >> "$GITHUB_OUTPUT"
|
|
46
|
+
cat "$GITHUB_OUTPUT"
|
|
47
|
+
|
|
48
|
+
- name: Check whether this version already exists on rubygems.org
|
|
49
|
+
id: check
|
|
50
|
+
env:
|
|
51
|
+
GEM_NAME: ${{ steps.spec.outputs.name }}
|
|
52
|
+
GEM_VERSION: ${{ steps.spec.outputs.version }}
|
|
53
|
+
run: |
|
|
54
|
+
set -euo pipefail
|
|
55
|
+
status=$(curl -sS -o versions.json -w '%{http_code}' "https://rubygems.org/api/v1/versions/${GEM_NAME}.json")
|
|
56
|
+
if [ "$status" = "404" ]; then
|
|
57
|
+
echo "Gem ${GEM_NAME} not found on rubygems.org; will publish."
|
|
58
|
+
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
if [ "$status" != "200" ]; then
|
|
62
|
+
echo "::error::rubygems.org API returned HTTP ${status}"
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
if jq -e --arg v "$GEM_VERSION" 'any(.[]; .number == $v)' versions.json > /dev/null; then
|
|
66
|
+
echo "${GEM_NAME} ${GEM_VERSION} already on rubygems.org; skip publish."
|
|
67
|
+
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
68
|
+
else
|
|
69
|
+
echo "${GEM_NAME} ${GEM_VERSION} not on rubygems.org yet; will publish."
|
|
70
|
+
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
test:
|
|
74
|
+
name: Test (Ruby 3.4 / Rails 8.1)
|
|
75
|
+
needs: check
|
|
76
|
+
if: needs.check.outputs.should_publish == 'true'
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
80
|
+
with:
|
|
81
|
+
persist-credentials: false
|
|
82
|
+
|
|
83
|
+
- uses: ruby/setup-ruby@b1da4809b005e0178fe08a1d57317bffc00b7b4c # v1.332.0
|
|
84
|
+
with:
|
|
85
|
+
ruby-version: ${{ env.RUBY_VERSION }}
|
|
86
|
+
bundler-cache: true
|
|
87
|
+
|
|
88
|
+
- run: bundle exec rake test
|
|
89
|
+
|
|
90
|
+
publish:
|
|
91
|
+
name: Publish ${{ needs.check.outputs.gem_name }} ${{ needs.check.outputs.gem_version }}
|
|
92
|
+
needs: [check, test]
|
|
93
|
+
if: needs.check.outputs.should_publish == 'true' && github.ref == 'refs/heads/master'
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
environment: gem-publish
|
|
96
|
+
permissions:
|
|
97
|
+
id-token: write # Trusted Publishing (OIDC)
|
|
98
|
+
contents: write # rake release 推送 vX.Y.Z tag
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
101
|
+
with:
|
|
102
|
+
persist-credentials: false
|
|
103
|
+
|
|
104
|
+
- uses: ruby/setup-ruby@b1da4809b005e0178fe08a1d57317bffc00b7b4c # v1.332.0
|
|
105
|
+
with:
|
|
106
|
+
ruby-version: ${{ env.RUBY_VERSION }}
|
|
107
|
+
bundler-cache: true
|
|
108
|
+
|
|
109
|
+
# setup-ruby 的 bundler-cache 會把 BUNDLE_PATH 等設定寫進 .bundle/config,
|
|
110
|
+
# 若該檔被 git 追蹤,rake release 的 guard_clean 會判定工作目錄不乾淨。
|
|
111
|
+
# 目前此 repo 沒有追蹤任何 .bundle/config;這一步對「有追蹤才標記」做容錯處理,
|
|
112
|
+
# 日後若加入 gemfiles/.bundle/config 或 .bundle/config 也不會讓發佈失敗。
|
|
113
|
+
- name: Ignore CI-only bundler config change
|
|
114
|
+
run: |
|
|
115
|
+
git status --porcelain
|
|
116
|
+
git ls-files -z '*.bundle/config' | xargs -0 -r git update-index --assume-unchanged
|
|
117
|
+
git status --porcelain
|
|
118
|
+
|
|
119
|
+
# 透過 rubygems/configure-rubygems-credentials 取得短效 OIDC token,
|
|
120
|
+
# 再執行 bundle exec rake release(build、建立並推送 vX.Y.Z tag、push gem)
|
|
121
|
+
- uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# 更新紀錄(Changelog)
|
|
2
|
+
|
|
3
|
+
本檔記錄此專案所有值得注意的變更。
|
|
4
|
+
|
|
5
|
+
格式參考 [Keep a Changelog](https://keepachangelog.com/zh-TW/1.1.0/),版本號遵循 [Semantic Versioning](https://semver.org/lang/zh-TW/)。
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- 新增 `CHANGELOG.md`。
|
|
12
|
+
- 完整的 `README.md`:概述、環境需求、安裝、DSL 用法、開發與發版流程。
|
|
13
|
+
- CLAUDE.md 規則:每個 PR 都必須同步更新 `README.md` 與 `CHANGELOG.md`;`README.md` 與 `CHANGELOG.md` 一律用繁體中文撰寫。
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `README.md`、`CHANGELOG.md` 改以繁體中文(zh-TW)撰寫。
|
|
18
|
+
|
|
19
|
+
## [1.3.0] - 2026-10-27
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `ExportTo::Base#each` 每次呼叫都會 raise `NoMethodError (undefined method 'rows!')`。現在會回傳所有列組成的 Array(第一列是標題列,內容與 `#to_csv` 相同);帶 block 時會逐列 yield 並回傳 exporter 本身,因此 Enumerable 方法(`map`、`to_a` 等)也能使用。`#each!` 沒有變動。
|
|
24
|
+
- `TESTING.md` 與 `bin/test_all` 已過時(只涵蓋 Ruby 2.7–3.1)。現在它們的說明與執行內容都與 `.github/workflows/ci.yml` 的矩陣一致。
|
|
25
|
+
|
|
26
|
+
### Deprecated
|
|
27
|
+
|
|
28
|
+
- `ExportTo::Base#to_xls`:自 1.0.0 起它回傳的就是 **XLSX** 資料(與 `#to_xlsx` 相同),而不是舊版 `.xls` 檔。為了不讓既有呼叫端壞掉,回傳值維持不變,但現在會印出一次性的 `[DEPRECATION]` 警告(輸出到 `$stderr`,每個 process 一次)。請改用 `#to_xlsx`;若要真正的 `.xls`,請用 `ExportTo::Exporter::Xls.new(export).to_xls`。未來的 major 版本可能會讓 `#to_xls` 回傳真正的 `.xls` 檔,或將它移除。
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- 測試:`#each`、`#to_xls` 的相容性(仍回傳 XLSX)與其棄用警告、`#to_xlsx` / `ExportTo::Exporter::Xls` 的輸出格式。
|
|
33
|
+
|
|
34
|
+
## [1.2.1] - 2026-10-27
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- 支援 Rails 8.0 / 8.1 與 Ruby 3.4(`activesupport` 現在放寬到 `< 8.2`;`csv` gem 改為明確相依;移除 `required_ruby_version` 的上限;新增 `ruby_3.3_rails_8.*` / `ruby_3.4_rails_8.*` gemfile 與對應的 CI job)。
|
|
39
|
+
- GitHub Actions `publish.yml`:新版本進到 `master` 時,透過 Trusted Publishing 自動發佈到 RubyGems。
|
|
40
|
+
- CLAUDE.md 加入發版規則。
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- 移除每個 gemfile 各自的舊 lockfile,這些 lockfile 會讓 CI 在較舊的 Ruby 上失敗。
|
|
45
|
+
|
|
46
|
+
## [1.2.0] - 2026-01-08
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- 新增 Ruby 3.2 與 3.3 的測試 gemfile(`ruby_3.2_rails_7.0`、`ruby_3.2_rails_7.1`、`ruby_3.3_rails_7.1`)與 CI job。
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- gemspec 的 `required_ruby_version` 上限由 `< 3.2.0` 提高到 `< 3.4.0`。
|
|
55
|
+
|
|
56
|
+
## [1.1.2] - 2026-01-07
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- 更新各 gemfile 的 lockfile。
|
|
61
|
+
|
|
62
|
+
## [1.1.1] - 2026-01-07
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- 多版本 Ruby / Rails 測試環境:每個版本各自的 gemfile、GitHub Actions 矩陣、`bin/test_all`、`bin/check_dependencies` 與 `TESTING.md`。
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- 測試不再依賴寫死的 macOS 路徑。
|
|
71
|
+
|
|
72
|
+
## [1.1.0] - 2023-02-02
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
|
|
76
|
+
- 修正並微調 XLSX exporter(`ExportTo::Exporter::Xlsx`)。
|
|
77
|
+
|
|
78
|
+
## [1.0.0] - 2023-02-02
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- 內部改寫:匯出邏輯移到 `ExportTo::Exporter::Csv` / `Xls` / `Xlsx`;`ExportTo::Base` 提供 `to_csv`、`to_xlsx` 與 `to_xls`。
|
|
83
|
+
- `activesupport` 限制為 `>= 6, < 7`;開發相依套件放寬為 `bundler >= 2.0`、`rake >= 13`。
|
|
84
|
+
|
|
85
|
+
## [0.1.2] - 2019-06-19
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
|
|
89
|
+
- CSV 輸出現在會強制幫每個欄位加上引號。
|
|
90
|
+
|
|
91
|
+
## [0.1.1] - 2018-12-05
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
|
|
95
|
+
- 修正匯出時出現 "File exists" 的 bug。
|
|
96
|
+
|
|
97
|
+
## [0.1.0] - 2018-10-21
|
|
98
|
+
|
|
99
|
+
### Added
|
|
100
|
+
|
|
101
|
+
- 首次發佈:`ExportTo::Base` DSL(`set`、`joins`、`presenter`、`each_with`)、帶有列計數器的 presenter、空的 join relation 處理,以及 `to_xlsx`。
|
|
102
|
+
|
|
103
|
+
[Unreleased]: https://github.com/superlanding/export_to/compare/v1.3.0...HEAD
|
|
104
|
+
[1.3.0]: https://github.com/superlanding/export_to/compare/v1.2.1...v1.3.0
|
|
105
|
+
[1.2.1]: https://github.com/superlanding/export_to/compare/v1.2.0...v1.2.1
|
|
106
|
+
[1.2.0]: https://github.com/superlanding/export_to/compare/v1.1.2...v1.2.0
|
|
107
|
+
[1.1.2]: https://github.com/superlanding/export_to/compare/v1.1.1...v1.1.2
|
|
108
|
+
[1.1.1]: https://github.com/superlanding/export_to/releases/tag/v1.1.1
|
|
109
|
+
[1.1.0]: https://github.com/superlanding/export_to/commit/48957da
|
|
110
|
+
[1.0.0]: https://github.com/superlanding/export_to/commit/8d7f937
|
|
111
|
+
[0.1.2]: https://github.com/superlanding/export_to/commit/d276482
|
|
112
|
+
[0.1.1]: https://github.com/superlanding/export_to/commit/12dd5c1
|
|
113
|
+
[0.1.0]: https://github.com/superlanding/export_to/commit/d605bf9
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## 發版規則(Ruby gem)
|
|
4
|
+
|
|
5
|
+
- 這個 gem(`export_to`,gemspec:`export_to.gemspec`)在 master 收到「新版本號」時,由 GitHub Actions(`.github/workflows/publish.yml`)自動發佈到 rubygems.org(Trusted Publishing,不使用 API key)。
|
|
6
|
+
- **每一個 PR 都必須跳版號**(改 `lib/export_to/version.rb`),除非這個 PR 只改文件、CI 或 CLAUDE.md 且明確不需要發版。沒跳版號的 PR 合進 master 後不會發佈。
|
|
7
|
+
- 跳版規則(Semantic Versioning):修 bug 跳 patch(x.y.Z);新增相容功能跳 minor(x.Y.0);不相容變更跳 major(X.0.0)。
|
|
8
|
+
- 版號只改在 PR 裡。AI 帳號只能開 branch 與 PR,不得合併到 master;只有真人帳號可以合併。合併就等於批准發佈。
|
|
9
|
+
- 不得為了讓測試通過而改變程式原本的行為。
|
|
10
|
+
|
|
11
|
+
## 文件與更新紀錄(README / CHANGELOG)
|
|
12
|
+
|
|
13
|
+
- **每一個 PR 都必須同步更新文件**:
|
|
14
|
+
- 只要行為、用法、設定、相依套件或支援的 Ruby / Rails 版本有變,就要更新 `README.md`。
|
|
15
|
+
- 一律要在 `CHANGELOG.md` 新增一筆條目([Keep a Changelog](https://keepachangelog.com/zh-TW/1.1.0/) 格式,分類用 Added / Changed / Deprecated / Removed / Fixed / Security)。
|
|
16
|
+
- **語言規則**:`README.md` 與 `CHANGELOG.md` 一律用繁體中文(zh-TW,台灣用語)撰寫;程式碼、識別字(class / 方法 / 變數名稱)、指令、檔名與路徑維持英文,不要翻譯。
|
|
17
|
+
- `CHANGELOG.md` 的寫法:
|
|
18
|
+
- 沒有跳版號的 PR:寫在最上方的 `## [Unreleased]` 底下。
|
|
19
|
+
- 有跳版號的 PR:把條目寫在新的版本章節 `## [x.y.z] - YYYY-MM-DD`(日期為合併日),並同步更新檔尾的版本比較連結。
|
|
20
|
+
- 版本號必須與 rubygems.org 上實際發佈的版本一致,不得寫還沒發佈的版本。
|
|
21
|
+
- 只改文件、CI 或 CLAUDE.md 的 PR 不需要跳版號,但仍要在 `CHANGELOG.md` 的 `Unreleased` 補一筆。
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
export_to (1.
|
|
5
|
-
activesupport (>= 6.0, < 8)
|
|
4
|
+
export_to (1.3.0)
|
|
5
|
+
activesupport (>= 6.0, < 8.2)
|
|
6
|
+
csv
|
|
6
7
|
fast_excel (~> 0.4)
|
|
7
8
|
iconv (~> 1.0)
|
|
8
9
|
spreadsheet (~> 1.3)
|
|
@@ -29,14 +30,15 @@ GEM
|
|
|
29
30
|
minitest (>= 5.1)
|
|
30
31
|
tzinfo (~> 2.0)
|
|
31
32
|
zeitwerk (~> 2.3)
|
|
32
|
-
bigdecimal (4.
|
|
33
|
+
bigdecimal (4.1.3)
|
|
33
34
|
builder (3.2.4)
|
|
34
35
|
concurrent-ruby (1.2.0)
|
|
35
36
|
crass (1.0.6)
|
|
37
|
+
csv (3.3.6)
|
|
36
38
|
erubi (1.12.0)
|
|
37
39
|
fast_excel (0.5.0)
|
|
38
40
|
ffi (> 1.9, < 2)
|
|
39
|
-
ffi (1.17.
|
|
41
|
+
ffi (1.17.4)
|
|
40
42
|
i18n (1.12.0)
|
|
41
43
|
concurrent-ruby (~> 1.0)
|
|
42
44
|
iconv (1.1.1)
|
|
@@ -68,7 +70,7 @@ GEM
|
|
|
68
70
|
rake (13.0.6)
|
|
69
71
|
ruby-ole (1.2.13.1)
|
|
70
72
|
shoulda-context (1.2.2)
|
|
71
|
-
spreadsheet (1.3.
|
|
73
|
+
spreadsheet (1.3.5)
|
|
72
74
|
bigdecimal
|
|
73
75
|
logger
|
|
74
76
|
ruby-ole
|
data/README.md
CHANGED
|
@@ -1,64 +1,156 @@
|
|
|
1
1
|
# ExportTo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://rubygems.org/gems/export_to)
|
|
4
|
+
[](https://github.com/superlanding/export_to/actions/workflows/ci.yml)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
用一套簡單的宣告式 DSL,把一批資料(ActiveRecord relation、Struct 陣列,或任何有 `each` 的物件)匯出成 **CSV** 或 **XLSX**(透過較底層的 exporter,也能匯出舊版 **XLS**)。
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
你只要在 `ExportTo::Base` 的子類別裡宣告一次欄位(標題、來源屬性、寬度、格式、值的轉換),需要計算欄位時可以加上 presenter,也可以加上 `joins` relation 把一筆資料展開成多列,最後呼叫 `to_csv` / `to_xlsx` 即可。
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## 環境需求
|
|
11
|
+
|
|
12
|
+
| Ruby | CI 測試的 Rails / ActiveSupport 版本 |
|
|
13
|
+
|------|------------------------------------|
|
|
14
|
+
| 2.7 | 6.1 |
|
|
15
|
+
| 3.0 | 6.1, 7.0 |
|
|
16
|
+
| 3.1 | 6.1, 7.0, 7.1 |
|
|
17
|
+
| 3.2 | 7.0, 7.1 |
|
|
18
|
+
| 3.3 | 7.1, 8.0, 8.1 |
|
|
19
|
+
| 3.4 | 8.0, 8.1 |
|
|
20
|
+
|
|
21
|
+
gemspec 要求 Ruby `>= 2.7.0` 與 `activesupport >= 6.0, < 8.2`。實際支援範圍以 [`.github/workflows/ci.yml`](.github/workflows/ci.yml) 為準。
|
|
22
|
+
|
|
23
|
+
Runtime 相依套件:`fast_excel`(XLSX)、`spreadsheet`(XLS)、`csv`、`iconv`、`activesupport`。
|
|
24
|
+
|
|
25
|
+
## 安裝
|
|
26
|
+
|
|
27
|
+
在應用程式的 Gemfile 加入這一行:
|
|
10
28
|
|
|
11
29
|
```ruby
|
|
12
|
-
gem
|
|
30
|
+
gem "export_to"
|
|
13
31
|
```
|
|
14
32
|
|
|
15
|
-
|
|
33
|
+
然後執行:
|
|
16
34
|
|
|
17
|
-
|
|
35
|
+
```bash
|
|
36
|
+
bundle install
|
|
37
|
+
```
|
|
18
38
|
|
|
19
|
-
|
|
39
|
+
或自行執行 `gem install export_to` 安裝。
|
|
20
40
|
|
|
21
|
-
|
|
41
|
+
## 使用方式
|
|
22
42
|
|
|
23
|
-
|
|
43
|
+
### 1. 定義 exporter
|
|
24
44
|
|
|
25
|
-
|
|
45
|
+
```ruby
|
|
46
|
+
class UserExport < ExportTo::Base
|
|
47
|
+
# set <標題>, <key>, <欄位選項>, &可選的轉換 block
|
|
48
|
+
set "Name", :full_name, width: 30
|
|
49
|
+
set "Mobile", :mobile, width: 20, format: { bold: true }
|
|
50
|
+
set "Amount", :amount, width: 12 do |value|
|
|
51
|
+
value + 100 # block 會收到儲存格的值,並回傳最終要輸出的值
|
|
52
|
+
end
|
|
53
|
+
set "Source", "web" # key 為 String 時,每一列都會寫入這個固定值
|
|
54
|
+
|
|
55
|
+
excel worksheet: "Users", height: 24 # 可選的 XLSX 設定(工作表名稱、列高)
|
|
56
|
+
|
|
57
|
+
presenter do # 可選:計算欄位
|
|
58
|
+
def full_name
|
|
59
|
+
"#{first_name} #{last_name}" # 找不到的方法會委派給該筆 record
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def amount
|
|
63
|
+
relation? ? relation.amount : 0
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
joins :wallets # 可選:record.wallets 裡每一筆各佔一列
|
|
68
|
+
end
|
|
69
|
+
```
|
|
26
70
|
|
|
27
|
-
|
|
71
|
+
- **`set(title, key, options = {}, &block)`**:每次呼叫新增一個欄位,依呼叫順序排列。
|
|
72
|
+
- `key` 為 **Symbol** 時,會從 presenter 讀取(presenter 會委派給 record);`nil` 會變成 `""`。
|
|
73
|
+
- `key` 為 **String** 時,直接當作固定內容使用。
|
|
74
|
+
- XLSX 專用的 `options`:`width:`(欄寬)與 `format:`(`FastExcel` 的 format hash,例如 `{ bold: true }`)。
|
|
75
|
+
- 可選的 block 用來後處理儲存格的值。
|
|
76
|
+
- **`excel(worksheet: "Name", height: 20)`**:工作表名稱(預設 `"Default"`)與資料列的列高(預設 `20`)。標題列為粗體、置中並帶有背景色。
|
|
77
|
+
- **`presenter { ... }`**:定義 `YourExport::Presenter`(是上層 exporter 的 presenter 的子類別)。在裡面可以使用 `model`、`relation`、`relation?`、`x`(record 的索引)與 `y`(`joins` relation 內的索引)。
|
|
78
|
+
- **`joins(:relation_name)`**:當 `record.relation_name` 有資料時,每個元素各自成為一列,且 `relation` 就是該元素;presenter 會先找 `relation` 上的方法,找不到再找 `model`。relation 為空的 record 會產生單獨一列(此時 `relation?` 為 `false`)。
|
|
79
|
+
- **`each_with { |presenter| ... }`**:hook,會在讀取各欄位之前,以每一列的 presenter 呼叫一次。
|
|
80
|
+
- **`self.each_method = :find_in_batches`**:改用 `records.find_in_batches` 而不是 `records.each` 來迭代,適合很大的 ActiveRecord relation。
|
|
28
81
|
|
|
29
|
-
|
|
30
|
-
- Ruby 2.7 with Rails 6.1
|
|
31
|
-
- Ruby 3.0 with Rails 6.1, 7.0
|
|
32
|
-
- Ruby 3.1 with Rails 6.1, 7.0, 7.1
|
|
33
|
-
- Ruby 3.2 with Rails 7.0, 7.1
|
|
34
|
-
- Ruby 3.3 with Rails 7.1
|
|
82
|
+
### 2. 匯出
|
|
35
83
|
|
|
36
|
-
|
|
84
|
+
```ruby
|
|
85
|
+
users = User.includes(:wallets).where(active: true) # 或任何可列舉的物件
|
|
86
|
+
export = UserExport.new(users)
|
|
37
87
|
|
|
38
|
-
|
|
88
|
+
export.to_csv # => String,每個欄位都有加引號("Name","Mobile",...)
|
|
89
|
+
export.to_xlsx # => .xlsx 檔案的二進位 String
|
|
90
|
+
export.each # => 所有列組成的 Array,第一列是標題列:[["Name", "Mobile", ...], ["Eddie Li", ...], ...]
|
|
91
|
+
export.each { |columns| ... } # 逐列 yield(第一列是標題列),並回傳 exporter 本身;Enumerable 方法也能用
|
|
92
|
+
```
|
|
39
93
|
|
|
40
|
-
|
|
94
|
+
兩位使用者的 CSV 輸出範例(第二位有兩個 wallet):
|
|
41
95
|
|
|
42
|
-
|
|
96
|
+
```
|
|
97
|
+
"Name","Mobile","Amount","Source"
|
|
98
|
+
"Eddie Li","+8869","100","web"
|
|
99
|
+
"Vegeta Lu","+8868","1100","web"
|
|
100
|
+
"Vegeta Lu","+8868","2100","web"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
在 Rails controller 裡提供下載:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
def export
|
|
107
|
+
send_data UserExport.new(User.all).to_xlsx,
|
|
108
|
+
filename: "users.xlsx",
|
|
109
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 已知限制
|
|
114
|
+
|
|
115
|
+
以下是目前程式碼的實際行為:
|
|
116
|
+
|
|
117
|
+
- `ExportTo::Base#to_xls` **已棄用,而且名稱有誤導性**:自 1.0.0 起它回傳的是與 `#to_xlsx` 相同的 **XLSX** 資料(不是舊版 `.xls` 檔)。保留這個行為是為了不讓既有呼叫端壞掉;呼叫時會印出一次性的 `[DEPRECATION]` 警告。請改用 `#to_xlsx`。若需要真正的舊版 `.xls` 檔,請用 `ExportTo::Exporter::Xls.new(export).to_xls`(不會套用任何樣式選項)。
|
|
118
|
+
- `width:` / `format:` 與 `excel` 選項只對 XLSX 輸出有效。
|
|
119
|
+
- 子類別會共用上層 class 的欄位定義陣列(`class_attribute`),所以如果某個 exporter 本身也會單獨使用,就不要在它的子類別裡呼叫 `set`。
|
|
120
|
+
|
|
121
|
+
## 開發
|
|
122
|
+
|
|
123
|
+
clone 下來之後,先執行 `bin/setup` 安裝相依套件,接著跑測試:
|
|
43
124
|
|
|
44
125
|
```bash
|
|
45
|
-
|
|
126
|
+
bundle exec rake test # 也可以用 rake t / rake(預設 task)
|
|
127
|
+
bin/console # 互動式 console
|
|
46
128
|
```
|
|
47
129
|
|
|
48
|
-
|
|
130
|
+
每個 Ruby / Rails 組合在 `gemfiles/` 內都有各自的 gemfile(CI 會把 `BUNDLE_GEMFILE` 指到它):
|
|
49
131
|
|
|
50
132
|
```bash
|
|
51
|
-
|
|
133
|
+
BUNDLE_GEMFILE=$PWD/gemfiles/ruby_3.4_rails_8.1.gemfile bundle install
|
|
134
|
+
BUNDLE_GEMFILE=$PWD/gemfiles/ruby_3.4_rails_8.1.gemfile bundle exec rake test
|
|
52
135
|
```
|
|
53
136
|
|
|
54
|
-
|
|
137
|
+
`bin/test_all` 會跑過所有支援的 Ruby 版本(需要 rbenv 或 rvm),`bin/check_dependencies` 會檢查相依套件的相容性;詳見 [TESTING.md](TESTING.md)。
|
|
138
|
+
|
|
139
|
+
## 發版
|
|
140
|
+
|
|
141
|
+
發版透過 RubyGems [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) 自動完成(repo 內不存放任何 API key):
|
|
142
|
+
|
|
143
|
+
1. 在你的 PR 裡修改 `lib/export_to/version.rb` 的 `ExportTo::VERSION`(遵循 [Semantic Versioning](https://semver.org/lang/zh-TW/):修 bug 跳 patch、相容的新功能跳 minor、不相容的變更跳 major)。
|
|
144
|
+
2. 在 `CHANGELOG.md` 新增對應的 `## [x.y.z] - YYYY-MM-DD` 區塊。
|
|
145
|
+
3. 由真人 review 後合併進 `master`(AI 帳號只能開 PR)。
|
|
146
|
+
4. [`.github/workflows/publish.yml`](.github/workflows/publish.yml) 會在 `master` 上執行:如果 gemspec 的版本還沒上 rubygems.org,就執行 `rake release`(打包 gem、打 `vX.Y.Z` tag、發佈)。沒有跳版號的合併不會發佈任何東西。
|
|
55
147
|
|
|
56
|
-
|
|
148
|
+
只改文件 / CI 的 PR 不需要跳版號。請見 [CLAUDE.md](CLAUDE.md):每個 PR 也都必須更新 `README.md`(行為或用法有變時)並在 `CHANGELOG.md` 新增一筆條目。
|
|
57
149
|
|
|
58
|
-
##
|
|
150
|
+
## 貢獻
|
|
59
151
|
|
|
60
|
-
|
|
152
|
+
歡迎到 GitHub 回報問題與提交 pull request:https://github.com/superlanding/export_to。
|
|
61
153
|
|
|
62
|
-
##
|
|
154
|
+
## 授權
|
|
63
155
|
|
|
64
|
-
|
|
156
|
+
本 gem 以 [MIT License](https://opensource.org/licenses/MIT) 條款開源。
|
data/TESTING.md
CHANGED
|
@@ -1,103 +1,45 @@
|
|
|
1
1
|
# Testing Guide
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
- Ruby 2.7 with Rails 6.1
|
|
5
|
-
- Ruby 3.0 with Rails 6.1, 7.0
|
|
6
|
-
- Ruby 3.1 with Rails 6.1, 7.0, 7.1
|
|
3
|
+
Tests use Minitest + shoulda-context (`test/**/*_test.rb`) and run against several Ruby / Rails combinations.
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
The supported matrix is defined in **one place**: [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Each combination has a gemfile `gemfiles/ruby_<ruby>_rails_<rails>.gemfile`. Lists of versions are intentionally not copied here so they cannot go stale; see the [README](README.md#requirements) for the current table.
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
## Run the tests for one combination
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Run the test suite against all supported Ruby versions:
|
|
9
|
+
CI selects the gemfile with `BUNDLE_GEMFILE`; do the same locally (switch to the matching Ruby first, e.g. with rbenv):
|
|
15
10
|
|
|
16
11
|
```bash
|
|
17
|
-
|
|
12
|
+
export BUNDLE_GEMFILE=$PWD/gemfiles/ruby_3.4_rails_8.1.gemfile
|
|
13
|
+
bundle install
|
|
14
|
+
bundle exec rake test # also: rake t / rake
|
|
18
15
|
```
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
- Automatically switch between Ruby versions (requires rbenv or rvm)
|
|
22
|
-
- Install dependencies for each version
|
|
23
|
-
- Run the test suite for each version
|
|
24
|
-
- Display a summary of results
|
|
25
|
-
|
|
26
|
-
#### Test a specific Ruby/Rails version combination
|
|
17
|
+
## Run the whole CI matrix locally
|
|
27
18
|
|
|
28
19
|
```bash
|
|
29
|
-
|
|
30
|
-
export BUNDLE_GEMFILE=gemfiles/ruby_2.7_rails_6.1.gemfile
|
|
31
|
-
bundle install
|
|
32
|
-
bundle exec rake test
|
|
33
|
-
|
|
34
|
-
# Ruby 3.0 with Rails 6.1
|
|
35
|
-
export BUNDLE_GEMFILE=gemfiles/ruby_3.0_rails_6.1.gemfile
|
|
36
|
-
bundle install
|
|
37
|
-
bundle exec rake test
|
|
38
|
-
|
|
39
|
-
# Ruby 3.0 with Rails 7.0
|
|
40
|
-
export BUNDLE_GEMFILE=gemfiles/ruby_3.0_rails_7.0.gemfile
|
|
41
|
-
bundle install
|
|
42
|
-
bundle exec rake test
|
|
43
|
-
|
|
44
|
-
# Ruby 3.1 with Rails 7.1
|
|
45
|
-
export BUNDLE_GEMFILE=gemfiles/ruby_3.1_rails_7.1.gemfile
|
|
46
|
-
bundle install
|
|
47
|
-
bundle exec rake test
|
|
20
|
+
bin/test_all
|
|
48
21
|
```
|
|
49
22
|
|
|
50
|
-
|
|
23
|
+
`bin/test_all` needs rbenv or rvm. For every entry of its `TEST_MATRIX` (kept equal to the CI matrix) it switches to the newest installed patch version of that Ruby, runs `bundle install` with the matching gemfile, runs `rake test` and prints a summary. A Ruby that is not installed is reported as a failure. When you change `ci.yml`, update `TEST_MATRIX` in `bin/test_all` as well.
|
|
51
24
|
|
|
52
|
-
Check
|
|
25
|
+
## Check dependencies
|
|
53
26
|
|
|
54
27
|
```bash
|
|
55
28
|
bin/check_dependencies
|
|
56
29
|
```
|
|
57
30
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
## CI/CD
|
|
61
|
-
|
|
62
|
-
The gem uses GitHub Actions for continuous integration. Every push and pull request will automatically test against all supported combinations:
|
|
63
|
-
- Ruby 2.7 / Rails 6.1
|
|
64
|
-
- Ruby 3.0 / Rails 6.1, 7.0
|
|
65
|
-
- Ruby 3.1 / Rails 6.1, 7.0, 7.1
|
|
66
|
-
|
|
67
|
-
See `.github/workflows/ci.yml` for the full configuration.
|
|
68
|
-
|
|
69
|
-
## Gemfiles Structure
|
|
70
|
-
|
|
71
|
-
The `gemfiles/` directory contains version-specific Gemfiles for each Ruby/Rails combination:
|
|
72
|
-
- `ruby_2.7_rails_6.1.gemfile` - Ruby 2.7 with Rails 6.1
|
|
73
|
-
- `ruby_3.0_rails_6.1.gemfile` - Ruby 3.0 with Rails 6.1
|
|
74
|
-
- `ruby_3.0_rails_7.0.gemfile` - Ruby 3.0 with Rails 7.0
|
|
75
|
-
- `ruby_3.1_rails_6.1.gemfile` - Ruby 3.1 with Rails 6.1
|
|
76
|
-
- `ruby_3.1_rails_7.0.gemfile` - Ruby 3.1 with Rails 7.0
|
|
77
|
-
- `ruby_3.1_rails_7.1.gemfile` - Ruby 3.1 with Rails 7.1
|
|
31
|
+
Verifies that the dependencies can be installed for the supported versions and reports outdated gems.
|
|
78
32
|
|
|
79
|
-
|
|
33
|
+
## Adding a Ruby / Rails combination
|
|
80
34
|
|
|
81
|
-
|
|
35
|
+
1. Add `gemfiles/ruby_<ruby>_rails_<rails>.gemfile` (copy a neighbouring one and change the `activesupport` / `railties` versions).
|
|
36
|
+
2. Add the combination to the matrix in `.github/workflows/ci.yml` and to `TEST_MATRIX` in `bin/test_all`.
|
|
37
|
+
3. Update the table in the README.
|
|
82
38
|
|
|
83
|
-
|
|
39
|
+
## Lockfiles
|
|
84
40
|
|
|
85
|
-
|
|
86
|
-
- **rbenv** with Ruby 2.7, 3.0, and 3.1 installed
|
|
87
|
-
- **rvm** with Ruby 2.7, 3.0, and 3.1 installed
|
|
41
|
+
Some gemfiles have a committed `*.gemfile.lock`, and `Gemfile.lock` is committed too. They contain the gem's own version (`export_to (x.y.z)`), so **keep them consistent: bump the version in those files whenever `lib/export_to/version.rb` changes**.
|
|
88
42
|
|
|
89
|
-
|
|
43
|
+
## CI
|
|
90
44
|
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
rbenv install 2.7.8
|
|
94
|
-
rbenv install 3.0.6
|
|
95
|
-
rbenv install 3.1.4
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
#### Using rvm:
|
|
99
|
-
```bash
|
|
100
|
-
rvm install 2.7.8
|
|
101
|
-
rvm install 3.0.6
|
|
102
|
-
rvm install 3.1.4
|
|
103
|
-
```
|
|
45
|
+
GitHub Actions runs every matrix entry on every push to `master` and on every pull request that targets `master`.
|