importance 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3ca5129019f5ae56f87a0f1fdecb67536010d39d93365f1ae46cb5bd41332ab
4
- data.tar.gz: eaa6be85de8f2d28a895d5a16a194b45e1bb46f01621a7b6b5a6ccae978ce9c3
3
+ metadata.gz: e0df06e38c78ddb548f82dd7108a1ff45ac679ad21c8b6549ccba6107cbf37b3
4
+ data.tar.gz: 1cd461bbaf521ab3d4358faa4accf85553d47fbb9941b8462d7e4342a154b45e
5
5
  SHA512:
6
- metadata.gz: f4e87f855969ab9da45721363e2b1d7bd17a9685381f10579ffa9de6df5b3b6267c78ac92ac6405d872926b50d87d09be3fa8b6a0419282255595b43ded63d63
7
- data.tar.gz: 67c9fe0b5f55256f657ac1e8a4c437a3a84e8412d712f28ee829fcc2b9ab1b937d9920a9817eb28b183e09aa5c61643138f12e10b3bf6560651a136ea5cc92e7
6
+ metadata.gz: 855023fbe7167ff911683626c1e3f0d2eb0ae058f261f56c7f2e9d96afb01dc40e8a7e192f62fb1f47c79e824a971b8b46dee408d0b68370fc304695a0e66020
7
+ data.tar.gz: 0b98ee22a71bb61a5cf7a218795eb934b418317dde08ebd22bd267757d7648c531d0ae0e432ec5e4746146ca54562620c2d397c005473fc237aa19cde80f8b23
data/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # Importance
2
- Short description and motivation.
3
2
 
4
- ## Usage
5
- How to use my plugin.
3
+ Importance allows users to select which columns of an Excel or CSV file should be
4
+ imported and which ones should be ignored. This makes it possible to upload
5
+ files with arbitrary headers, as long as all necessary data is contained.
6
6
 
7
7
  ## Installation
8
+
8
9
  Add this line to your application's Gemfile:
9
10
 
10
11
  ```ruby
@@ -21,12 +22,191 @@ Or install it yourself as:
21
22
  $ gem install importance
22
23
  ```
23
24
 
24
- ## Releasing
25
+ Generate the initializer:
26
+
27
+ ```bash
28
+ rails generate importance:install
29
+ ```
30
+
31
+ This will create a configuration file at `config/initializers/importance.rb` and mount
32
+ the engine in `config/routes.rb`.
33
+
34
+ ## Usage
35
+
36
+ Importance allows you to define one or more `importers`, where each allows you
37
+ to define a different treatment of the data you uploaded.
38
+
39
+ Define the uploaders in an initializer, for example `config/initializers/importance.rb`.
40
+ You can define as many importers as you want.
41
+
42
+ Each importer can define callbacks that control what is done before the import, during the import,
43
+ after the import and if any errors occurred.
44
+
45
+ | Callback | Usage |
46
+ |---|---|
47
+ | `setup` | Code to be run once before the import. Initialization of an error array, loading of required parent records |
48
+ | `perform` | The actual import logic. This block receives a collection of `records` for which you write the logic to import. It may be called multiple times if the dataset is large. |
49
+ | `teardown` | Code to be run one after the import. Cleanup, flushing data to a log. |
50
+ | `error` | Callback if any unhandled exception occurred. Recives the exception as a parameter. |
51
+
52
+ ```ruby
53
+ Importance.configure do |config|
54
+ config.set_layout :bootstrap
55
+
56
+ config.register_importer :students do |importer|
57
+ importer.attribute :first_name, [ "Vorname", "vorname", "vname", "fname", "l_vorname" ]
58
+ importer.attribute :last_name, [ "Nachname", "nachname", "nname", "lname", "l_nachname" ]
59
+ importer.attribute :email, [ "E-Mail", "email", "mail", "l_email" ]
60
+
61
+ importer.batch_size 500
62
+
63
+ # Setup code runs before import
64
+ importer.setup do
65
+ @total_count = 0
66
+ @errors = []
67
+ @school = School.find(params[:school_id]) # Access to params
68
+ end
69
+
70
+ # Main import logic has access to instance variables from setup
71
+ importer.perform do |records|
72
+ @total_count += records.size
73
+
74
+ records.each do |record|
75
+ begin
76
+ Student.create(
77
+ first_name: record[:first_name],
78
+ last_name: record[:last_name],
79
+ email: record[:email],
80
+ created_by: current_user.id, # Access to current_user
81
+ school_id: @school.id # Access to instance var from setup
82
+ )
83
+ rescue => e
84
+ @errors << { record: record, message: e.message }
85
+ end
86
+ end
87
+ end
88
+
89
+ # Teardown code runs after import
90
+ importer.teardown do
91
+ # Can access both controller context and setup variables
92
+ ActivityLog.create(
93
+ user: current_user,
94
+ action: "import",
95
+ details: "Imported #{@total_count} students with #{@errors.size} errors"
96
+ )
97
+
98
+ # Display errors to the user if any occurred
99
+ if @errors.any?
100
+ # Store errors in database to avoid session size limits (4KB)
101
+ import_log = ImportLog.create!(
102
+ user: current_user,
103
+ total_records: @total_count,
104
+ error_count: @errors.size,
105
+ errors_data: @errors.to_json
106
+ )
107
+
108
+ flash[:alert] = "Import completed with #{@errors.size} errors. Please review the details below."
109
+ redirect_to rails_routes.import_log_path(import_log)
110
+ else
111
+ redirect_to rails_routes.students_path, notice: "Successfully imported #{@total_count} students."
112
+ end
113
+ end
114
+
115
+ # Controller code to run after the import
116
+ importer.error do |exception|
117
+ redirect_to rails_routes.root_path, alert: "Import failed: #{exception.message}"
118
+ end
119
+ end
120
+ end
121
+ ```
122
+
123
+ Add a file upload form to your application. You can use libraries like
124
+ Dropzone.js to create drag and drop interfaces, and you can style them just
125
+ as you wish. Make sure the path stays, and it is a multipart form.
25
126
 
