importu 0.1.0 → 0.2.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 (110) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +15 -0
  3. data/.github/workflows/ci.yml +48 -0
  4. data/.gitignore +4 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +311 -0
  7. data/.simplecov +14 -0
  8. data/.yardstick.yml +36 -0
  9. data/Appraisals +22 -0
  10. data/CHANGELOG.md +51 -0
  11. data/CONTRIBUTING.md +86 -0
  12. data/Gemfile +5 -1
  13. data/LICENSE +21 -0
  14. data/README.md +435 -52
  15. data/Rakefile +71 -0
  16. data/UPGRADING.md +188 -0
  17. data/gemfiles/rails_7_2.gemfile +11 -0
  18. data/gemfiles/rails_7_2.gemfile.lock +268 -0
  19. data/gemfiles/rails_8_0.gemfile +11 -0
  20. data/gemfiles/rails_8_0.gemfile.lock +271 -0
  21. data/gemfiles/rails_8_1.gemfile +11 -0
  22. data/gemfiles/rails_8_1.gemfile.lock +269 -0
  23. data/gemfiles/standalone.gemfile +8 -0
  24. data/gemfiles/standalone.gemfile.lock +197 -0
  25. data/importu.gemspec +41 -22
  26. data/lib/importu/backends/active_record.rb +171 -0
  27. data/lib/importu/backends/middleware/duplicate_manager_proxy.rb +41 -0
  28. data/lib/importu/backends/middleware/enforce_allowed_actions.rb +52 -0
  29. data/lib/importu/backends/middleware.rb +11 -0
  30. data/lib/importu/backends.rb +103 -0
  31. data/lib/importu/config_dsl.rb +381 -0
  32. data/lib/importu/converter_context.rb +94 -0
  33. data/lib/importu/converters.rb +119 -64
  34. data/lib/importu/definition.rb +23 -0
  35. data/lib/importu/duplicate_manager.rb +88 -0
  36. data/lib/importu/exceptions.rb +135 -4
  37. data/lib/importu/importer.rb +183 -96
  38. data/lib/importu/record.rb +138 -102
  39. data/lib/importu/sources/csv.rb +122 -0
  40. data/lib/importu/sources/json.rb +106 -0
  41. data/lib/importu/sources/ruby.rb +46 -0
  42. data/lib/importu/sources/xml.rb +133 -0
  43. data/lib/importu/sources.rb +13 -0
  44. data/lib/importu/summary.rb +277 -0
  45. data/lib/importu/version.rb +3 -1
  46. data/lib/importu.rb +45 -9
  47. data/spec/fixtures/books-duplicates/README.md +7 -0
  48. data/spec/fixtures/books-duplicates/infile.csv +7 -0
  49. data/spec/fixtures/books-duplicates/model.json +23 -0
  50. data/spec/fixtures/books-duplicates/summary.json +10 -0
  51. data/spec/fixtures/books-valid/README.md +13 -0
  52. data/spec/fixtures/books-valid/infile.csv +4 -0
  53. data/spec/fixtures/books-valid/infile.json +23 -0
  54. data/spec/fixtures/books-valid/infile.xml +21 -0
  55. data/spec/fixtures/books-valid/model.json +23 -0
  56. data/spec/fixtures/books-valid/record.json +26 -0
  57. data/spec/fixtures/books-valid/summary.json +8 -0
  58. data/spec/fixtures/source-empty-file/infile.csv +0 -0
  59. data/spec/fixtures/source-empty-file/infile.json +0 -0
  60. data/spec/fixtures/source-empty-file/infile.xml +0 -0
  61. data/spec/fixtures/source-empty-records/infile.csv +3 -0
  62. data/spec/fixtures/source-empty-records/infile.json +1 -0
  63. data/spec/fixtures/source-empty-records/infile.xml +6 -0
  64. data/spec/fixtures/source-malformed/infile.csv +1 -0
  65. data/spec/fixtures/source-malformed/infile.json +1 -0
  66. data/spec/fixtures/source-malformed/infile.xml +3 -0
  67. data/spec/fixtures/source-no-records/infile.csv +1 -0
  68. data/spec/fixtures/source-no-records/infile.json +1 -0
  69. data/spec/fixtures/source-no-records/infile.xml +3 -0
  70. data/spec/lib/importu/backends/active_record_spec.rb +150 -0
  71. data/spec/lib/importu/backends/middleware/duplicate_manager_proxy_spec.rb +70 -0
  72. data/spec/lib/importu/backends/middleware/enforce_allowed_actions_spec.rb +70 -0
  73. data/spec/lib/importu/backends_spec.rb +170 -0
  74. data/spec/lib/importu/converters_spec.rb +184 -141
  75. data/spec/lib/importu/definition_spec.rb +248 -0
  76. data/spec/lib/importu/duplicate_manager_spec.rb +92 -0
  77. data/spec/lib/importu/exceptions_spec.rb +69 -16
  78. data/spec/lib/importu/import_context_spec.rb +199 -0
  79. data/spec/lib/importu/importer_spec.rb +95 -0
  80. data/spec/lib/importu/integration_spec.rb +221 -0
  81. data/spec/lib/importu/record_spec.rb +130 -80
  82. data/spec/lib/importu/sources/csv_spec.rb +29 -0
  83. data/spec/lib/importu/sources/importer_source_examples.rb +175 -0
  84. data/spec/lib/importu/sources/json_spec.rb +29 -0
  85. data/spec/lib/importu/sources/ruby_spec.rb +102 -0
  86. data/spec/lib/importu/sources/xml_spec.rb +70 -0
  87. data/spec/lib/importu/summary_spec.rb +186 -0
  88. data/spec/spec_helper.rb +91 -7
  89. data/spec/support/active_record.rb +20 -0
  90. data/spec/support/book_importer.rb +31 -0
  91. data/spec/support/dummy_backend.rb +50 -0
  92. data/spec/support/fixtures_helper.rb +43 -0
  93. data/spec/support/matchers/delegate_matcher.rb +14 -8
  94. metadata +173 -100
  95. data/lib/importu/core_ext/array/deep_freeze.rb +0 -7
  96. data/lib/importu/core_ext/deep_freeze.rb +0 -3
  97. data/lib/importu/core_ext/hash/deep_freeze.rb +0 -7
  98. data/lib/importu/core_ext/object/deep_freeze.rb +0 -6
  99. data/lib/importu/core_ext.rb +0 -3
  100. data/lib/importu/dsl.rb +0 -127
  101. data/lib/importu/importer/csv.rb +0 -52
  102. data/lib/importu/importer/json.rb +0 -45
  103. data/lib/importu/importer/xml.rb +0 -55
  104. data/spec/factories/importer.rb +0 -12
  105. data/spec/factories/importer_record.rb +0 -13
  106. data/spec/factories/json_importer.rb +0 -14
  107. data/spec/factories/xml_importer.rb +0 -12
  108. data/spec/lib/importu/dsl_spec.rb +0 -26
  109. data/spec/lib/importu/importer/json_spec.rb +0 -37
  110. data/spec/lib/importu/importer/xml_spec.rb +0 -14
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 919b992ad441913c1a9de0cb80f81640f95be6031133908b14b792ce3831bf15
4
+ data.tar.gz: 555d5534282aab463239f6d8323c78083d75e2d861e0627b502948a7c04a7a1c
5
+ SHA512:
6
+ metadata.gz: b950525fd0c9998a6b6897debb9658ffb0d9c1ed7360aa0e870264daaa17184c6b5ffcdbee2991bd680f5838b427516dffb4cfd8a24fdff688f99e48c2197870
7
+ data.tar.gz: eb2fda5952a2a18baba157a55799d06d934babad3922b4f829ccbeba854e69f46eea71ebcd58f7990977d7fa3c21ca9fe677faaf1767b851e2938b4c25c445d7
data/.editorconfig ADDED
@@ -0,0 +1,15 @@
1
+ # EditorConfig helps maintain consistent coding styles
2
+ # https://editorconfig.org
3
+
4
+ root = true
5
+
6
+ [*]
7
+ charset = utf-8
8
+ end_of_line = lf
9
+ indent_style = space
10
+ indent_size = 2
11
+ insert_final_newline = true
12
+ trim_trailing_whitespace = true
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
@@ -0,0 +1,48 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby: ["3.1", "3.2", "3.3", "4.0"]
16
+ appraisal: [standalone, rails-7-2, rails-8-0, rails-8-1]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Ruby
22
+ uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: ${{ matrix.ruby }}
25
+ bundler-cache: true
26
+
27
+ - name: Install appraisal dependencies
28
+ run: bundle exec appraisal ${{ matrix.appraisal }} bundle install
29
+
30
+ - name: Run tests
31
+ run: bundle exec appraisal ${{ matrix.appraisal }} rspec
32
+
33
+ lint:
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+
38
+ - name: Set up Ruby
39
+ uses: ruby/setup-ruby@v1
40
+ with:
41
+ ruby-version: "3.3"
42
+ bundler-cache: true
43
+
44
+ - name: Run RuboCop
45
+ run: bundle exec rubocop
46
+
47
+ - name: Verify YARD documentation coverage
48
+ run: bundle exec rake yardstick
data/.gitignore CHANGED
@@ -1,3 +1,7 @@
1
1
  Gemfile.lock
