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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +56 -0
  3. data/.github/workflows/build.yml +12 -3
  4. data/.rubocop_todo.yml +5 -4
  5. data/Appraisals +20 -22
  6. data/Gemfile.lock +36 -25
  7. data/README.md +161 -9
  8. data/comma.gemspec +7 -2
  9. data/gemfiles/{active6.0.6.gemfile → active7.1.6.gemfile} +2 -2
  10. data/gemfiles/active7.1.6.gemfile.lock +183 -0
  11. data/gemfiles/{active7.0.8.gemfile → active7.2.3.2.gemfile} +2 -2
  12. data/gemfiles/active7.2.3.2.gemfile.lock +182 -0
  13. data/gemfiles/{active6.1.7.6.gemfile → active8.0.5.1.gemfile} +2 -2
  14. data/gemfiles/active8.0.5.1.gemfile.lock +183 -0
  15. data/gemfiles/{active7.1.3.gemfile → active8.1.3.1.gemfile} +2 -2
  16. data/gemfiles/active8.1.3.1.gemfile.lock +183 -0
  17. data/gemfiles/{rails7.0.8.gemfile → rails7.1.6.gemfile} +1 -1
  18. data/gemfiles/rails7.1.6.gemfile.lock +326 -0
  19. data/gemfiles/{rails7.1.3.gemfile → rails7.2.3.2.gemfile} +1 -1
  20. data/gemfiles/rails7.2.3.2.gemfile.lock +321 -0
  21. data/gemfiles/{rails6.0.6.gemfile → rails8.0.5.1.gemfile} +1 -2
  22. data/gemfiles/rails8.0.5.1.gemfile.lock +319 -0
  23. data/gemfiles/{rails6.1.7.6.gemfile → rails8.1.3.1.gemfile} +1 -2
  24. data/gemfiles/rails8.1.3.1.gemfile.lock +322 -0
  25. data/lib/comma/array.rb +1 -3
  26. data/lib/comma/collection_export.rb +9 -0
  27. data/lib/comma/data_extractor.rb +16 -20
  28. data/lib/comma/data_mapper_collection.rb +1 -3
  29. data/lib/comma/extractor.rb +33 -2
  30. data/lib/comma/generator.rb +4 -8
  31. data/lib/comma/header_extractor.rb +10 -19
  32. data/lib/comma/mongoid.rb +1 -3
  33. data/lib/comma/object.rb +37 -11
  34. data/lib/comma/options.rb +18 -0
  35. data/lib/comma/rails/renderer.rb +21 -0
  36. data/lib/comma/rails.rb +16 -0
  37. data/lib/comma/version.rb +1 -1
  38. data/lib/comma.rb +4 -43
  39. data/spec/comma/comma_spec.rb +93 -34
  40. data/spec/comma/data_extractor_spec.rb +3 -3
  41. data/spec/comma/header_extractor_spec.rb +1 -1
  42. data/spec/comma/options_spec.rb +63 -0
  43. data/spec/comma/rails/active_record_spec.rb +0 -1
  44. data/spec/controllers/users_controller_spec.rb +13 -28
  45. data/spec/gemspec_spec.rb +11 -0
  46. data/spec/support/comma_class_helper.rb +12 -0
  47. data/spec/support/csv_expectation_helper.rb +14 -0
  48. metadata +92 -23
  49. data/gemfiles/active6.0.6.gemfile.lock +0 -136
  50. data/gemfiles/active6.1.7.6.gemfile.lock +0 -135
  51. data/gemfiles/active7.0.8.gemfile.lock +0 -133
  52. data/gemfiles/active7.1.3.gemfile.lock +0 -139
  53. data/gemfiles/rails6.0.6.gemfile.lock +0 -268
  54. data/gemfiles/rails6.1.7.6.gemfile.lock +0 -271
  55. data/gemfiles/rails7.0.8.gemfile.lock +0 -266
  56. data/gemfiles/rails7.1.3.gemfile.lock +0 -289
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebf4796fc5d10dd1af410b86168ed9bce3083bd8182606000062ef572771d0a8
4
- data.tar.gz: d3b8bfbe4556b3954e3d1205dcff81390b9491546581771d0cfb1862df3fc45b
3
+ metadata.gz: ce045c478a147d8f8cf10cb1228ba6ba810b459ad61cfccd9b91f4029dad9c73
4
+ data.tar.gz: 75da1697c5e08af1a1bae4567780d2b9e0a535de873b910f6760479be5a3b519
5
5
  SHA512:
6
- metadata.gz: a365b5fa5f0e9ae46dfb0ac321fd1e4838589fd162ae16fb21e9262ac7e367c0d99031a52e5c8f02259bdf9e38f82ee72dd5b3e498c9ee90e8de4aed6073e6c6
7
- data.tar.gz: dd02d4b771ea4139820ba98b6f99d0d4fe2ff6626fade1d47e4190c5d6640d06079347195b4c224a499b2cc8c380a74261d1e0b292c59a98d50a1e3583927397
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.
@@ -5,12 +5,21 @@ jobs:
5
5
  strategy:
6
6
  fail-fast: false
7
7
  matrix:
8
- ruby: ['3.0', '3.1', '3.2', '3.3', '3.4']
9
- gemfile: ['active6.0.6', 'active6.1.7.6', 'active7.0.8', 'active7.1.3', 'rails6.0.6', 'rails6.1.7.6', 'rails7.0.8', 'rails7.1.3']
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: 11
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: 3
128
+ # Offense count: 2
128
129
  Style/MissingRespondToMissing:
129
130
  Exclude:
130
131
  - 'lib/comma/data_extractor.rb'
131
- - 'lib/comma/header_extractor.rb'
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 'rails6.0.6' do
4
- gem 'rails', '6.0.6'
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 'active6.0.6' do
10
- gem 'activesupport', '6.0.6'
11
- gem 'activerecord', '6.0.6'
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 'rails6.1.7.6' do
15
- gem 'rails', '6.1.7.6'
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 'active6.1.7.6' do
21
- gem 'activesupport', '6.1.7.6'
22
- gem 'activerecord', '6.1.7.6'
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 'rails7.0.8' do
26
- gem 'rails', '7.0.8'
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 'active7.0.8' do
31
- gem 'activesupport', '7.0.8'
32
- gem 'activerecord', '7.0.8'
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 'rails7.1.3' do
36
- gem 'rails', '7.1.3'
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 'active7.1.3' do
41
- gem 'activesupport', '7.1.3'
42
- gem 'activerecord', '7.1.3'
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.9.0)
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.1.3)
12
- activesupport (= 7.1.3)
13
- activesupport (7.1.3)
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.0.2)
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
- minitest (>= 5.1)
21
- mutex_m
22
- tzinfo (~> 2.0)
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
- concurrent-ruby (1.3.6)
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
- mini_portile2 (2.8.5)
46
- minitest (5.14.4)
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.5.0)
58
- rspec-core (~> 3.5.0)
59
- rspec-expectations (~> 3.5.0)
60
- rspec-mocks (~> 3.5.0)
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.5.2)
66
- rspec-support (~> 3.5.0)
67
- rspec-expectations (3.5.0)
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.5.0)
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.5.0)
79
+ rspec-mocks (3.13.1)
74
80
  diff-lcs (>= 1.2.0, < 2.0)
75
- rspec-support (~> 3.5.0)
76
- rspec-support (3.5.0)
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 (1.7.0)
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
- minitest (= 5.14.4)
128
+ logger
129
+ minitest (= 5.27.0)
120
130
  mutex_m
131
+ ostruct
121
132
  rake (~> 13.0.1)
122
- rspec (~> 3.5.0)
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 to generate comma seperated value (CSV) for Ruby objects like ActiveRecord and Array
3
+ A library for generating comma-separated values (CSV) from Ruby objects, arrays, and supported ORM collections.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/comma.svg)](http://badge.fury.io/rb/comma) [![Build Status](https://github.com/comma-csv/comma/actions/workflows/build.yml/badge.svg)](https://github.com/comma-csv/comma/actions/workflows/build.yml) [![Code Climate](https://codeclimate.com/github/comma-csv/comma.svg)](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 to use ruby 3.0 or later. If you generate CSV from ActiveRecord models, you need to have ActiveRecord 6.0 or later.
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', '~> 4.8.0'
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', git: 'git://github.com/comma-csv/comma.git'
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
- See [this page](https://github.com/comma-csv/comma/wiki) for usages.
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
- To run the test suite across multiple gem file sets, we're using [Appraisal](https://github.com/thoughtbot/appraisal), use the following commands:
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
- $ bundle exec appraisal install
41
- $ bundle exec appraisal rake spec
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 fore details.
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 'minitest', '5.14.4'
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.5.0']
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", "6.0.6"
10
- gem "activerecord", "6.0.6"
9
+ gem "activesupport", "7.1.6"
10
+ gem "activerecord", "7.1.6"
11
11
 
12
12
  gemspec :path => "../"