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.
- checksums.yaml +7 -0
- data/.editorconfig +15 -0
- data/.github/workflows/ci.yml +48 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rubocop.yml +311 -0
- data/.simplecov +14 -0
- data/.yardstick.yml +36 -0
- data/Appraisals +22 -0
- data/CHANGELOG.md +51 -0
- data/CONTRIBUTING.md +86 -0
- data/Gemfile +5 -1
- data/LICENSE +21 -0
- data/README.md +435 -52
- data/Rakefile +71 -0
- data/UPGRADING.md +188 -0
- data/gemfiles/rails_7_2.gemfile +11 -0
- data/gemfiles/rails_7_2.gemfile.lock +268 -0
- data/gemfiles/rails_8_0.gemfile +11 -0
- data/gemfiles/rails_8_0.gemfile.lock +271 -0
- data/gemfiles/rails_8_1.gemfile +11 -0
- data/gemfiles/rails_8_1.gemfile.lock +269 -0
- data/gemfiles/standalone.gemfile +8 -0
- data/gemfiles/standalone.gemfile.lock +197 -0
- data/importu.gemspec +41 -22
- data/lib/importu/backends/active_record.rb +171 -0
- data/lib/importu/backends/middleware/duplicate_manager_proxy.rb +41 -0
- data/lib/importu/backends/middleware/enforce_allowed_actions.rb +52 -0
- data/lib/importu/backends/middleware.rb +11 -0
- data/lib/importu/backends.rb +103 -0
- data/lib/importu/config_dsl.rb +381 -0
- data/lib/importu/converter_context.rb +94 -0
- data/lib/importu/converters.rb +119 -64
- data/lib/importu/definition.rb +23 -0
- data/lib/importu/duplicate_manager.rb +88 -0
- data/lib/importu/exceptions.rb +135 -4
- data/lib/importu/importer.rb +183 -96
- data/lib/importu/record.rb +138 -102
- data/lib/importu/sources/csv.rb +122 -0
- data/lib/importu/sources/json.rb +106 -0
- data/lib/importu/sources/ruby.rb +46 -0
- data/lib/importu/sources/xml.rb +133 -0
- data/lib/importu/sources.rb +13 -0
- data/lib/importu/summary.rb +277 -0
- data/lib/importu/version.rb +3 -1
- data/lib/importu.rb +45 -9
- data/spec/fixtures/books-duplicates/README.md +7 -0
- data/spec/fixtures/books-duplicates/infile.csv +7 -0
- data/spec/fixtures/books-duplicates/model.json +23 -0
- data/spec/fixtures/books-duplicates/summary.json +10 -0
- data/spec/fixtures/books-valid/README.md +13 -0
- data/spec/fixtures/books-valid/infile.csv +4 -0
- data/spec/fixtures/books-valid/infile.json +23 -0
- data/spec/fixtures/books-valid/infile.xml +21 -0
- data/spec/fixtures/books-valid/model.json +23 -0
- data/spec/fixtures/books-valid/record.json +26 -0
- data/spec/fixtures/books-valid/summary.json +8 -0
- data/spec/fixtures/source-empty-file/infile.csv +0 -0
- data/spec/fixtures/source-empty-file/infile.json +0 -0
- data/spec/fixtures/source-empty-file/infile.xml +0 -0
- data/spec/fixtures/source-empty-records/infile.csv +3 -0
- data/spec/fixtures/source-empty-records/infile.json +1 -0
- data/spec/fixtures/source-empty-records/infile.xml +6 -0
- data/spec/fixtures/source-malformed/infile.csv +1 -0
- data/spec/fixtures/source-malformed/infile.json +1 -0
- data/spec/fixtures/source-malformed/infile.xml +3 -0
- data/spec/fixtures/source-no-records/infile.csv +1 -0
- data/spec/fixtures/source-no-records/infile.json +1 -0
- data/spec/fixtures/source-no-records/infile.xml +3 -0
- data/spec/lib/importu/backends/active_record_spec.rb +150 -0
- data/spec/lib/importu/backends/middleware/duplicate_manager_proxy_spec.rb +70 -0
- data/spec/lib/importu/backends/middleware/enforce_allowed_actions_spec.rb +70 -0
- data/spec/lib/importu/backends_spec.rb +170 -0
- data/spec/lib/importu/converters_spec.rb +184 -141
- data/spec/lib/importu/definition_spec.rb +248 -0
- data/spec/lib/importu/duplicate_manager_spec.rb +92 -0
- data/spec/lib/importu/exceptions_spec.rb +69 -16
- data/spec/lib/importu/import_context_spec.rb +199 -0
- data/spec/lib/importu/importer_spec.rb +95 -0
- data/spec/lib/importu/integration_spec.rb +221 -0
- data/spec/lib/importu/record_spec.rb +130 -80
- data/spec/lib/importu/sources/csv_spec.rb +29 -0
- data/spec/lib/importu/sources/importer_source_examples.rb +175 -0
- data/spec/lib/importu/sources/json_spec.rb +29 -0
- data/spec/lib/importu/sources/ruby_spec.rb +102 -0
- data/spec/lib/importu/sources/xml_spec.rb +70 -0
- data/spec/lib/importu/summary_spec.rb +186 -0
- data/spec/spec_helper.rb +91 -7
- data/spec/support/active_record.rb +20 -0
- data/spec/support/book_importer.rb +31 -0
- data/spec/support/dummy_backend.rb +50 -0
- data/spec/support/fixtures_helper.rb +43 -0
- data/spec/support/matchers/delegate_matcher.rb +14 -8
- metadata +173 -100
- data/lib/importu/core_ext/array/deep_freeze.rb +0 -7
- data/lib/importu/core_ext/deep_freeze.rb +0 -3
- data/lib/importu/core_ext/hash/deep_freeze.rb +0 -7
- data/lib/importu/core_ext/object/deep_freeze.rb +0 -6
- data/lib/importu/core_ext.rb +0 -3
- data/lib/importu/dsl.rb +0 -127
- data/lib/importu/importer/csv.rb +0 -52
- data/lib/importu/importer/json.rb +0 -45
- data/lib/importu/importer/xml.rb +0 -55
- data/spec/factories/importer.rb +0 -12
- data/spec/factories/importer_record.rb +0 -13
- data/spec/factories/json_importer.rb +0 -14
- data/spec/factories/xml_importer.rb +0 -12
- data/spec/lib/importu/dsl_spec.rb +0 -26
- data/spec/lib/importu/importer/json_spec.rb +0 -37
- 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
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
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.
|