26
- Run `rake build` to build the gem. Then run `rake release` to push it to rubygems.org.
127
+ The gem supports Excel files (.xlsx, .xls) and CSV files (.csv). For CSV files,
128
+ the first row is automatically treated as the header row.
129
+
130
+ ```erb
131
+ <%= form_with url: importance.submit_path(importer: :students), multipart: true do |form| %>
132
+ <%= form.file_field :file, accept: ".xlsx,.xls,.csv" %>
133
+ <%= form.submit "Submit" %>
134
+ <% end %>
135
+ ```
136
+
137
+ ### Displaying Import Errors
138
+
139
+ If you collect errors in the `@errors` variable during import (as shown in the example above), you can display them to users in your views. Since sessions have a 4KB limit, errors are stored in the database:
140
+
141
+ First, create an ImportLog model to store the errors:
142
+
143
+ ```ruby
144
+ # app/models/import_log.rb
145
+ class ImportLog < ApplicationRecord
146
+ belongs_to :user
147
+
148
+ def errors_array
149
+ JSON.parse(errors_data || '[]')
150
+ end
151
+ end
152
+ ```
153
+
154
+ ```ruby
155
+ # Migration
156
+ class CreateImportLogs < ActiveRecord::Migration[7.0]
157
+ def change
158
+ create_table :import_logs do |t|
159
+ t.references :user, null: false, foreign_key: true
160
+ t.integer :total_records
161
+ t.integer :error_count
162
+ t.text :errors_data
163
+ t.timestamps
164
+ end
165
+ end
166
+ end
167
+ ```
168
+
169
+ Then display the errors in your view:
170
+
171
+ ```erb
172
+ <!-- In your import_logs/show.html.erb view -->
173
+ <div class="alert alert-warning">
174
+ <h4>Import Errors</h4>
175
+ <p>The following <%= @import_log.error_count %> records could not be imported:</p>
176
+
177
+ <% errors = @import_log.errors_array %>
178
+ <% if errors.size > 50 %>
179
+ <p><em>Showing first 50 errors (total: <%= errors.size %>)</em></p>
180
+ <% errors = errors.first(50) %>
181
+ <% end %>
182
+
183
+ <ul>
184
+ <% errors.each do |error| %>
185
+ <li>
186
+ <strong>Row data:</strong> <%= error["record"].inspect %><br>
187
+ <strong>Error:</strong> <%= error["message"] %>
188
+ </li>
189
+ <% end %>
190
+ </ul>
191
+ </div>
192
+ ```
193
+
194
+ ## Customization
195
+
196
+ The following translations can be overriden by the application
197
+
198
+ ```yml
199
+ en:
200
+ importance:
201
+ use_column_as: Use column as
202
+ ignore: Ignore
203
+ import: Import
204
+ ```
27
205
 
