comma 4.9.0 → 5.1.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/copilot-instructions.md +56 -0
- data/.github/workflows/build.yml +12 -3
- data/.rubocop_todo.yml +5 -4
- data/Appraisals +20 -22
- data/Gemfile.lock +36 -25
- data/README.md +161 -9
- data/comma.gemspec +7 -2
- data/gemfiles/{active6.0.6.gemfile → active7.1.6.gemfile} +2 -2
- data/gemfiles/active7.1.6.gemfile.lock +183 -0
- data/gemfiles/{active7.0.8.gemfile → active7.2.3.2.gemfile} +2 -2
- data/gemfiles/active7.2.3.2.gemfile.lock +182 -0
- data/gemfiles/{active6.1.7.6.gemfile → active8.0.5.1.gemfile} +2 -2
- data/gemfiles/active8.0.5.1.gemfile.lock +183 -0
- data/gemfiles/{active7.1.3.gemfile → active8.1.3.1.gemfile} +2 -2
- data/gemfiles/active8.1.3.1.gemfile.lock +183 -0
- data/gemfiles/{rails7.0.8.gemfile → rails7.1.6.gemfile} +1 -1
- data/gemfiles/rails7.1.6.gemfile.lock +326 -0
- data/gemfiles/{rails7.1.3.gemfile → rails7.2.3.2.gemfile} +1 -1
- data/gemfiles/rails7.2.3.2.gemfile.lock +321 -0
- data/gemfiles/{rails6.0.6.gemfile → rails8.0.5.1.gemfile} +1 -2
- data/gemfiles/rails8.0.5.1.gemfile.lock +319 -0
- data/gemfiles/{rails6.1.7.6.gemfile → rails8.1.3.1.gemfile} +1 -2
- data/gemfiles/rails8.1.3.1.gemfile.lock +322 -0
- data/lib/comma/array.rb +1 -3
- data/lib/comma/collection_export.rb +9 -0
- data/lib/comma/data_extractor.rb +16 -20
- data/lib/comma/data_mapper_collection.rb +1 -3
- data/lib/comma/extractor.rb +33 -2
- data/lib/comma/generator.rb +4 -8
- data/lib/comma/header_extractor.rb +10 -19
- data/lib/comma/mongoid.rb +1 -3
- data/lib/comma/object.rb +37 -11
- data/lib/comma/options.rb +18 -0
- data/lib/comma/rails/renderer.rb +21 -0
- data/lib/comma/rails.rb +16 -0
- data/lib/comma/version.rb +1 -1
- data/lib/comma.rb +4 -43
- data/spec/comma/comma_spec.rb +93 -34
- data/spec/comma/data_extractor_spec.rb +3 -3
- data/spec/comma/header_extractor_spec.rb +1 -1
- data/spec/comma/options_spec.rb +63 -0
- data/spec/comma/rails/active_record_spec.rb +0 -1
- data/spec/controllers/users_controller_spec.rb +13 -28
- data/spec/gemspec_spec.rb +11 -0
- data/spec/support/comma_class_helper.rb +12 -0
- data/spec/support/csv_expectation_helper.rb +14 -0
- metadata +92 -23
- data/gemfiles/active6.0.6.gemfile.lock +0 -136
- data/gemfiles/active6.1.7.6.gemfile.lock +0 -135
- data/gemfiles/active7.0.8.gemfile.lock +0 -133
- data/gemfiles/active7.1.3.gemfile.lock +0 -139
- data/gemfiles/rails6.0.6.gemfile.lock +0 -268
- data/gemfiles/rails6.1.7.6.gemfile.lock +0 -271
- data/gemfiles/rails7.0.8.gemfile.lock +0 -266
- data/gemfiles/rails7.1.3.gemfile.lock +0 -289
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ce045c478a147d8f8cf10cb1228ba6ba810b459ad61cfccd9b91f4029dad9c73
|
|
4
|
+
data.tar.gz: 75da1697c5e08af1a1bae4567780d2b9e0a535de873b910f6760479be5a3b519
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d921a85f27d283ad74aba9a0996daa982af4ad3cf6599a1a473234b50fe25e9e5224cca78315de35aa52a1a88610beb172ae514e35f8f0c8abd162ab334c24c5
|
|
7
|
+
data.tar.gz: 81e45224e2949885afaa99859391c229358163b1ce840ff57b900bb13ac1f3d71b0b54f3b4dfbc4d4ee54ae5e945f15b1dc1bd7a788998028fc45b01e84ee49c
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copilot instructions for `comma`
|
|
2
|
+
|
|
3
|
+
## Build, test, and lint commands
|
|
4
|
+
|
|
5
|
+
This repository is a Ruby gem. Ruby 3.0+ is required, and the CI matrix exercises Ruby 3.0-3.4 against Rails/ActiveRecord appraisals from 6.0 through 7.1.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
# install dependencies for the base Gemfile
|
|
9
|
+
bundle install
|
|
10
|
+
|
|
11
|
+
# install all appraisal Gemfiles used by CI
|
|
12
|
+
bundle exec appraisal install
|
|
13
|
+
|
|
14
|
+
# build the gem
|
|
15
|
+
bundle exec rake build
|
|
16
|
+
|
|
17
|
+
# lint
|
|
18
|
+
bundle exec rubocop -P
|
|
19
|
+
# or
|
|
20
|
+
bundle exec rake rubocop
|
|
21
|
+
|
|
22
|
+
# base test suite (runs non-Rails specs when framework gems are absent)
|
|
23
|
+
bundle exec rspec spec
|
|
24
|
+
# or
|
|
25
|
+
bundle exec rake spec
|
|
26
|
+
|
|
27
|
+
# run one example from the base suite
|
|
28
|
+
bundle exec rspec spec/comma/comma_spec.rb:205
|
|
29
|
+
|
|
30
|
+
# run the full multi-gemfile matrix locally
|
|
31
|
+
bundle exec appraisal rake spec
|
|
32
|
+
|
|
33
|
+
# run a single spec file under a specific appraisal
|
|
34
|
+
bundle exec appraisal active7.1.3 bundle exec rspec spec/comma/rails/active_record_spec.rb
|
|
35
|
+
|
|
36
|
+
# CI equivalent for a single matrix entry
|
|
37
|
+
BUNDLE_GEMFILE=gemfiles/rails7.1.3.gemfile bundle exec rspec spec/controllers/users_controller_spec.rb
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## High-level architecture
|
|
41
|
+
|
|
42
|
+
- `lib/comma/object.rb` adds the core DSL globally via `Object.comma`. Each class stores named format blocks in `comma_formats`, and subclasses inherit a shallow copy of those formats.
|
|
43
|
+
- `lib/comma/extractor.rb` is the shared execution engine. It `instance_eval`s a stored format block and collects stringified results. The two concrete extractors split responsibilities:
|
|
44
|
+
- `lib/comma/data_extractor.rb` resolves values from the instance or an associated object and applies optional block-based transformations.
|
|
45
|
+
- `lib/comma/header_extractor.rb` derives headers from the same DSL and can map association columns to the associated model class for humanized labels.
|
|
46
|
+
- `lib/comma/generator.rb` is the only place that turns object rows into CSV output. `Array`, `ActiveRecord::Relation`, `Mongoid::Criteria`, and `DataMapper::Collection` each delegate to it from their own extension files.
|
|
47
|
+
- `lib/comma.rb` wires framework integrations lazily with `ActiveSupport.on_load`. That file is also where the Rails `render csv:` renderer is registered, so controller behavior is part of the gem's load path rather than test-only glue.
|
|
48
|
+
- Integration specs use the miniature app under `spec/rails_app/` plus inline model definitions inside spec files. The base suite still runs without Rails, Mongoid, DataMapper, or ActiveRecord because integration examples are wrapped in `if defined?(...)` guards in the specs.
|
|
49
|
+
|
|
50
|
+
## Key conventions
|
|
51
|
+
|
|
52
|
+
- The CSV DSL is method-call driven. Inside a `comma` block, a bare method adds one column, a string argument overrides the header label, and symbol/hash arguments traverse associations. The same block definition drives both headers and row data.
|
|
53
|
+
- Style composition uses `__use__ :style_name`, and computed/static columns use `__static_column__`. Preserve those internal DSL entry points when changing extractor behavior.
|
|
54
|
+
- Header labels default to `HeaderExtractor.value_humanizer`, which is mutable global state. Specs that override it reset it afterward; follow the same pattern for any future changes.
|
|
55
|
+
- `ActiveRecord::Relation#to_comma` intentionally uses `find_each` only for unordered, unlimited relations. If a relation has `limit` or `order`, it falls back to `each`; when Rails is loaded it also emits a warning instead of forcing batched iteration.
|
|
56
|
+
- Specs commonly define throwaway classes inline to document DSL behavior instead of introducing fixtures or helper classes. Prefer extending the existing focused examples in `spec/comma/*` unless a change genuinely needs the Rails test app.
|
data/.github/workflows/build.yml
CHANGED
|
@@ -5,12 +5,21 @@ jobs:
|
|
|
5
5
|
strategy:
|
|
6
6
|
fail-fast: false
|
|
7
7
|
matrix:
|
|
8
|
-
ruby: ['3.
|
|
9
|
-
gemfile: ['
|
|
8
|
+
ruby: ['3.1', '3.2', '3.3', '3.4', '4.0']
|
|
9
|
+
gemfile: ['active7.1.6', 'active7.2.3.2', 'active8.0.5.1', 'active8.1.3.1', 'rails7.1.6', 'rails7.2.3.2', 'rails8.0.5.1', 'rails8.1.3.1']
|
|
10
|
+
exclude:
|
|
11
|
+
- ruby: '3.1'
|
|
12
|
+
gemfile: 'active8.0.5.1'
|
|
13
|
+
- ruby: '3.1'
|
|
14
|
+
gemfile: 'active8.1.3.1'
|
|
15
|
+
- ruby: '3.1'
|
|
16
|
+
gemfile: 'rails8.0.5.1'
|
|
17
|
+
- ruby: '3.1'
|
|
18
|
+
gemfile: 'rails8.1.3.1'
|
|
10
19
|
runs-on: ubuntu-latest
|
|
11
20
|
env:
|
|
12
21
|
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
|
|
13
|
-
BUNDLE_FORCE_RUBY_PLATFORM: ${{ matrix.ruby == '3.4' }}
|
|
22
|
+
BUNDLE_FORCE_RUBY_PLATFORM: ${{ matrix.ruby == '3.4' || matrix.ruby == '4.0' }}
|
|
14
23
|
RUBY_OPT: --disable=did_you_mean
|
|
15
24
|
services:
|
|
16
25
|
mongodb:
|
data/.rubocop_todo.yml
CHANGED
|
@@ -55,7 +55,6 @@ Layout/SpaceAroundMethodCallOperator:
|
|
|
55
55
|
# AllowedMethods: enums
|
|
56
56
|
Lint/ConstantDefinitionInBlock:
|
|
57
57
|
Exclude:
|
|
58
|
-
- 'spec/comma/comma_spec.rb'
|
|
59
58
|
- 'spec/comma/rails/active_record_spec.rb'
|
|
60
59
|
- 'spec/comma/rails/data_mapper_collection_spec.rb'
|
|
61
60
|
- 'spec/comma/rails/mongoid_spec.rb'
|
|
@@ -108,13 +107,14 @@ Style/BlockDelimiters:
|
|
|
108
107
|
- 'spec/comma/data_extractor_spec.rb'
|
|
109
108
|
- 'spec/comma/header_extractor_spec.rb'
|
|
110
109
|
|
|
111
|
-
# Offense count:
|
|
110
|
+
# Offense count: 12
|
|
112
111
|
# Configuration parameters: AllowedConstants.
|
|
113
112
|
Style/Documentation:
|
|
114
113
|
Exclude:
|
|
115
114
|
- 'spec/**/*'
|
|
116
115
|
- 'test/**/*'
|
|
117
116
|
- 'lib/comma/array.rb'
|
|
117
|
+
- 'lib/comma/collection_export.rb'
|
|
118
118
|
- 'lib/comma/data_extractor.rb'
|
|
119
119
|
- 'lib/comma/data_mapper_collection.rb'
|
|
120
120
|
- 'lib/comma/extractor.rb'
|
|
@@ -122,13 +122,14 @@ Style/Documentation:
|
|
|
122
122
|
- 'lib/comma/header_extractor.rb'
|
|
123
123
|
- 'lib/comma/mongoid.rb'
|
|
124
124
|
- 'lib/comma/object.rb'
|
|
125
|
+
- 'lib/comma/options.rb'
|
|
125
126
|
- 'lib/comma/relation.rb'
|
|
126
127
|
|
|
127
|
-
# Offense count:
|
|
128
|
+
# Offense count: 2
|
|
128
129
|
Style/MissingRespondToMissing:
|
|
129
130
|
Exclude:
|
|
130
131
|
- 'lib/comma/data_extractor.rb'
|
|
131
|
-
- 'lib/comma/
|
|
132
|
+
- 'lib/comma/extractor.rb'
|
|
132
133
|
|
|
133
134
|
# Offense count: 1
|
|
134
135
|
# This cop supports safe autocorrection (--autocorrect).
|
data/Appraisals
CHANGED
|
@@ -1,43 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
appraise '
|
|
4
|
-
gem 'rails', '
|
|
3
|
+
appraise 'rails7.1.6' do
|
|
4
|
+
gem 'rails', '7.1.6'
|
|
5
5
|
gem 'rspec-rails'
|
|
6
|
-
gem 'test-unit'
|
|
7
6
|
end
|
|
8
7
|
|
|
9
|
-
appraise '
|
|
10
|
-
gem 'activesupport', '
|
|
11
|
-
gem 'activerecord', '
|
|
8
|
+
appraise 'active7.1.6' do
|
|
9
|
+
gem 'activesupport', '7.1.6'
|
|
10
|
+
gem 'activerecord', '7.1.6'
|
|
12
11
|
end
|
|
13
12
|
|
|
14
|
-
appraise '
|
|
15
|
-
gem 'rails', '
|
|
13
|
+
appraise 'rails7.2.3.2' do
|
|
14
|
+
gem 'rails', '7.2.3.2'
|
|
16
15
|
gem 'rspec-rails'
|
|
17
|
-
gem 'test-unit'
|
|
18
16
|
end
|
|
19
17
|
|
|
20
|
-
appraise '
|
|
21
|
-
gem 'activesupport', '
|
|
22
|
-
gem 'activerecord', '
|
|
18
|
+
appraise 'active7.2.3.2' do
|
|
19
|
+
gem 'activesupport', '7.2.3.2'
|
|
20
|
+
gem 'activerecord', '7.2.3.2'
|
|
23
21
|
end
|
|
24
22
|
|
|
25
|
-
appraise '
|
|
26
|
-
gem 'rails', '
|
|
23
|
+
appraise 'rails8.0.5.1' do
|
|
24
|
+
gem 'rails', '8.0.5.1'
|
|
27
25
|
gem 'rspec-rails'
|
|
28
26
|
end
|
|
29
27
|
|
|
30
|
-
appraise '
|
|
31
|
-
gem 'activesupport', '
|
|
32
|
-
gem 'activerecord', '
|
|
28
|
+
appraise 'active8.0.5.1' do
|
|
29
|
+
gem 'activesupport', '8.0.5.1'
|
|
30
|
+
gem 'activerecord', '8.0.5.1'
|
|
33
31
|
end
|
|
34
32
|
|
|
35
|
-
appraise '
|
|
36
|
-
gem 'rails', '
|
|
33
|
+
appraise 'rails8.1.3.1' do
|
|
34
|
+
gem 'rails', '8.1.3.1'
|
|
37
35
|
gem 'rspec-rails'
|
|
38
36
|
end
|
|
39
37
|
|
|
40
|
-
appraise '
|
|
41
|
-
gem 'activesupport', '
|
|
42
|
-
gem 'activerecord', '
|
|
38
|
+
appraise 'active8.1.3.1' do
|
|
39
|
+
gem 'activesupport', '8.1.3.1'
|
|
40
|
+
gem 'activerecord', '8.1.3.1'
|
|
43
41
|
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
comma (
|
|
4
|
+
comma (5.1.0)
|
|
5
5
|
activesupport (>= 4.2.0)
|
|
6
6
|
csv (>= 3.3)
|
|
7
7
|
|
|
8
8
|
GEM
|
|
9
9
|
remote: https://rubygems.org/
|
|
10
10
|
specs:
|
|
11
|
-
activemodel (7.
|
|
12
|
-
activesupport (= 7.
|
|
13
|
-
activesupport (7.
|
|
11
|
+
activemodel (7.2.3.1)
|
|
12
|
+
activesupport (= 7.2.3.1)
|
|
13
|
+
activesupport (7.2.3.1)
|
|
14
14
|
base64
|
|
15
|
+
benchmark (>= 0.3)
|
|
15
16
|
bigdecimal
|
|
16
|
-
concurrent-ruby (~> 1.0, >= 1.
|
|
17
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
17
18
|
connection_pool (>= 2.2.5)
|
|
18
19
|
drb
|
|
19
20
|
i18n (>= 1.6, < 2)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
logger (>= 1.4.2)
|
|
22
|
+
minitest (>= 5.1, < 6)
|
|
23
|
+
securerandom (>= 0.3)
|
|
24
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
23
25
|
appraisal (1.0.3)
|
|
24
26
|
bundler
|
|
25
27
|
rake
|
|
26
28
|
thor (>= 0.14.0)
|
|
27
29
|
ast (2.4.2)
|
|
28
30
|
base64 (0.3.0)
|
|
31
|
+
benchmark (0.5.0)
|
|
29
32
|
bigdecimal (4.1.2)
|
|
30
|
-
|
|
33
|
+
cgi (0.3.7)
|
|
34
|
+
concurrent-ruby (1.3.7)
|
|
31
35
|
connection_pool (2.5.5)
|
|
32
36
|
coveralls (0.8.23)
|
|
33
37
|
json (>= 1.8, < 3)
|
|
@@ -42,9 +46,11 @@ GEM
|
|
|
42
46
|
i18n (1.14.8)
|
|
43
47
|
concurrent-ruby (~> 1.0)
|
|
44
48
|
json (2.7.1)
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
logger (1.7.0)
|
|
50
|
+
mini_portile2 (2.8.9)
|
|
51
|
+
minitest (5.27.0)
|
|
47
52
|
mutex_m (0.3.0)
|
|
53
|
+
ostruct (0.5.2)
|
|
48
54
|
parallel (1.24.0)
|
|
49
55
|
parser (3.3.0.4)
|
|
50
56
|
ast (~> 2.4.1)
|
|
@@ -54,26 +60,26 @@ GEM
|
|
|
54
60
|
rake (13.0.1)
|
|
55
61
|
regexp_parser (2.9.0)
|
|
56
62
|
rexml (3.4.2)
|
|
57
|
-
rspec (3.
|
|
58
|
-
rspec-core (~> 3.
|
|
59
|
-
rspec-expectations (~> 3.
|
|
60
|
-
rspec-mocks (~> 3.
|
|
63
|
+
rspec (3.13.0)
|
|
64
|
+
rspec-core (~> 3.13.0)
|
|
65
|
+
rspec-expectations (~> 3.13.0)
|
|
66
|
+
rspec-mocks (~> 3.13.0)
|
|
61
67
|
rspec-activemodel-mocks (1.1.0)
|
|
62
68
|
activemodel (>= 3.0)
|
|
63
69
|
activesupport (>= 3.0)
|
|
64
70
|
rspec-mocks (>= 2.99, < 4.0)
|
|
65
|
-
rspec-core (3.
|
|
66
|
-
rspec-support (~> 3.
|
|
67
|
-
rspec-expectations (3.
|
|
71
|
+
rspec-core (3.13.1)
|
|
72
|
+
rspec-support (~> 3.13.0)
|
|
73
|
+
rspec-expectations (3.13.3)
|
|
68
74
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
69
|
-
rspec-support (~> 3.
|
|
75
|
+
rspec-support (~> 3.13.0)
|
|
70
76
|
rspec-its (1.2.0)
|
|
71
77
|
rspec-core (>= 3.0.0)
|
|
72
78
|
rspec-expectations (>= 3.0.0)
|
|
73
|
-
rspec-mocks (3.
|
|
79
|
+
rspec-mocks (3.13.1)
|
|
74
80
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
75
|
-
rspec-support (~> 3.
|
|
76
|
-
rspec-support (3.
|
|
81
|
+
rspec-support (~> 3.13.0)
|
|
82
|
+
rspec-support (3.13.1)
|
|
77
83
|
rubocop (1.30.1)
|
|
78
84
|
parallel (~> 1.10)
|
|
79
85
|
parser (>= 3.1.0.0)
|
|
@@ -89,12 +95,13 @@ GEM
|
|
|
89
95
|
rubocop (>= 0.90.0, < 2.0)
|
|
90
96
|
rubocop-ast (>= 0.4.0)
|
|
91
97
|
ruby-progressbar (1.13.0)
|
|
98
|
+
securerandom (0.4.1)
|
|
92
99
|
simplecov (0.16.1)
|
|
93
100
|
docile (~> 1.1)
|
|
94
101
|
json (>= 1.8, < 3)
|
|
95
102
|
simplecov-html (~> 0.10.0)
|
|
96
103
|
simplecov-html (0.10.2)
|
|
97
|
-
sqlite3 (
|
|
104
|
+
sqlite3 (2.8.1)
|
|
98
105
|
mini_portile2 (~> 2.8.0)
|
|
99
106
|
sync (0.5.0)
|
|
100
107
|
term-ansicolor (1.7.1)
|
|
@@ -112,14 +119,18 @@ PLATFORMS
|
|
|
112
119
|
DEPENDENCIES
|
|
113
120
|
appraisal (~> 1.0.0)
|
|
114
121
|
base64
|
|
122
|
+
benchmark
|
|
115
123
|
bigdecimal
|
|
124
|
+
cgi
|
|
116
125
|
comma!
|
|
117
126
|
coveralls
|
|
118
127
|
drb
|
|
119
|
-
|
|
128
|
+
logger
|
|
129
|
+
minitest (= 5.27.0)
|
|
120
130
|
mutex_m
|
|
131
|
+
ostruct
|
|
121
132
|
rake (~> 13.0.1)
|
|
122
|
-
rspec (~> 3.
|
|
133
|
+
rspec (~> 3.13)
|
|
123
134
|
rspec-activemodel-mocks
|
|
124
135
|
rspec-its
|
|
125
136
|
rubocop (~> 1.30.0)
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Comma
|
|
2
2
|
|
|
3
|
-
A library
|
|
3
|
+
A library for generating comma-separated values (CSV) from Ruby objects, arrays, and supported ORM collections.
|
|
4
4
|
|
|
5
5
|
[](http://badge.fury.io/rb/comma) [](https://github.com/comma-csv/comma/actions/workflows/build.yml) [](https://codeclimate.com/github/comma-csv/comma)
|
|
6
6
|
|
|
@@ -8,7 +8,9 @@ A library to generate comma seperated value (CSV) for Ruby objects like ActiveRe
|
|
|
8
8
|
|
|
9
9
|
### Prerequisites
|
|
10
10
|
|
|
11
|
-
You need
|
|
11
|
+
You need Ruby 3.1 or later.
|
|
12
|
+
|
|
13
|
+
For Rails / ActiveRecord integration, this repository is currently tested against ActiveRecord and Rails 7.1 and 7.2 on Ruby 3.1 through 4.0, and Rails 8.0 and 8.1 on Ruby 3.2 through 4.0 (Rails 8.x requires Ruby >= 3.2).
|
|
12
14
|
|
|
13
15
|
### Installing
|
|
14
16
|
|
|
@@ -17,33 +19,183 @@ Comma is distributed as a gem, best installed via Bundler.
|
|
|
17
19
|
Include the gem in your Gemfile:
|
|
18
20
|
|
|
19
21
|
```ruby
|
|
20
|
-
gem 'comma', '~>
|
|
22
|
+
gem 'comma', '~> 5.1.0'
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
Or, if you want to live life on the edge, you can get master from the main comma repository:
|
|
24
26
|
|
|
25
27
|
```ruby
|
|
26
|
-
gem 'comma',
|
|
28
|
+
gem 'comma', git: 'https://github.com/comma-csv/comma.git'
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
Then, run `bundle install`.
|
|
30
32
|
|
|
31
33
|
### Usage
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
Define a CSV format on your object with `comma`. Calling `to_comma` on a single object returns the row values, while calling it on an array or supported collection generates CSV output:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
class User
|
|
39
|
+
attr_reader :first_name, :last_name
|
|
40
|
+
|
|
41
|
+
def initialize(first_name, last_name)
|
|
42
|
+
@first_name = first_name
|
|
43
|
+
@last_name = last_name
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
comma do
|
|
47
|
+
first_name
|
|
48
|
+
last_name
|
|
49
|
+
full_name 'Name'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def full_name
|
|
53
|
+
"#{first_name} #{last_name}".strip
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
user = User.new('Ada', 'Lovelace')
|
|
58
|
+
user.to_comma
|
|
59
|
+
# => ["Ada", "Lovelace", "Ada Lovelace"]
|
|
60
|
+
|
|
61
|
+
users = [User.new('Ada', 'Lovelace'), User.new('Grace', 'Hopper')]
|
|
62
|
+
users.to_comma
|
|
63
|
+
# => "First name,Last name,Name\nAda,Lovelace,Ada Lovelace\nGrace,Hopper,Grace Hopper\n"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You can also define named styles and select them when generating CSV:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
class User
|
|
70
|
+
comma :short do
|
|
71
|
+
first_name
|
|
72
|
+
last_name
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
users.to_comma(:short)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Dynamic columns
|
|
80
|
+
|
|
81
|
+
Formats are evaluated separately to produce headers and to produce each row. For CSVs whose columns are configured at runtime, resolve and order the full field-definition list before declaring the `comma` format, and keep that format isolated to the current export so another export cannot redefine it midway through generation.
|
|
82
|
+
|
|
83
|
+
The following self-contained example uses `Struct` rather than Ruby 3.2+'s
|
|
84
|
+
`Data`. It resolves the runtime field list once, attaches the format to a row
|
|
85
|
+
class created only for that export, emits headers from `field.label`, and reads
|
|
86
|
+
each value from the wrapped user's associated dynamic values via `field.key`:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
DynamicField = Struct.new(:key, :label)
|
|
90
|
+
DynamicValue = Struct.new(:field_key, :value)
|
|
91
|
+
|
|
92
|
+
class User
|
|
93
|
+
attr_reader :name
|
|
94
|
+
|
|
95
|
+
def initialize(name, dynamic_values)
|
|
96
|
+
@name = name
|
|
97
|
+
@dynamic_values_by_field = dynamic_values.to_h do |value|
|
|
98
|
+
[value.field_key, value.value]
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def dynamic_value(field_key)
|
|
103
|
+
@dynamic_values_by_field[field_key]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def export_users(users, resolved_fields)
|
|
108
|
+
fields = resolved_fields.to_a
|
|
109
|
+
row_class = Struct.new(:user)
|
|
110
|
+
|
|
111
|
+
row_class.comma :export do
|
|
112
|
+
__static_column__ 'Name' do |row|
|
|
113
|
+
row.user.name
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
fields.each do |field|
|
|
117
|
+
__static_column__ field.label do |row|
|
|
118
|
+
row.user.dynamic_value(field.key)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
rows = users.map { |user| row_class.new(user) }
|
|
124
|
+
rows.to_comma(:export)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
users = [
|
|
128
|
+
User.new('Ada', [
|
|
129
|
+
DynamicValue.new(:favorite_color, 'Blue'),
|
|
130
|
+
DynamicValue.new(:support_tier, 'Gold')
|
|
131
|
+
]),
|
|
132
|
+
User.new('Grace', [
|
|
133
|
+
DynamicValue.new(:favorite_color, 'Green'),
|
|
134
|
+
DynamicValue.new(:support_tier, 'Silver')
|
|
135
|
+
])
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
fields = [
|
|
139
|
+
DynamicField.new(:favorite_color, 'Favorite color'),
|
|
140
|
+
DynamicField.new(:support_tier, 'Support tier')
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
export_users(users, fields)
|
|
144
|
+
# => "Name,Favorite color,Support tier\nAda,Blue,Gold\nGrace,Green,Silver\n"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
In the example, `field.label` supplies each runtime header and `row.user.dynamic_value(field.key)` reads the matching value from the associated `dynamic_values` records.
|
|
148
|
+
|
|
149
|
+
When field definitions come from tenant or platform configuration, fetch and order them once for the requested export before the `comma` block is defined. That lets header generation and row generation iterate over the same field list, preserving header/value alignment. Creating `row_class` inside `export_users` also isolates the `:export` format to that one CSV, so a simultaneous export builds its own class instead of overwriting the format being used here.
|
|
150
|
+
|
|
151
|
+
In Rails controllers, requiring the gem registers `render csv:` support:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
def index
|
|
155
|
+
render csv: User.all
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
See the [wiki](https://github.com/comma-csv/comma/wiki) for more usage examples.
|
|
34
160
|
|
|
35
161
|
## Running the tests
|
|
36
162
|
|
|
37
|
-
|
|
163
|
+
Install dependencies:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
bundle install
|
|
167
|
+
bundle exec appraisal install
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Run the default test suite and linter:
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
bundle exec rspec spec
|
|
174
|
+
bundle exec rubocop -P
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Run a single example:
|
|
38
178
|
|
|
39
179
|
```sh
|
|
40
|
-
|
|
41
|
-
|
|
180
|
+
bundle exec rspec spec/comma/comma_spec.rb:205
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
To run the test suite across the Rails / ActiveRecord gemfile matrix, this repository uses [Appraisal](https://github.com/thoughtbot/appraisal):
|
|
42
184
|
|
|
185
|
+
```sh
|
|
186
|
+
bundle exec appraisal rake spec
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
You can also run a specific spec under one appraisal:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
bundle exec appraisal rails7.1.6 bundle exec rspec spec/controllers/users_controller_spec.rb
|
|
43
193
|
```
|
|
44
194
|
|
|
45
195
|
## Contributing
|
|
46
196
|
|
|
197
|
+
Please make sure `bundle exec rspec spec`, `bundle exec rubocop -P`, and any relevant `bundle exec appraisal ...` commands pass before opening a pull request.
|
|
198
|
+
|
|
47
199
|
## Versioning
|
|
48
200
|
|
|
49
201
|
We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/comma-csv/comma/tags).
|
|
@@ -56,4 +208,4 @@ We use [SemVer](http://semver.org/) for versioning. For the versions available,
|
|
|
56
208
|
|
|
57
209
|
## License
|
|
58
210
|
|
|
59
|
-
This project is licensed under the MIT License - see the [MIT-LICENSE](https://github.com/comma-csv/comma/blob/master/MIT-LICENSE) file
|
|
211
|
+
This project is licensed under the MIT License - see the [MIT-LICENSE](https://github.com/comma-csv/comma/blob/master/MIT-LICENSE) file for details.
|
data/comma.gemspec
CHANGED
|
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
|
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
|
16
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
17
17
|
s.require_paths = ['lib']
|
|
18
|
+
s.required_ruby_version = '>= 3.1'
|
|
18
19
|
|
|
19
20
|
s.licenses = ['MIT']
|
|
20
21
|
|
|
@@ -23,12 +24,16 @@ Gem::Specification.new do |s|
|
|
|
23
24
|
|
|
24
25
|
s.add_development_dependency 'appraisal', ['~> 1.0.0']
|
|
25
26
|
s.add_development_dependency 'base64'
|
|
27
|
+
s.add_development_dependency 'benchmark'
|
|
26
28
|
s.add_development_dependency 'bigdecimal'
|
|
29
|
+
s.add_development_dependency 'cgi'
|
|
27
30
|
s.add_development_dependency 'drb'
|
|
28
|
-
s.add_development_dependency '
|
|
31
|
+
s.add_development_dependency 'logger'
|
|
32
|
+
s.add_development_dependency 'minitest', '5.27.0'
|
|
29
33
|
s.add_development_dependency 'mutex_m'
|
|
34
|
+
s.add_development_dependency 'ostruct'
|
|
30
35
|
s.add_development_dependency 'rake', '~> 13.0.1'
|
|
31
|
-
s.add_development_dependency 'rspec', ['~> 3.
|
|
36
|
+
s.add_development_dependency 'rspec', ['~> 3.13']
|
|
32
37
|
s.add_development_dependency 'rspec-activemodel-mocks'
|
|
33
38
|
s.add_development_dependency 'rspec-its'
|
|
34
39
|
end
|
|
@@ -6,7 +6,7 @@ gem "coveralls", :require => false
|
|
|
6
6
|
gem "rubocop", "~> 1.30.0", :require => false
|
|
7
7
|
gem "rubocop-performance", :require => false
|
|
8
8
|
gem "sqlite3"
|
|
9
|
-
gem "activesupport", "
|
|
10
|
-
gem "activerecord", "
|
|
9
|
+
gem "activesupport", "7.1.6"
|
|
10
|
+
gem "activerecord", "7.1.6"
|
|
11
11
|
|
|
12
12
|
gemspec :path => "../"
|