csb 0.16.0 → 0.17.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/.gitignore +3 -0
- data/README.md +12 -0
- data/csb.gemspec +3 -1
- data/lib/csb/version.rb +1 -1
- data/skills/csb/SKILL.md +150 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f05cd902f17175e076c30da2f46b027e9306a8027c535a592bb6b177e6075247
|
|
4
|
+
data.tar.gz: 863d4f9abbdf7489a78494065c6b6f50f3cef74865e692002d8f0214f5a2b3d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2733b58f2e976bc92affbc510392949f12795d7d78f417fcee413255724f3d313088731869f0ed3179cc5ba6674cfb96133ae21fd44870d283004f26e6262d39
|
|
7
|
+
data.tar.gz: 1c7231a8a755593232e033d503d16951b2d39c998ce010c2063317460582cc9ae08a4a665594c2ddd2f38f514a293567ca968dbcd8cfdf0b373cce0a0c8aa5ee
|
data/.gitignore
CHANGED
data/README.md
CHANGED
|
@@ -174,6 +174,18 @@ Csb.configure do |config|
|
|
|
174
174
|
end
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
+
## Agent skill
|
|
178
|
+
|
|
179
|
+
This gem ships an [agent skill](skills/csb/) (`SKILL.md`) so AI coding agents (e.g. Claude Code) understand how to use csb. Install it into your project with [apm (Agent Package Manager)](https://github.com/microsoft/apm):
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
apm install aki77/csb/skills/csb
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
apm deploys the skill to each agent's directory (e.g. `.claude/skills/`) and locks the version in its lockfile.
|
|
186
|
+
|
|
187
|
+
Alternatively, if your project already pulls csb in via Bundler, the [bundler-skills](https://github.com/aki77/bundler-skills) plugin auto-syncs this skill on `bundle install` — keeping the skill version locked to the gem version.
|
|
188
|
+
|
|
177
189
|
## Contributing
|
|
178
190
|
|
|
179
191
|
Bug reports and pull requests are welcome on GitHub at https://github.com/aki77/csb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
data/csb.gemspec
CHANGED
|
@@ -20,7 +20,9 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
22
22
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
23
|
-
`git ls-files -z`.split("\x0").reject
|
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
+
f.match(%r{^(test|spec|features|\.claude)/}) || f.match(%r{^skills/.*-ja\.md$})
|
|
25
|
+
end
|
|
24
26
|
end
|
|
25
27
|
spec.bindir = "exe"
|
|
26
28
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
data/lib/csb/version.rb
CHANGED
data/skills/csb/SKILL.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: csb
|
|
3
|
+
description: "Generate streaming, Excel-friendly CSV downloads in Rails with the csb gem. Use when implementing a CSV export/download, writing a `.csv.csb` template, building CSV via Csb::Builder, or testing column definitions. Not for parsing CSV."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# csb
|
|
7
|
+
|
|
8
|
+
A simple, streaming CSV template engine for Ruby on Rails (the name is short for **CSV builder**). Use it to generate Excel-friendly, memory-safe CSV downloads with column definitions that are easy to read and unit-test.
|
|
9
|
+
|
|
10
|
+
## When to use csb
|
|
11
|
+
|
|
12
|
+
Reach for csb when building a CSV **download/export** in Rails. It replaces the naive hand-written `CSV.generate` in a view, which has recurring problems csb solves:
|
|
13
|
+
|
|
14
|
+
| Problem with hand-written CSV | csb solution |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| Garbled in Excel (UTF-8 without BOM) | Optional UTF-8 BOM output |
|
|
17
|
+
| Memory / timeout on large datasets | Row-by-row streaming download |
|
|
18
|
+
| Headers and values defined far apart | Each column's header + value on one line |
|
|
19
|
+
| Export logic buried in a view, hard to test | Extract column defs to a model and unit-test |
|
|
20
|
+
|
|
21
|
+
Out of scope: **parsing** CSV (use the `csv` stdlib directly).
|
|
22
|
+
|
|
23
|
+
## Approach 1: Template handler (the common case)
|
|
24
|
+
|
|
25
|
+
Controller — just assign the records (an `ActiveRecord::Relation` is fine, no `.to_a` needed):
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# app/controllers/reports_controller.rb
|
|
29
|
+
def index
|
|
30
|
+
@reports = Report.preload(:categories)
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
View — `app/views/reports/index.csv.csb` (note the `.csv.csb` extension):
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
csv.items = @reports
|
|
38
|
+
|
|
39
|
+
# Each column: header + value defined together.
|
|
40
|
+
csv.cols.add('Update date') { |r| l(r.updated_at.to_date) } # block receives the record
|
|
41
|
+
csv.cols.add('Categories') { |r| r.categories.pluck(:name).join(' ') }
|
|
42
|
+
csv.cols.add('Content', :content) # Symbol -> calls the method on the record
|
|
43
|
+
csv.cols.add('Static', 'dummy') # String -> output verbatim
|
|
44
|
+
csv.cols.add('Empty') # no value -> empty column
|
|
45
|
+
csv.cols.add('Dup', :col1) # the same header may be added more than once;
|
|
46
|
+
csv.cols.add('Dup', :col2) # columns are output in definition order
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A link like `link_to 'Download CSV', reports_path(format: :csv)` triggers the streaming download automatically.
|
|
50
|
+
|
|
51
|
+
### Large datasets
|
|
52
|
+
|
|
53
|
+
Pass an Enumerator so streaming starts immediately instead of loading every record first:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
csv.items = @reports.find_each
|
|
57
|
+
# With a decorator (e.g. Draper), kept lazy:
|
|
58
|
+
csv.items = @reports.find_each.lazy.map(&:decorate)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Per-view overrides
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
csv.filename = "reports_#{Time.current.to_i}.csv"
|
|
65
|
+
csv.streaming = false
|
|
66
|
+
csv.csv_options = { col_sep: "\t" }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Approach 2: Direct generation (outside a request)
|
|
70
|
+
|
|
71
|
+
For background jobs or anywhere outside a controller, use `Csb::Builder`:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
csv = Csb::Builder.new(items: items)
|
|
75
|
+
csv.cols.add('Update date') { |r| l(r.updated_at.to_date) }
|
|
76
|
+
csv.cols.add('Categories') { |r| r.categories.pluck(:name).join(' ') }
|
|
77
|
+
csv.cols.add('Content', :content)
|
|
78
|
+
csv.build # => returns the CSV string
|
|
79
|
+
|
|
80
|
+
# File.write('reports.csv', csv.build)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Testing column definitions
|
|
84
|
+
|
|
85
|
+
Extract the column definitions into a model method so they can be unit-tested apart from the view:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
# app/views/articles/index.csv.csb
|
|
89
|
+
csv.items = @articles
|
|
90
|
+
csv.cols = Article.csb_cols
|
|
91
|
+
|
|
92
|
+
# app/models/article.rb
|
|
93
|
+
def self.csb_cols
|
|
94
|
+
Csb::Cols.new do |cols|
|
|
95
|
+
cols.add('Update date') { |r| I18n.l(r.updated_at.to_date) }
|
|
96
|
+
cols.add('Categories') { |r| r.categories.pluck(:name).join(' ') }
|
|
97
|
+
cols.add('Title', :title)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
# spec/models/article_spec.rb
|
|
104
|
+
require 'csb/testing' # adds col_pairs and as_table
|
|
105
|
+
|
|
106
|
+
# One record, header/value pairs:
|
|
107
|
+
expect(Article.csb_cols.col_pairs(article)).to eq [
|
|
108
|
+
['Update date', '2020-01-01'],
|
|
109
|
+
['Categories', 'test rspec'],
|
|
110
|
+
['Title', 'Testing'],
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
# Whole table (header row + value rows):
|
|
114
|
+
expect(Article.csb_cols.as_table(articles)).to eq [
|
|
115
|
+
['Update date', 'Categories', 'Title'],
|
|
116
|
+
['2020-01-01', 'test rspec', 'Testing'],
|
|
117
|
+
['2020-02-01', 'rails gem', 'Rails 6.2'],
|
|
118
|
+
]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
`config/initializers/csb.rb`:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
Csb.configure do |config|
|
|
127
|
+
config.utf8_bom = true # default: false. Set true so Excel opens without mojibake.
|
|
128
|
+
config.streaming = false # default: true
|
|
129
|
+
config.csv_options = { col_sep: "\t" } # default: {}
|
|
130
|
+
|
|
131
|
+
# Called when an error is raised during streaming. WITHOUT this, mid-stream
|
|
132
|
+
# errors are silently swallowed and won't reach tools like Bugsnag.
|
|
133
|
+
config.after_streaming_error = ->(error) do # default: nil
|
|
134
|
+
Rails.logger.error(error)
|
|
135
|
+
Bugsnag.notify(error)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Error classes to ignore (not re-raise) during streaming, e.g. when the
|
|
139
|
+
# client disconnects before the download finishes.
|
|
140
|
+
config.ignore_class_names = %w[Puma::ConnectionError] # default: %w[Puma::ConnectionError]
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Pitfalls
|
|
145
|
+
|
|
146
|
+
- The view must use the `.csv.csb` extension.
|
|
147
|
+
- Trigger the download with `format: :csv` (e.g. `link_to 'Download', reports_path(format: :csv)`).
|
|
148
|
+
- The `cols.add` value rules: block → receives the record; `Symbol` → calls that method; `String` → literal; omitted → empty cell.
|
|
149
|
+
- Streaming errors are swallowed unless you set `config.after_streaming_error` — set it if you rely on error reporting.
|
|
150
|
+
- For big tables, pass `find_each` (an Enumerator), not a fully-loaded array, to keep streaming memory-safe.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: csb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- aki77
|
|
@@ -111,6 +111,7 @@ files:
|
|
|
111
111
|
- lib/csb/template.rb
|
|
112
112
|
- lib/csb/testing.rb
|
|
113
113
|
- lib/csb/version.rb
|
|
114
|
+
- skills/csb/SKILL.md
|
|
114
115
|
homepage: https://github.com/aki77/csb
|
|
115
116
|
licenses:
|
|
116
117
|
- MIT
|