28
206
  ## Contributing
207
+
29
208
  Contribution directions go here.
30
209
 
31
210
  ## License
211
+
32
212
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,4 +1,6 @@
1
1
  module Importance
2
- class ApplicationController < ActionController::Base
2
+ class ApplicationController < ::ApplicationController
3
+ include Rails.application.routes.url_helpers
4
+ helper Rails.application.routes.url_helpers
3
5
  end
4
6
  end
@@ -0,0 +1,134 @@
1
+ require "xsv"
2
+ require "csv"
3
+
4
+ module Importance
5
+ class ImportsController < ApplicationController
6
+ # Form submission target. Persist the file and redirect to the mapping page.
7
+ def submit
8
+ upload = params[:file]
9
+
10
+ raise ArgumentError, "Upload cannot be nil" if upload.nil?
11
+
12
+ upload_extension = File.extname(upload.original_filename).downcase
13
+ supported_extensions = [ ".xlsx", ".xls", ".csv" ]
14
+
15
+ raise ArgumentError, "Unsupported file format. Please upload Excel (.xlsx, .xls) or CSV (.csv) files." unless supported_extensions.include?(upload_extension)
16
+
17
+ system_tmp_dir = Dir.tmpdir
18
+ upload_path = upload.tempfile.path
19
+ persist_filename = "#{SecureRandom.uuid}#{upload_extension}"
20
+
21
+ persist_path = File.join(system_tmp_dir, persist_filename)
22
+
23
+ raise ArgumentError, "File does not exist at #{upload_path}" if !File.exist?(upload_path)
24
+
25
+ FileUtils.mv(upload_path, persist_path)
26
+
27
+ session[:path] = persist_path
28
+ session[:importer] = params[:importer].to_sym
29
+
30
+ redirect_to map_path
31
+ end
32
+
33
+ # Mapping page. Load headers and samples, display the form.
34
+ def map
35
+ importer = Importance.configuration.importers[session[:importer].to_sym]
36
+
37
+ raise ArgumentError, "Importer cannot be nil" if importer.nil?
38
+
39
+ if csv_file?
40
+ csv_data = CSV.read(session[:path], headers: true)
41
+ @file_headers = csv_data.headers
42
+ @samples = csv_data.first(5).map(&:to_h)
43
+ else
44
+ workbook = Xsv.open(session[:path], parse_headers: true)
45
+ worksheet = workbook.first
46
+ @file_headers = worksheet.first.keys
47
+ @samples = worksheet.first(5)
48
+ end
49
+
50
+ @importer_attributes = importer.attributes
51
+ @layout = "Importance::#{Importance.configuration.layout.to_s.camelize}Layout".constantize
52
+ end
53
+
54
+ # Import page. Load the file according to the mapping and import it.
55
+ def import
56
+ importer = Importance.configuration.importers[session[:importer].to_sym]
57
+ mappings = params[:mappings]
58
+
59
+ raise ArgumentError, "Mapping cannot be nil" if mappings.nil?
60
+
61
+ if importer.setup_callback
62
+ instance_exec(&importer.setup_callback)
63
+ end
64
+
65
+ begin
66
+ records_to_import = []
67
+
68
+ each_processed_row(mappings) do |record|
69
+ records_to_import << record
70
+
71
+ if importer.batch && records_to_import.size >= importer.batch
72
+ instance_exec(records_to_import, &importer.perform_callback)
73
+ records_to_import = []
74
+ end
75
+ end
76
+
77
+ if records_to_import.any?
78
+ instance_exec(records_to_import, &importer.perform_callback)
79
+ end
80
+
81
+ if importer.teardown_callback
82
+ instance_exec(&importer.teardown_callback)
83
+ else
84
+ redirect_to session[:redirect_url] || root_path, notice: "Import completed."
85
+ end
86
+
87
+ rescue => e
88
+ if importer.error_callback
89
+ instance_exec(e, &importer.error_callback)
90
+ end
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def csv_file?
97
+ File.extname(session[:path]).downcase == ".csv"
98
+ end
99
+
100
+ def each_processed_row(mappings)
101
+ if csv_file?
102
+ CSV.foreach(session[:path], headers: true) do |row|
103
+ record = process_row(row.to_h, mappings)
104
+ next if record.empty? || record.values.all? { |v| v.nil? || v.to_s.strip.empty? }
105
+ yield record
106
+ end
107
+ else
108
+ workbook = Xsv.open(session[:path], parse_headers: true)
109
+ worksheet = workbook.first
110
+ worksheet.each do |row|
111
+ record = process_row(row, mappings)
112
+ next if record.empty? || record.values.all? { |v| v.nil? || v.to_s.strip.empty? }
113
+ yield record
114
+ end
115
+ end
116
+ end
117
+
118
+ def process_row(row, mappings)
119
+ record = {}
120
+ row.each do |row_header, value|
121
+ attribute = mappings.permit!.to_h.find { |column_name, attribute_name| column_name == row_header }
122
+ next if attribute.nil?
123
+ attribute = attribute[1]
124
+ next if attribute.nil? || attribute == ""
125
+ record[attribute.to_sym] = value
126
+ end
127
+ record
128
+ end
129
+
130
+ def rails_routes
131
+ ::Rails.application.routes.url_helpers
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,19 @@
1
+ module Importance
2
+ class BlankLayout
3
+ def self.select_class
4
+ ""
5
+ end
6
+
7
+ def self.submit_class
8
+ ""
9
+ end
10
+
11
+ def self.table_class
12
+ ""
13
+ end
14
+
15
+ def self.wrapper_class
16
+ ""
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Importance
2
+ class BootstrapLayout < BlankLayout
3
+ def self.select_class
4
+ "form-select"
5
+ end
6
+
7
+ def self.submit_class
8
+ "btn btn-primary"
9
+ end
10
+
11
+ def self.table_class
12
+ "table"
13
+ end
14
+
15
+ def self.wrapper_class
16
+ "table-responsive"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ module Importance
2
+ class Header
3
+ def self.match_attributes_to_headers(importer_attributes, file_headers)
4
+ attribute_mappings = {}
5
+
6
+ importer_attributes.each do |attribute|
7
+ best_header = nil
8
+ best_similarity = 0
9
+
10
+ file_headers.each do |header|
11
+ attribute.labels.each do |label|
12
+ if header == label
13
+ best_header = header
14
+ best_similarity = 1.0
15
+ break # No need to check further if an exact match is found
16
+ end
17
+ distance = DidYouMean::Levenshtein.distance(header, label)
18
+ percentage = distance / header.length.to_f
19
+ similarity = 1 - percentage
20
+ if similarity > best_similarity
21
+ best_similarity = similarity
22
+ best_header = header
23
+ end
24
+ end
25
+ end
26
+
27
+ # Only assign if similarity is reasonable (> 0.5) and header isn't already taken
28
+ if best_similarity > 0.5 && !attribute_mappings.values.include?(best_header)
29
+ attribute_mappings[attribute.key] = best_header
30
+ end
31
+ end
32
+
33
+ attribute_mappings
34
+ end
35
+
36
+ def self.default_value_for_header(file_header, attribute_mappings)
37
+ attribute_mappings.key(file_header) || ""
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,82 @@
1
+ <%= form_with url: importance.import_path, method: :post do |form| %>
2
+ <div class="importance-wrapper <%= @layout.wrapper_class %>">
3
+ <%= form.submit t('importance.import'), class: @layout.submit_class %>
4
+ <table class="importance-table <%= @layout.table_class %>">
5
+ <thead>
6
+ <tr>
7
+ <% @file_headers.each do |file_header| %>
8
+ <th>
9
+ <%= t('importance.use_column_as') %>
10
+ <%
11
+ attribute_mappings = Importance::Header.match_attributes_to_headers(@importer_attributes, @file_headers)
12
+ default_value = Importance::Header.default_value_for_header(file_header, attribute_mappings)
13
+ %>
14
+ <%= form.select "mappings[#{file_header}]",
15
+ options_for_select(
16
+ [[t('importance.ignore'), ""]] +
17
+ @importer_attributes.map { |attr| [attr.labels.first, attr.key] },
18
+ default_value
19
+ ), {}, class: @layout.select_class %>
20
+ </th>
21
+ <% end %>
22
+ </tr>
23
+ <tr>
24
+ <% @file_headers.each do |file_header| %>
25
+ <th><%= file_header %></th>
26
+ <% end %>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <% @samples.each do |sample| %>
31
+ <tr>
32
+ <% @file_headers.each do |file_header| %>
33
+ <td><%= sample[file_header] %></td>
34
+ <% end %>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ </table>
39
+ </div>
40
+ <% end %>
41
+
42
+ <script>
43
+ const table = document.querySelector('.importance-table');
44
+ if (table) {
45
+ const selects = table.querySelectorAll('thead tr:first-child th select');
46
+ const dataRows = table.querySelectorAll('tbody tr, thead tr');
47
+
48
+ const updateIgnoredColumns = () => {
49
+ dataRows.forEach(row => {
50
+ Array.from(row.children).forEach(cell => {
51
+ if (cell.tagName === 'TD' || cell.tagName == 'TH') {
52
+ cell.classList.remove('ignored');
53
+ }
54
+ });
55
+ });
56
+
57
+ selects.forEach(selectElement => {
58
+ if (selectElement.value === "") {
59
+ const headerCell = selectElement.closest('th');
60
+ if (headerCell && headerCell.parentElement) {
61
+ const columnIndex = Array.from(headerCell.parentElement.children).indexOf(headerCell);
62
+
63
+ if (columnIndex !== -1) {
64
+ dataRows.forEach(row => {
65
+ const cell = row.children[columnIndex];
66
+ if (cell && (cell.tagName === 'TD' || cell.tagName === 'TH')) {
67
+ cell.classList.add('ignored');
68
+ }
69
+ });
70
+ }
71
+ }
72
+ }
73
+ });
74
+ };
75
+
76
+ updateIgnoredColumns();
77
+
78
+ selects.forEach(selectElement => {
79
+ selectElement.addEventListener('change', updateIgnoredColumns);
80
+ });
81
+ }
82
+ </script>
@@ -0,0 +1,5 @@
1
+ de:
2
+ importance:
3
+ use_column_as: Spalte verwenden als
4
+ ignore: Ignorieren
5
+ import: Importieren
@@ -0,0 +1,5 @@
1
+ en:
2
+ importance:
3
+ use_column_as: Use column as
4
+ ignore: Ignore
5
+ import: Import
@@ -0,0 +1,5 @@
1
+ fr:
2
+ importance:
3
+ use_column_as: Utiliser la colonne comme
4
+ ignore: Ignorer
5
+ import: Importer
@@ -0,0 +1,5 @@
1
+ it:
2
+ importance:
3
+ use_column_as: Utilizzare come
4
+ ignore: Ignora
5
+ import: Importare
data/config/routes.rb CHANGED
@@ -1,2 +1,5 @@
1
1
  Importance::Engine.routes.draw do