2
2
  .bundle/
3
+ .yardoc/
4
+ coverage/
5
+ doc/
6
+ spec/examples.txt
3
7
  vendor/bundle
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --order random
data/.rubocop.yml ADDED
@@ -0,0 +1,311 @@
1
+ plugins:
2
+ - rubocop-performance
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 3.1
6
+ NewCops: enable
7
+ SuggestExtensions: false
8
+ Exclude:
9
+ - "gemfiles/**/*"
10
+ - "vendor/**/*"
11
+
12
+ # Match existing style: allow flexible layout
13
+ Layout/HashAlignment:
14
+ Enabled: false
15
+
16
+ Layout/SpaceInsideBlockBraces:
17
+ Enabled: false
18
+
19
+ Layout/FirstArrayElementIndentation:
20
+ Enabled: false
21
+
22
+ Layout/FirstHashElementIndentation:
23
+ Enabled: false
24
+
25
+ Layout/MultilineOperationIndentation:
26
+ Enabled: false
27
+
28
+ Layout/MultilineHashBraceLayout:
29
+ Enabled: false
30
+
31
+ Layout/MultilineMethodCallIndentation:
32
+ Enabled: false
33
+
34
+ Layout/EmptyLinesAroundBlockBody:
35
+ Enabled: false
36
+
37
+ Layout/EmptyLinesAroundClassBody:
38
+ Enabled: false
39
+
40
+ Layout/EmptyLinesAroundModuleBody:
41
+ Enabled: false
42
+
43
+ Layout/EmptyLineAfterMagicComment:
44
+ Enabled: false
45
+
46
+ Layout/EmptyLineAfterGuardClause:
47
+ Enabled: false
48
+
49
+ # Allow one-liner methods for simple accessors
50
+ Style/SingleLineMethods:
51
+ Enabled: false
52
+
53
+ # Match existing style: allow compact empty class bodies
54
+ Style/EmptyMethod:
55
+ EnforcedStyle: compact
56
+
57
+ # Allow trailing comma in multiline for cleaner diffs
58
+ Style/TrailingCommaInArrayLiteral:
59
+ Enabled: false
60
+
61
+ Style/TrailingCommaInHashLiteral:
62
+ Enabled: false
63
+
64
+ # Match existing style: prefer double quotes
65
+ Style/StringLiterals:
66
+ EnforcedStyle: double_quotes
67
+
68
+ Style/StringLiteralsInInterpolation:
69
+ EnforcedStyle: double_quotes
70
+
71
+ # Match existing style: allow proc { } and -> { } based on context
72
+ Style/Lambda:
73
+ EnforcedStyle: literal
74
+
75
+ # Allow raising with string message directly
76
+ Style/RaiseArgs:
77
+ Enabled: false
78
+
79
+ # Disable guard clause enforcement (existing style is mixed)
80
+ Style/GuardClause:
81
+ Enabled: false
82
+
83
+ # Match existing style: allow explicit return in short methods
84
+ Style/RedundantReturn:
85
+ Enabled: false
86
+
87
+ # Match existing style: use && and || over and/or
88
+ Style/AndOr:
89
+ EnforcedStyle: always
90
+
91
+ # Match existing style: allow conditional assignment
92
+ Style/ConditionalAssignment:
93
+ Enabled: false
94
+
95
+ # Allow rescuing StandardError explicitly
96
+ Style/RescueStandardError:
97
+ Enabled: false
98
+
99
+ # Match existing style: allow `private def` syntax
100
+ Style/AccessModifierDeclarations:
101
+ EnforcedStyle: inline
102
+
103
+ # Disable overly pedantic cops
104
+ Style/Documentation:
105
+ Enabled: false
106
+
107
+ Style/DocumentationMethod:
108
+ Enabled: false
109
+
110
+ # Match existing style: allow compact module/class definitions
111
+ Style/ClassAndModuleChildren:
112
+ Enabled: false
113
+
114
+ # Match existing style: allow rescue modifier for simple cases
115
+ Style/RescueModifier:
116
+ Enabled: false
117
+
118
+ # Match existing style: allow string concatenation
119
+ Style/StringConcatenation:
120
+ Enabled: false
121
+
122
+ Style/LineEndConcatenation:
123
+ Enabled: false
124
+
125
+ # Match existing style: allow word arrays with quotes
126
+ Style/WordArray:
127
+ Enabled: false
128
+
129
+ Style/SymbolArray:
130
+ Enabled: false
131
+
132
+ Style/PercentLiteralDelimiters:
133
+ Enabled: false
134
+
135
+ # Match existing style: allow hash rocket for alignment
136
+ Style/HashSyntax:
137
+ EnforcedShorthandSyntax: never
138
+
139
+ Style/RedundantSelf:
140
+ Enabled: true
141
+
142
+ # Allow redundant :: for top-level constants (clearer intent)
143
+ Style/RedundantConstantBase:
144
+ Enabled: false
145
+
146
+ # Allow block comments for larger sections
147
+ Style/BlockComments:
148
+ Enabled: false
149
+
150
+ # Allow multiline if modifier
151
+ Style/MultilineIfModifier:
152
+ Enabled: false
153
+
154
+ # Allow has_key? for clarity
155
+ Style/PreferredHashMethods:
156
+ Enabled: false
157
+
158
+ # Allow empty string interpolation
159
+ Style/EmptyStringInsideInterpolation:
160
+ Enabled: false
161
+
162
+ # Allow multiline ternary (matches existing style)
163
+ Style/MultilineTernaryOperator:
164
+ Enabled: false
165
+
166
+ Style/HashConversion:
167
+ Enabled: true
168
+
169
+ # Allow parallel assignment (matches existing style)
170
+ Style/ParallelAssignment:
171
+ Enabled: false
172
+
173
+ # Allow Proc.new style (matches existing style)
174
+ Style/Proc:
175
+ Enabled: false
176
+
177
+ # Allow existing except pattern
178
+ Style/HashExcept:
179
+ Enabled: false
180
+
181
+ Style/RedundantBegin:
182
+ Enabled: true
183
+
184
+ # Keep existing self assignment patterns
185
+ Style/RedundantSelfAssignmentBranch:
186
+ Enabled: false
187
+
188
+ # Gemspec settings
189
+ Gemspec/DevelopmentDependencies:
190
+ Enabled: false
191
+
192
+ Gemspec/RequireMFA:
193
+ Enabled: false
194
+
195
+ Gemspec/OrderedDependencies:
196
+ Enabled: true
197
+
198
+ Style/ExpandPathArguments:
199
+ Enabled: true
200
+
201
+ Lint/UnusedBlockArgument:
202
+ Enabled: true
203
+
204
+ Lint/UnusedMethodArgument:
205
+ AllowUnusedKeywordArguments: true
206
+
207
+ Lint/UselessAssignment:
208
+ Enabled: true
209
+
210
+ # Allow 1.times for spec clarity
211
+ Lint/UselessTimes:
212
+ Enabled: false
213
+
214
+ Lint/AmbiguousBlockAssociation:
215
+ Exclude:
216
+ - "spec/**/*"
217
+
218
+ # Allow percent string arrays with quotes
219
+ Lint/PercentStringArray:
220
+ Enabled: false
221
+
222
+ # Metrics
223
+ Metrics/BlockLength:
224
+ Max: 60
225
+ Exclude:
226
+ - "spec/**/*"
227
+ - "*.gemspec"
228
+ - "lib/importu/converters.rb" # Converter definitions are intentionally grouped
229
+
230
+ Metrics/MethodLength:
231
+ Max: 25
232
+ Exclude:
233
+ - "lib/importu/converters.rb" # Converter definitions are intentionally grouped
234
+
235
+ Metrics/ClassLength:
236
+ Max: 150
237
+
238
+ Metrics/ModuleLength:
239
+ Max: 150
240
+
241
+ Metrics/AbcSize:
242
+ Enabled: false
243
+
244
+ Metrics/CyclomaticComplexity:
245
+ Enabled: false
246
+
247
+ Metrics/PerceivedComplexity:
248
+ Enabled: false
249
+
250
+ # Naming
251
+ Naming/BlockForwarding:
252
+ Enabled: false
253
+
254
+ # Allow memoization with different variable names (intentional pattern)
255
+ Naming/MemoizedInstanceVariableName:
256
+ Enabled: false
257
+
258
+ Performance/RedundantMerge:
259
+ Enabled: true
260
+
261
+ # Allow existing case indentation style
262
+ Layout/CaseIndentation:
263
+ Enabled: false
264
+
265
+ # Allow assignment in condition (idiomatic Ruby pattern)
266
+ Lint/AssignmentInCondition:
267
+ Enabled: false
268
+
269
+ # Allow missing super in custom exceptions (intentional)
270
+ Lint/MissingSuper:
271
+ Enabled: false
272
+
273
+ # Allow empty blocks in specs (intentional DSL usage)
274
+ Lint/EmptyBlock:
275
+ Enabled: false
276
+
277
+ # Keep existing argument forwarding style
278
+ Style/ArgumentsForwarding:
279
+ Enabled: false
280
+
281
+ # Allow if modifier to be optional
282
+ Style/IfUnlessModifier:
283
+ Enabled: false
284
+
285
+ # Allow existing next/break style
286
+ Style/Next:
287
+ Enabled: false
288
+
289
+ # Allow trailing commas in method calls (existing style)
290
+ Style/TrailingCommaInArguments:
291
+ Enabled: false
292
+
293
+ # Allow semicolons in one-liners (existing test style)
294
+ Style/Semicolon:
295
+ Enabled: false
296
+
297
+ # Allow flexible hash brace spacing
298
+ Layout/SpaceInsideHashLiteralBraces:
299
+ Enabled: false
300
+
301
+ # Enforce frozen_string_literal
302
+ Style/FrozenStringLiteralComment:
303
+ Enabled: true
304
+
305
+ # Allow simple heredoc delimiters
306
+ Naming/HeredocDelimiterNaming:
307
+ Enabled: false
308
+
309
+ # Allow slight overruns in specs
310
+ Layout/LineLength:
311
+ Max: 105
data/.simplecov ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ require "simplecov"
3
+
4
+ # Make each appraisal have a unique simplecov name. Needed to allow merging
5
+ # of results since each appraisal may only run a subset of relevant specs.
6
+ gemfile = ENV.fetch("BUNDLE_GEMFILE", "system")
7
+ SimpleCov.command_name "appraisal:#{File.basename(gemfile, ".gemfile")}"
8
+
9
+ SimpleCov.start do
10
+ add_filter do |source_file|
11
+ # Filter out all files that are not in the gem's lib/ directory
12
+ source_file.filename.start_with?("#{SimpleCov.root}/lib/") == false
13
+ end
14
+ end
data/.yardstick.yml ADDED
@@ -0,0 +1,36 @@
1
+ ---
2
+ # Minimum documentation coverage required for verifications to pass
3
+ threshold: 70
4
+
5
+ # Specify if the coverage summary should be displayed
6
+ verbose: true
7
+
8
+ # List of paths to measure. List may contain paths to files or globs
9
+ path:
10
+ - lib/**/*.rb
11
+
12
+ # Specify if the threshold should match the coverage
13
+ require_exact_threshold: false
14
+
15
+ # Rules that get applied to each source code file
16
+ rules:
17
+ # Restrict method summary to 80 characters. This is annoying because where
18
+ # else are you supposed to be able to describe the behavior of the method
19
+ # or instructions on replacing it with your own implementation?
20
+ Summary::Length:
21
+ enabled: false
22
+ exclude: []
23
+
24
+ # Summary should not end with a period. This is a YARD convention but
25
+ # conflicts with natural documentation style when using multiple sentences.
26
+ Summary::Delimiter:
27
+ enabled: false
28
+ exclude: []
29
+
30
+ # Require summary to appear on one line in comments. Even if we left the
31
+ # 80 character restriction, this would be annoying because methods and
32
+ # attributes are often indented so you lose quite a few characters from
33
+ # a possible description without breaking the 80 character terminal width.
34
+ Summary::SingleLine:
35
+ enabled: false
36
+ exclude: []
data/Appraisals ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ appraise "standalone" do
3
+ # w/o any frameworks
4
+ end
5
+
6
+ appraise "rails-7-2" do
7
+ gem "sqlite3", "~> 2.0"
8
+ gem "database_cleaner-active_record"
9
+ gem "activerecord", "~> 7.2.0"
10
+ end
11
+
12
+ appraise "rails-8-0" do
13
+ gem "sqlite3", "~> 2.0"
14
+ gem "database_cleaner-active_record"
15
+ gem "activerecord", "~> 8.0.0"
16
+ end
17
+
18
+ appraise "rails-8-1" do
19
+ gem "sqlite3", "~> 2.0"
20
+ gem "database_cleaner-active_record"
21
+ gem "activerecord", "~> 8.1.0"
22
+ end
data/CHANGELOG.md ADDED
@@ -0,0 +1,51 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0] - 2026-01-28
9
+
10
+ ### Breaking Changes
11
+
12
+ See [UPGRADING.md](UPGRADING.md) for detailed migration steps.
13
+
14
+ - Require Ruby >= 3.1
15
+ - Add csv and bigdecimal as gem dependencies (no longer default gems in Ruby 3.4+)
16
+ - Replace multi_json with stdlib json (JSON::ParserError instead of MultiJson::DecodeError)
17
+ - Drop Rails 4.x and 5.x support; minimum is now Rails 7.2
18
+
19
+ ### Added
20
+
21
+ - Add CONTRIBUTING.md with development setup and architecture overview
22
+ - Add explicit `backend: :auto` option for discoverable backend auto-detection
23
+
24
+ ### Changed
25
+
26
+ - `require "importu"` now loads the Importer class (previously required explicit `require "importu/importer"`)
27
+ - Update README with Quick Start, Sources, and Allowed Actions sections
28
+ - Improve YARD documentation coverage (55% to 70%+)
29
+ - Improve MissingField error to include available fields from source data
30
+ - Improve error messages when create/update actions are not allowed (now includes fix guidance)
31
+ - Document before_save as a backend hook that backends may choose to honor
32
+ - Extract AR-specific error normalization from Summary into ActiveRecord backend
33
+ - InvalidRecord now accepts normalized_message for aggregation-friendly error messages
34
+
35
+ ### Fixed
36
+
37
+ - Fix datetime converter to avoid Rails deprecation warning
38
+ - Fix validation_errors not being passed to InvalidRecord in ActiveRecord backend
39
+ - Remove phantom require for nonexistent backends/ruby file
40
+ - Add explicit require for delegate stdlib
41
+ - Add explicit require for date stdlib
42
+ - Add explicit require for forwardable stdlib
43
+ - Fix ConverterStub to properly initialize Proc superclass
44
+ - Fix keyword argument splatting throughout codebase
45
+ - Fix CSV source to create fresh reader on each rows call
46
+ - Fix JSON source to handle empty documents
47
+ - Fix ActiveRecord backend for Rails 7+ errors API
48
+
49
+ ## [0.1.0] - 2017-09-25
50
+
51
+ Initial release.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,86 @@
1
+ # Contributing to Importu
2
+
3
+ ## Development Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/dhedlund/importu.git
7
+ cd importu
8
+ bundle install
9
+ bundle exec appraisal install
10
+ ```
11
+
12
+ ## Running Checks
13
+
14
+ The preflight task runs specs, RuboCop, and YARD coverage in one command:
15
+
16
+ ```bash
17
+ bundle exec rake preflight
18
+ ```
19
+
20
+ To run checks individually:
21
+
22
+ ```bash
23
+ bundle exec rspec # Test suite (standalone, no Rails)
24
+ bundle exec rubocop # Code style
25
+ bundle exec rake yardstick # Documentation coverage
26
+ ```
27
+
28
+ To test against specific Rails versions:
29
+
30
+ ```bash
31
+ bundle exec appraisal rspec # All Rails versions
32
+ bundle exec appraisal standalone rspec # No Rails
33
+ bundle exec appraisal rails-8-1 rspec # Specific version
34
+ ```
35
+
36
+ ## Pull Requests
37
+
38
+ Before submitting a PR, run the preflight checks:
39
+
40
+ ```bash
41
+ bundle exec rake preflight
42
+ ```
43
+
44
+ This runs the test suite, RuboCop, and YARD coverage verification.
45
+
46
+ Also:
47
+
48
+ 1. Add tests for new functionality
49
+ 2. Update CHANGELOG.md under `[Unreleased]` for user-facing changes
50
+
51
+ ## Architecture Overview
52
+
53
+ The codebase is organized around a few core concepts:
54
+
55
+ - **Sources** (`lib/importu/sources/`) - Parse input formats (CSV, JSON, XML, Ruby hashes). Each source provides a `rows` enumerator.
56
+
57
+ - **Backends** (`lib/importu/backends/`) - Persist records to a data store. The ActiveRecord backend is included; others can be added.
58
+
59
+ - **Middleware** (`lib/importu/backends/middleware/`) - Wrap backends to add cross-cutting behavior like duplicate detection and action enforcement.
60
+
61
+ - **Converters** (`lib/importu/converters.rb`) - Transform raw field values into typed data. Built-in converters handle strings, integers, dates, booleans, etc.
62
+
63
+ - **Summary** (`lib/importu/summary.rb`) - Aggregates import results (created, updated, invalid counts and error details).
64
+
65
+ The importer DSL (`lib/importu/config_dsl.rb`) ties these together, letting you declare fields, converters, and backend configuration in a readable format.
66
+
67
+ ## Adding a New Source
68
+
69
+ Implement a class with:
70
+ - `initialize(infile, **options)` - Accept input and source-specific options
71
+ - `rows` - Return an enumerator yielding hashes (one per record)
72
+
73
+ See `lib/importu/sources/csv.rb` for an example.
74
+
75
+ ## Adding a New Backend
76
+
77
+ Implement a class with:
78
+ - `self.supported_by_model?(model)` - Return true if this backend handles the model
79
+ - `initialize(model:, finder_fields:, **options)` - Accept configuration
80
+ - `find(record)` - Look up existing record, return nil if not found
81
+ - `create(record)` - Create new record, return `[status, object]`
82
+ - `update(record, object)` - Update existing record, return `[status, object]`
83
+
84
+ Register it with `Importu::Backends.registry.register(:name, YourBackend)`.
85
+
86
+ See `lib/importu/backends/active_record.rb` for an example.
data/Gemfile CHANGED
@@ -1,4 +1,8 @@
1
- source 'http://rubygems.org'
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
2
3
 
3
4
  # Specify your gem's dependencies in importu.gemspec
4
5
  gemspec
6
+
7
+ gem "irb" # Required by YARD on Ruby 4.0+
8
+ gem "yardstick", "~> 0.9", require: false
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Daniel Hedlund
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.