From 6bced4d5c1d3608fa4b34b6f9dc1cf2ea5c328b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=99=E6=B0=B4=E5=BF=8D?= <335298362+sensui624@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:01:44 +0800 Subject: [PATCH] docs: add CHANGELOG, rewrite README, require README/CHANGELOG updates in CLAUDE.md Docs only, no version bump. --- CHANGELOG.md | 94 ++++++++++++++++++++++++++++++++++ CLAUDE.md | 11 ++++ README.md | 139 ++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 220 insertions(+), 24 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..43a4594 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,94 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- `CHANGELOG.md`. +- Full `README.md`: overview, requirements, installation, DSL usage, development and release flow. +- CLAUDE.md rule: every PR must also update `README.md` and `CHANGELOG.md`. + +## [1.2.1] - 2026-10-27 + +### Added + +- Support for Rails 8.0 / 8.1 and Ruby 3.4 (`activesupport` is now allowed up to `< 8.2`; the `csv` gem is an explicit dependency; the `required_ruby_version` upper bound is removed; new `ruby_3.3_rails_8.*` / `ruby_3.4_rails_8.*` gemfiles and CI jobs). +- GitHub Actions `publish.yml`: a new version on `master` is published to RubyGems through Trusted Publishing. +- CLAUDE.md release rules. + +### Changed + +- Removed stale per-gemfile lockfiles that broke older Rubies in CI. + +## [1.2.0] - 2026-01-08 + +### Added + +- Ruby 3.2 and 3.3 test gemfiles (`ruby_3.2_rails_7.0`, `ruby_3.2_rails_7.1`, `ruby_3.3_rails_7.1`) and CI jobs. + +### Changed + +- Gemspec `required_ruby_version` upper bound raised from `< 3.2.0` to `< 3.4.0`. + +## [1.1.2] - 2026-01-07 + +### Changed + +- Updated gemfile lockfiles. + +## [1.1.1] - 2026-01-07 + +### Added + +- Multi-version Ruby / Rails test setup: per-version gemfiles, GitHub Actions matrix, `bin/test_all`, `bin/check_dependencies` and `TESTING.md`. + +### Fixed + +- A test no longer depends on a hard-coded macOS path. + +## [1.1.0] - 2023-02-02 + +### Changed + +- XLSX exporter fixes and tweaks (`ExportTo::Exporter::Xlsx`). + +## [1.0.0] - 2023-02-02 + +### Changed + +- Rewrite of the internals: export logic moved into `ExportTo::Exporter::Csv` / `Xls` / `Xlsx`; `ExportTo::Base` exposes `to_csv`, `to_xlsx` and `to_xls`. +- `activesupport` is now constrained to `>= 6, < 7`; development dependencies relaxed to `bundler >= 2.0`, `rake >= 13`. + +## [0.1.2] - 2019-06-19 + +### Changed + +- CSV output now force-quotes every field. + +## [0.1.1] - 2018-12-05 + +### Fixed + +- "File exists" bug when exporting. + +## [0.1.0] - 2018-10-21 + +### Added + +- Initial release: `ExportTo::Base` DSL (`set`, `joins`, `presenter`, `each_with`), presenters with row counters, empty join relations, `to_xlsx`. + +[Unreleased]: https://github.com/superlanding/export_to/compare/v1.2.1...HEAD +[1.2.1]: https://github.com/superlanding/export_to/compare/v1.2.0...v1.2.1 +[1.2.0]: https://github.com/superlanding/export_to/compare/v1.1.2...v1.2.0 +[1.1.2]: https://github.com/superlanding/export_to/compare/v1.1.1...v1.1.2 +[1.1.1]: https://github.com/superlanding/export_to/releases/tag/v1.1.1 +[1.1.0]: https://github.com/superlanding/export_to/commit/48957da +[1.0.0]: https://github.com/superlanding/export_to/commit/8d7f937 +[0.1.2]: https://github.com/superlanding/export_to/commit/d276482 +[0.1.1]: https://github.com/superlanding/export_to/commit/12dd5c1 +[0.1.0]: https://github.com/superlanding/export_to/commit/d605bf9 diff --git a/CLAUDE.md b/CLAUDE.md index 4d060f6..7004c75 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,3 +7,14 @@ - 跳版規則(Semantic Versioning):修 bug 跳 patch(x.y.Z);新增相容功能跳 minor(x.Y.0);不相容變更跳 major(X.0.0)。 - 版號只改在 PR 裡。AI 帳號只能開 branch 與 PR,不得合併到 master;只有真人帳號可以合併。合併就等於批准發佈。 - 不得為了讓測試通過而改變程式原本的行為。 + +## 文件與更新紀錄(README / CHANGELOG) + +- **每一個 PR 都必須同步更新文件**: + - 只要行為、用法、設定、相依套件或支援的 Ruby / Rails 版本有變,就要更新 `README.md`。 + - 一律要在 `CHANGELOG.md` 新增一筆條目([Keep a Changelog](https://keepachangelog.com/zh-TW/1.1.0/) 格式,分類用 Added / Changed / Deprecated / Removed / Fixed / Security)。 +- `CHANGELOG.md` 的寫法: + - 沒有跳版號的 PR:寫在最上方的 `## [Unreleased]` 底下。 + - 有跳版號的 PR:把條目寫在新的版本章節 `## [x.y.z] - YYYY-MM-DD`(日期為合併日),並同步更新檔尾的版本比較連結。 + - 版本號必須與 rubygems.org 上實際發佈的版本一致,不得寫還沒發佈的版本。 +- 只改文件、CI 或 CLAUDE.md 的 PR 不需要跳版號,但仍要在 `CHANGELOG.md` 的 `Unreleased` 補一筆。 diff --git a/README.md b/README.md index 9f3ca1d..b569484 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,150 @@ # ExportTo -Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/export_to`. To experiment with that code, run `bin/console` for an interactive prompt. +[![Gem Version](https://badge.fury.io/rb/export_to.svg)](https://rubygems.org/gems/export_to) +[![CI](https://github.com/superlanding/export_to/actions/workflows/ci.yml/badge.svg)](https://github.com/superlanding/export_to/actions/workflows/ci.yml) -TODO: Delete this and the text above, and describe your gem +Export a collection of records (ActiveRecord relations, arrays of Structs, anything that responds to `each`) to **CSV**, **XLSX** or **XLS** with a small declarative DSL. + +You describe the columns once (title, source attribute, width, format, value transform) in an `ExportTo::Base` subclass, optionally add a presenter for computed columns and a `joins` relation that expands one record into several rows, then call `to_csv` / `to_xlsx` / `to_xls`. + +## Requirements + +| Ruby | Rails / ActiveSupport tested in CI | +|------|------------------------------------| +| 2.7 | 6.1 | +| 3.0 | 6.1, 7.0 | +| 3.1 | 6.1, 7.0, 7.1 | +| 3.2 | 7.0, 7.1 | +| 3.3 | 7.1, 8.0, 8.1 | +| 3.4 | 8.0, 8.1 | + +The gemspec requires Ruby `>= 2.7.0` and `activesupport >= 6.0, < 8.2`. The source of truth is [`.github/workflows/ci.yml`](.github/workflows/ci.yml). + +Runtime dependencies: `fast_excel` (XLSX), `spreadsheet` (XLS), `csv`, `iconv`, `activesupport`. ## Installation Add this line to your application's Gemfile: ```ruby -gem 'export_to' +gem "export_to" ``` And then execute: - $ bundle - -Or install it yourself as: +```bash +bundle install +``` - $ gem install export_to +Or install it yourself with `gem install export_to`. ## Usage -TODO: Write usage instructions here +### 1. Define an exporter -## Ruby & Rails Version Support +```ruby +class UserExport < ExportTo::Base + # set
, , , &optional_transform + set "Name", :full_name, width: 30 + set "Mobile", :mobile, width: 20, format: { bold: true } + set "Amount", :amount, width: 12 do |value| + value + 100 # the block receives the cell value and returns the final one + end + set "Source", "web" # a String key is written as a fixed value in every row + + excel worksheet: "Users", height: 24 # optional XLSX settings (worksheet name, row height) + + presenter do # optional: computed columns + def full_name + "#{first_name} #{last_name}" # unknown methods are delegated to the record + end + + def amount + relation? ? relation.amount : 0 + end + end + + joins :wallets # optional: one row per record in record.wallets +end +``` -This gem supports: -- Ruby 2.7 with Rails 6.1 -- Ruby 3.0 with Rails 6.1, 7.0 -- Ruby 3.1 with Rails 6.1, 7.0, 7.1 -- Ruby 3.2 with Rails 7.0, 7.1 -- Ruby 3.3 with Rails 7.1 +- **`set(title, key, options = {}, &block)`**: one column per call, in order. + - `key` as a **Symbol** is read from the presenter (which delegates to the record); `nil` becomes `""`. + - `key` as a **String** is used literally. + - XLSX `options`: `width:` (column width) and `format:` (a `FastExcel` format hash, e.g. `{ bold: true }`). + - The optional block post-processes the cell value. +- **`excel(worksheet: "Name", height: 20)`**: worksheet name (default `"Default"`) and data row height (default `20`). The header row is bold, centered and has a background color. +- **`presenter { ... }`**: defines `YourExport::Presenter` (a subclass of the parent exporter's presenter). Inside it you can use `model`, `relation`, `relation?`, `x` (record index) and `y` (index inside the `joins` relation). +- **`joins(:relation_name)`**: when `record.relation_name` is present, every element becomes its own row and `relation` is that element; the presenter first looks for a method on `relation`, then on `model`. Records with an empty relation produce a single row (`relation?` is `false`). +- **`each_with { |presenter| ... }`**: a hook called with each row's presenter before the columns are read. +- **`self.each_method = :find_in_batches`**: iterate with `records.find_in_batches` instead of `records.each`, for large ActiveRecord relations. -## Development +### 2. Export -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +```ruby +users = User.includes(:wallets).where(active: true) # or any enumerable +export = UserExport.new(users) -### Testing Multiple Ruby Versions +export.to_csv # => String, every field quoted ("Name","Mobile",...) +export.to_xlsx # => binary String of an .xlsx file +``` -To test against all supported Ruby versions at once: +Example CSV output for two users (the second has two wallets): + +``` +"Name","Mobile","Amount","Source" +"Eddie Li","+8869","100","web" +"Vegeta Lu","+8868","1100","web" +"Vegeta Lu","+8868","2100","web" +``` + +Serving it from a Rails controller: + +```ruby +def export + send_data UserExport.new(User.all).to_xlsx, + filename: "users.xlsx", + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" +end +``` + +### Known limitations + +These describe the current behavior of the code: + +- `ExportTo::Base#to_xls` currently delegates to the **XLSX** exporter, so it returns xlsx data. For a real legacy `.xls` file use `ExportTo::Exporter::Xls.new(export).to_xls`. +- `ExportTo::Base#each` calls an undefined `rows!` and raises `NoMethodError`. Iterate with `each!` (yields `columns, record, index`, with the header row first) or use one of the `to_*` methods. +- `width:` / `format:` and `excel` options only affect XLSX output. +- Subclasses share the column definitions arrays of their parent class (`class_attribute`), so do not call `set` on a subclass of an exporter that is used on its own. + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then run the tests: ```bash -bin/test_all +bundle exec rake test # also: rake t / rake (default) +bin/console # interactive prompt ``` -To check dependency compatibility: +Each Ruby / Rails combination has its own gemfile in `gemfiles/` (CI sets `BUNDLE_GEMFILE` to it): ```bash -bin/check_dependencies +BUNDLE_GEMFILE=$PWD/gemfiles/ruby_3.4_rails_8.1.gemfile bundle install +BUNDLE_GEMFILE=$PWD/gemfiles/ruby_3.4_rails_8.1.gemfile bundle exec rake test ``` -For more detailed testing instructions, see [TESTING.md](TESTING.md). +`bin/test_all` runs every supported Ruby (needs rbenv or rvm) and `bin/check_dependencies` checks dependency compatibility; see [TESTING.md](TESTING.md) for details (its version lists can lag behind CI). + +## Releasing + +Releases are automated with RubyGems [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) (no API key is stored in the repo): + +1. In your PR, bump `ExportTo::VERSION` in `lib/export_to/version.rb` ([Semantic Versioning](https://semver.org/): patch for fixes, minor for compatible features, major for breaking changes). +2. Add a matching `## [x.y.z] - YYYY-MM-DD` section to `CHANGELOG.md`. +3. A human reviews and merges the PR into `master` (AI accounts can only open PRs). +4. [`.github/workflows/publish.yml`](.github/workflows/publish.yml) runs on `master`: if the gemspec version is not on rubygems.org yet it runs `rake release` (builds the gem, tags `vX.Y.Z`, publishes). A merge without a version bump publishes nothing. -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). +Docs/CI-only PRs do not need a version bump. See [CLAUDE.md](CLAUDE.md): every PR must also update `README.md` (when behavior or usage changes) and add an entry to `CHANGELOG.md`. ## Contributing