2
+ post "/importance/submit", to: "imports#submit", as: :submit
3
+ get "/importance/map", to: "imports#map", as: :map
4
+ post "/importance/import", to: "imports#import", as: :import
2
5
  end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ The importance:install generator creates an initializer file at
3
+ config/initializers/importance.rb with a basic configuration.
4
+
5
+ Examples:
6
+ rails generate importance:install
7
+
8
+ This will create:
9
+ config/initializers/importance.rb
@@ -0,0 +1,17 @@
1
+ module Importance
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("templates", __dir__)
5
+
6
+ desc "Creates an Importance initializer for your application"
7
+
8
+ def copy_initializer
9
+ template "importance.rb", "config/initializers/importance.rb"
10
+ end
11
+
12
+ def add_route
13
+ route 'mount Importance::Engine, at: "/"'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,68 @@
1
+ # Importance gem configuration
2
+ # Generated on <%= Date.today.strftime("%Y-%m-%d") %>
3
+
4
+ Importance.configure do |config|
5
+ # Set the layout to be used for the form. Can be :default or :bootstrap
6
+ config.set_layout :bootstrap
7
+
8
+ # Example importer configuration:
9
+ config.register_importer :students do |importer|
10
+ # Define required attributes with possible column names
11
+ importer.attribute :first_name, [ "First Name", "FirstName", "fname" ]
12
+ importer.attribute :last_name, [ "Last Name", "LastName", "lname" ]
13
+ importer.attribute :email, [ "Email", "E-Mail", "email", "mail" ]
14
+
15
+ # Process records in batches of this size
16
+ importer.batch_size 500
17
+
18
+ # Setup runs before the import begins
19
+ importer.setup do
20
+ @total_count = 0
21
+ @errors = []
22
+
23
+ # Access controller context and request info
24
+ @current_user_id = current_user.id
25
+ @import_source = request.remote_ip
26
+
27
+ # Initialize any resources needed for the import
28
+ @logger = Logger.new(Rails.root.join("log/imports.log"))
29
+ @logger.info("Starting import by #{current_user.email}")
30
+ end
31
+
32
+ # Main import logic
33
+ importer.on_complete do |records|
34
+ @total_count += records.size
35
+
36
+ records.each do |record|
37
+ begin
38
+ Student.create!(
39
+ first_name: record[:first_name],
40
+ last_name: record[:last_name],
41
+ email: record[:email],
42
+ created_by: @current_user_id
43
+ )
44
+ rescue => e
45
+ @errors << { record: record, message: e.message }
46
+ @logger.error("Error importing #{record}: #{e.message}")
47
+ end
48
+ end
49
+ end
50
+
51
+ # Teardown runs after the import finishes
52
+ importer.teardown do
53
+ # Log import results
54
+ @logger.info("Import completed: #{@total_count} records processed with #{@errors.size} errors")
55
+
56
+ # Create an audit record
57
+ ImportAudit.create!(
58
+ user_id: @current_user_id,
59
+ records_count: @total_count,
60
+ errors_count: @errors.size,
61
+ source: @import_source
62
+ )
63
+
64
+ # Clean up resources
65
+ @logger.close
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,67 @@
1
+ require "ostruct"
2
+
3
+ module Importance
4
+ class Configuration
5
+ attr_accessor :importers, :layout
6
+
7
+ def initialize
8
+ @importers = {}
9
+ @layout = :blank
10
+ end
11
+
12
+ def register_importer(name, &block)
13
+ @importers[name] = Importer.new(name, &block)
14
+ end
15
+
16
+ def set_layout(name)
17
+ @layout = name
18
+ end
19
+ end
20
+
21
+ class Importer
22
+ attr_reader :name, :attributes, :batch, :setup_callback, :perform_callback, :teardown_callback, :error_callback
23
+
24
+ def initialize(name, &block)
25
+ @name = name
26
+ @attributes = []
27
+ @setup_callback = nil
28
+ @perform_callback = nil
29
+ @teardown_callback = nil
30
+ @error_callback = nil
31
+ @batch = false
32
+ instance_eval(&block) if block_given?
33
+ end
34
+
35
+ def attribute(key, labels)
36
+ @attributes << OpenStruct.new(key: key, labels: labels)
37
+ end
38
+
39
+ def batch_size(size)
40
+ @batch = size
41
+ end
42
+
43
+ def setup(&block)
44
+ @setup_callback = block
45
+ end
46
+
47
+ def perform(&block)
48
+ @perform_callback = block
49
+ end
50
+
51
+ def teardown(&block)
52
+ @teardown_callback = block
53
+ end
54
+
55
+ def error(&block)
56
+ @error_callback = block
57
+ end
58
+ end
59
+
60
+ def self.configure
61
+ yield(configuration)
62
+ end
63
+
64
+ def self.configuration
65
+ @configuration ||= Configuration.new
66
+ end
67
+ end
@@ -1,3 +1,3 @@
1
1
  module Importance
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/importance.rb CHANGED
@@ -1,6 +1,25 @@
1
1
  require "importance/version"
