csv_wizard 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f0ac1e6eeb36ef75a527c25437cc2f7e89c32536eb6eda719ffff65a92ab5d79
4
+ data.tar.gz: f82fb939fd275974c66eb8567dccc4c2d75a8db9d748f5ca1a7dd82ec99dbef5
5
+ SHA512:
6
+ metadata.gz: 7999f9a582bdaece1d3f0618a7ce16514ceb4a5a9a333e19fb9dc03809f4968e26c6b1c1ac4f18916a74070c2c731bf8e1722f14dec16b644bcb2cca77fd3b71
7
+ data.tar.gz: c52a76153ffc66d1a5c2d6a254c2a03fc7d692a1ccb6ffc7d30751930603e470a89c3514f39d028fa5b2e4841ff9fc6bbad1de5a0a7ff93882d4a3f138321ba1
@@ -0,0 +1,26 @@
1
+ name: Ruby CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@78c01b705fd9d5ad960d432d3a0cfa341d50e410 # v1.179.1
18
+ with:
19
+ ruby-version: 3.2.0
20
+ bundler-cache: true
21
+
22
+ - name: Install dependencies
23
+ run: bundle install
24
+
25
+ - name: Run RSpec tests
26
+ run: bundle exec rspec
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 mrmalvi
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # CsvWizard
2
+
3
+ CsvWizard is a Ruby gem for **importing CSV files into Rails models** with support for:
4
+
5
+ - Required fields
6
+ - Default values
7
+ - Row-level validation
8
+ - Pre- and post-import hooks
9
+ - Logging with Rails.logger or the Logging gem
10
+
11
+ It is designed to simplify CSV imports in Rails applications.
12
+
13
+ ---
14
+
15
+ ## Installation
16
+
17
+ Add this line to your Rails application's Gemfile:
18
+
19
+ ```ruby
20
+ gem "csv_wizard"
21
+ ```
22
+
23
+ Then run:
24
+
25
+ ```bash
26
+ bundle install
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Requirements
32
+
33
+ - Ruby >= 2.6
34
+ - Rails >= 6.0
35
+ ---
36
+
37
+ ## Usage
38
+
39
+ ### 1. Basic Import
40
+
41
+ ```ruby
42
+ mappings = {
43
+ "Name" => :name,
44
+ "Email" => :email,
45
+ "Age" => :age
46
+ }
47
+
48
+ file_path = "path/to/users.csv"
49
+
50
+ CsvWizard::ImportJob.new.perform("User", mappings, file_path)
51
+ ```
52
+
53
+ - `model_name`: The name of the Rails model to import to.
54
+ - `mappings`: A hash mapping CSV column headers to model attributes.
55
+ - `file_path`: Path to the CSV file.
56
+
57
+ ---
58
+
59
+ ### 2. Using Pre- and Post-Processing Hooks
60
+
61
+ ```ruby
62
+ importer = Object.new.extend(CsvWizard::Importer)
63
+ importer.map_csv_to(User) do
64
+ column "Name", required: true
65
+ column "Email", required: true
66
+ column "Age", default: 18
67
+ end
68
+
69
+ failed_rows = importer.import(
70
+ "path/to/users.csv",
71
+ before_import: ->(attrs) { attrs[:name] = attrs[:name].strip },
72
+ after_import: ->(record) { puts "Imported #{record.id}" }
73
+ )
74
+
75
+ puts failed_rows.inspect
76
+ ```
77
+
78
+ ---
79
+
80
+ ### 3. Handling Logging
81
+
82
+ - By default, `CsvWizard::ImportJob` logs via **Rails.logger** if Rails is defined.
83
+ - Otherwise, it uses the **Logging gem**.
84
+
85
+ ```ruby
86
+ require "logging"
87
+ logger = Logging.logger["CsvWizard"]
88
+ logger.level = :info
89
+
90
+ CsvWizard::ImportJob.new.perform("User", mappings, file_path, logger: logger)
91
+ ```
92
+
93
+ ---
94
+
95
+ ### 4. Error Reporting
96
+
97
+ You can use `CsvWizard::ErrorReporter` to generate a full report of errors:
98
+
99
+ ```ruby
100
+ reporter = CsvWizard::ErrorReporter.new
101
+ reporter.add_errors(["Name is required", "Email is invalid"], 2)
102
+ reporter.add_errors(["Age must be numeric"], 5)
103
+
104
+ puts reporter.full_report
105
+ # Output:
106
+ # Line 2: Name is required, Email is invalid
107
+ # Line 5: Age must be numeric
108
+ ```
109
+
110
+ ---
111
+
112
+ ### 5. Example CSV
113
+
114
+ ```csv
115
+ Name,Email,Age
116
+ John Doe,john@example.com,30
117
+ Jane Smith,,25
118
+ Bob, bob@example.com,
119
+ ```
120
+
121
+ - Missing values trigger **validation errors**.
122
+ - Default values are applied if configured.
123
+
124
+ ---
125
+
126
+ ## Contributing
127
+
128
+ 1. Fork the repository
129
+ 2. Create a feature branch (`git checkout -b feature/my_feature`)
130
+ 3. Commit your changes (`git commit -am 'Add feature'`)
131
+ 4. Push to the branch (`git push origin feature/my_feature`)
132
+ 5. Open a Pull Request
133
+
134
+ ---
135
+
136
+ ## License
137
+
138
+ MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.
139
+
140
+ ---
141
+
142
+ ## Metadata for RubyGems
143
+
144
+ - Homepage: [https://github.com/username/csv_wizard](https://github.com/username/csv_wizard)
145
+ - Source code: [https://github.com/username/csv_wizard](https://github.com/username/csv_wizard)
146
+ - Required Ruby version: >= 2.6
147
+ - Required Rails version: >= 6.0
148
+
149
+ ---
150
+
151
+ ## Summary
152
+
153
+ CsvWizard makes CSV imports **easy, reliable, and Rails-friendly**.
154
+ It supports validations, defaults, hooks, and error reporting to make importing data **safe and efficient**.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,21 @@
1
+ module CsvWizard
2
+ class ErrorReporter
3
+ attr_reader :row_errors
4
+
5
+ def initialize
6
+ @row_errors = {}
7
+ end
8
+
9
+ def add_errors(errors, line_number)
10
+ @row_errors[line_number] = errors
11
+ end
12
+
13
+ def any?
14
+ row_errors.any?
15
+ end
16
+
17
+ def full_report
18
+ row_errors.map { |line, errors| "Line #{line}: #{errors.join(", ")}" }.join("\n")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ module CsvWizard
2
+ class ImportJob < ActiveJob::Base
3
+ queue_as :default
4
+
5
+ def perform(model_name, mappings, file_path, logger: nil)
6
+ model = model_name.constantize
7
+ mapper = Mapper.new
8
+ mappings.each { |csv, attr| mapper.map(csv, to: attr) }
9
+
10
+ importer = Object.new.extend(Importer)
11
+ importer.instance_variable_set(:@mapper, mapper)
12
+ importer.instance_variable_set(:@model_class, model)
13
+
14
+ failed_rows = importer.import(file_path)
15
+
16
+ # Conditional logger
17
+ log =
18
+ if logger
19
+ logger
20
+ elsif defined?(Rails) && Rails.respond_to?(:logger)
21
+ Rails.logger
22
+ else
23
+ require "logging"
24
+ Logging.logger['CsvWizard'].tap { |l| l.level = :info }
25
+ end
26
+
27
+ if failed_rows.any?
28
+ log.error("[CSV Import] Errors: #{failed_rows.inspect}")
29
+ else
30
+ log.info("[CSV Import] Completed successfully.")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ module CsvWizard
2
+ module Importer
3
+ def map_csv_to(model_class, &block)
4
+ @mapper = Mapper.new
5
+ instance_eval(&block)
6
+ @model_class = model_class
7
+ end
8
+
9
+ def column(name, required: false, default: nil)
10
+ @mapper.map(name, to: name.downcase.to_sym, required: required, default: default)
11
+ end
12
+
13
+ def import(file_path, before_import: nil, after_import: nil)
14
+ failed_rows = []
15
+
16
+ CSV.foreach(file_path, headers: true).with_index(2) do |row, idx|
17
+ processor = RowProcessor.new(@model_class, @mapper, row.to_h, idx, before_import: before_import, after_import: after_import)
18
+ success = processor.save
19
+ failed_rows << { line: idx, row: row.to_h, errors: processor.errors } unless success
20
+ end
21
+
22
+ failed_rows
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module CsvWizard
2
+ class Mapper
3
+ attr_reader :mappings
4
+
5
+ def initialize
6
+ @mappings = {}
7
+ @required_columns = []
8
+ @defaults = {}
9
+ end
10
+
11
+ def map(csv_column, to:, required: false, default: nil)
12
+ @mappings[csv_column] = to
13
+ @required_columns << csv_column if required
14
+ @defaults[to] = default if default
15
+ end
16
+
17
+ def required_columns
18
+ @required_columns
19
+ end
20
+
21
+ def defaults
22
+ @defaults
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,47 @@
1
+ module CsvWizard
2
+ class RowProcessor
3
+ attr_reader :model, :mapper, :row, :line_number, :errors
4
+
5
+ def initialize(model, mapper, row, line_number, defaults: {}, before_import: nil, after_import: nil)
6
+ @model = model
7
+ @mapper = mapper
8
+ @row = row
9
+ @line_number = line_number
10
+ @errors = []
11
+ @defaults = defaults
12
+ @before_import = before_import
13
+ @after_import = after_import
14
+ end
15
+
16
+ def save
17
+ missing = mapper.required_columns.select { |col| row[col].nil? || row[col].strip.empty? }
18
+ unless missing.empty?
19
+ @errors = missing.map { |col| "#{col} is required" }
20
+ return false
21
+ end
22
+
23
+ attrs = mapped_attributes
24
+ attrs = @before_import.call(attrs) if @before_import
25
+
26
+ record = model.new(attrs)
27
+ if record.save
28
+ @after_import.call(record) if @after_import
29
+ true
30
+ else
31
+ @errors = record.errors.full_messages
32
+ false
33
+ end
34
+ rescue => e
35
+ @errors = ["Exception: #{e.message}"]
36
+ false
37
+ end
38
+
39
+ private
40
+
41
+ def mapped_attributes
42
+ mapper.mappings.each_with_object({}) do |(csv_col, attr), hash|
43
+ hash[attr] = row[csv_col] || @defaults[attr] || mapper.defaults[attr]
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CsvWizard
4
+ VERSION = "0.1.0"
5
+ end
data/lib/csv_wizard.rb ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ require "csv"
3
+ require "active_support"
4
+ require "active_record"
5
+ require "active_job"
6
+
7
+ require "csv_wizard/importer"
8
+ require "csv_wizard/mapper"
9
+ require "csv_wizard/row_processor"
10
+ require "csv_wizard/error_reporter"
11
+ require "csv_wizard/import_job"
12
+
13
+ module CsvWizard
14
+ class Error < StandardError; end
15
+ # Fucked up
16
+ end
@@ -0,0 +1,4 @@
1
+ module CsvWizard
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: csv_wizard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - mrmalvi
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activerecord
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '6.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: activejob
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '6.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '6.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: csv
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '3.2'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '3.2'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.12'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.12'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec-rails
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '6.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '6.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: sqlite3
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '2.1'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '2.1'
110
+ - !ruby/object:Gem::Dependency
111
+ name: logging
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '2.3'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '2.3'
124
+ description: CsvWizard provides a simple framework to import CSV files into Rails
125
+ models. It supports required fields, default values, row-level validation, and hooks
126
+ for pre- and post-processing.
127
+ email:
128
+ - malviyak00@gmail.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - ".github/workflows/rubyonrails.yml"
134
+ - ".ruby-version"
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - lib/csv_wizard.rb
139
+ - lib/csv_wizard/error_reporter.rb
140
+ - lib/csv_wizard/import_job.rb
141
+ - lib/csv_wizard/importer.rb
142
+ - lib/csv_wizard/mapper.rb
143
+ - lib/csv_wizard/row_processor.rb
144
+ - lib/csv_wizard/version.rb
145
+ - sig/csv_wizard.rbs
146
+ homepage: https://github.com/mrmalvi/csv_wizard
147
+ licenses:
148
+ - MIT
149
+ metadata:
150
+ homepage_uri: https://github.com/mrmalvi/csv_wizard
151
+ source_code_uri: https://github.com/mrmalvi/csv_wizard
152
+ changelog_uri: https://github.com/mrmalvi/csv_wizard/blob/main/CHANGELOG.md
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: 2.6.0
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubygems_version: 3.6.9
168
+ specification_version: 4
169
+ summary: 'CsvWizard: Import and process CSV files into ActiveRecord models.'
170
+ test_files: []