2
2
  require "importance/engine"
3
+ require "importance/configuration"
4
+ require "generators/importance/install/install_generator" if defined?(Rails::Generators)
3
5
 
4
6
  module Importance
5
- # Your code goes here...
7
+ class << self
8
+ attr_writer :configuration
9
+
10
+ def configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ # Alias for YourImporterGemName.configuration
15
+ def config
16
+ configuration
17
+ end
18
+
19
+ # Yields the singleton configuration object to a block.
20
+ # Used in the Rails initializer.
21
+ def configure
22
+ yield(configuration)
23
+ end
24
+ end
6
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: importance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lukas_Skywalker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-23 00:00:00.000000000 Z
11
+ date: 2025-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,18 +16,63 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 7.1.1
19
+ version: 7.0.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 7.1.1
27
- description: Importance allows users to upload spreadsheets in any format and lets
28
- them select how the columns should be imported.
26
+ version: 7.0.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: xsv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ostruct
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: debug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.10'
69
+ description: Importance is a Rails engine that allows users to upload Excel and CSV
70
+ files and interactively map columns to model attributes. It handles files with arbitrary
71
+ headers by letting users select which columns to import and which to ignore, with
72
+ support for flexible attribute mapping, batch processing, error handling, and customizable
73
+ import workflows.
29
74
  email:
30
- - LukasSkywalker@users.noreply.github.com
75
+ - git@lukasdiener.ch
31
76
  executables: []
32
77
  extensions: []
33
78
  extra_rdoc_files: []
@@ -35,16 +80,27 @@ files:
35
80
  - MIT-LICENSE
36
81
  - README.md
37
82
  - Rakefile
38
- - app/assets/config/importance_manifest.js
39
83
  - app/assets/stylesheets/importance/application.css
40
84
  - app/controllers/importance/application_controller.rb
85
+ - app/controllers/importance/imports_controller.rb
41
86
  - app/helpers/importance/application_helper.rb
42
87
  - app/jobs/importance/application_job.rb
43
88
  - app/mailers/importance/application_mailer.rb
44
89
  - app/models/importance/application_record.rb
45
- - app/views/layouts/importance/application.html.erb
90
+ - app/models/importance/blank_layout.rb
91
+ - app/models/importance/bootstrap_layout.rb
92
+ - app/models/importance/header.rb
93
+ - app/views/importance/imports/map.html.erb
94
+ - config/locales/de.yml
95
+ - config/locales/en.yml
96
+ - config/locales/fr.yml
97
+ - config/locales/it.yml
46
98
  - config/routes.rb
99
+ - lib/generators/importance/install/USAGE
100
+ - lib/generators/importance/install/install_generator.rb
101
+ - lib/generators/importance/install/templates/importance.rb
47
102
  - lib/importance.rb
103
+ - lib/importance/configuration.rb
48
104
  - lib/importance/engine.rb
49
105
  - lib/importance/version.rb
50
106
  - lib/tasks/importance_tasks.rake
@@ -71,8 +127,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
127
  - !ruby/object:Gem::Version
72
128
  version: '0'
73
129
  requirements: []
74
- rubygems_version: 3.4.10
130
+ rubygems_version: 3.5.16
75
131
  signing_key:
76
132
  specification_version: 4
77
- summary: Flexible importer for Excel and CSV files.
133
+ summary: Flexible Excel and CSV import engine with column mapping for Rails applications
78
134
  test_files: []
@@ -1 +0,0 @@
1
- //= link_directory ../stylesheets/importance .css
@@ -1,15 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Importance</title>
5
- <%= csrf_meta_tags %>
6
- <%= csp_meta_tag %>
7
-
8
- <%= stylesheet_link_tag "importance/application", media: "all" %>
9
- </head>
10
- <body>
11
-
12
- <%= yield %>
13
-
14
- </body>
15
- </html>