importance 0.2.4 → 0.2.6

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: dc6615b341ca9d538f4e54700b6c4deb6220f235a4b7e8325cffd9358d732178
4
- data.tar.gz: a043ef84dea2f19a58ececdd8ddc9fc1b46619128a657f845330f8c1429e5b94
3
+ metadata.gz: 495f2f10d4834e86a1224ba0368b4642fb136c371dc79f635cd5d2187be756f9
4
+ data.tar.gz: 9971a1b0c872ab7a4f1b705ff3b105b9217d95a7c1030043053f69a1ae95fc53
5
5
  SHA512:
6
- metadata.gz: 5c84fc09606e89071f37f12627a4392ebea48e3b5363c48ee3fcaa0ad101b19ffed448f96ab73fe2ad255b83e6da304dc02b44675bbbf259cd5bceedd4fba0a4
7
- data.tar.gz: 697a39cbee99aa846b9b36df8b4aaef611d7a5fc27c420ca869bca71bcffa8c19e7b0cc81a8cb6da4e9b752562f8b023b445f5defe3874d6bd6c82d71c9971ce
6
+ metadata.gz: f73128060f9b98baac9959e8a3f94ba3e061d019b7b2d06df6145afaec892ff4271a4f4b34052c4ac700010f52ef3603da750327e20006e0f40539a4b2dbdc10
7
+ data.tar.gz: 131100a75c3ea0b074f3a097d759b77bd582b549c7206e469c7a180ce3b716c576bfa25fdaf0183d7b899cdce48c9297af9e71296f41d2af9021bddb65d99d4e
@@ -6,125 +6,113 @@ module Importance
6
6
  # Form submission target. Persist the file and redirect to the mapping page.
7
7
  def submit
8
8
  upload = params[:file]
9
+ session[:redirect_url] = params[:redirect_url]
10
+ session[:importer] = params[:importer].to_sym
9
11
 
10
- raise ArgumentError, "Upload cannot be nil" if upload.nil?
12
+ if upload.nil?
13
+ flash[:alert] = t("importance.errors.no_file")
14
+ redirect_to session[:redirect_url] and return
15
+ end
11
16
 
12
17
  upload_extension = File.extname(upload.original_filename).downcase
13
18
  supported_extensions = [ ".xlsx", ".xls", ".csv" ]
14
19
 
15
- raise ArgumentError, "Unsupported file format. Please upload Excel (.xlsx, .xls) or CSV (.csv) files." unless supported_extensions.include?(upload_extension)
20
+ if !supported_extensions.include?(upload_extension)
21
+ flash[:alert] = t("importance.errors.invalid_file_type", types: supported_extensions.join(", "))
22
+ redirect_to session[:redirect_url] and return
23
+ end
16
24
 
17
25
  system_tmp_dir = Dir.tmpdir
18
26
  upload_path = upload.tempfile.path
19
27
  persist_filename = "#{SecureRandom.uuid}#{upload_extension}"
20
28
 
21
29
  persist_path = File.join(system_tmp_dir, persist_filename)
30
+ session[:path] = persist_path
22
31
 
23
- raise ArgumentError, "File does not exist at #{upload_path}" if !File.exist?(upload_path)
32
+ if !File.exist?(upload_path)
33
+ flash[:alert] = t("importance.errors.no_file")
34
+ redirect_to session[:redirect_url] and return
35
+ end
24
36
 
25
37
  FileUtils.mv(upload_path, persist_path)
26
38
 
27
- session[:path] = persist_path
28
- session[:importer] = params[:importer].to_sym
29
- session[:redirect_url] = params[:redirect_url]
30
-
31
39
  redirect_to map_path
32
40
  end
33
41
 
34
42
  # Mapping page. Load headers and samples, display the form.
35
43
  def map
36
- importer = Importance.configuration.importers[session[:importer].to_sym]
37
-
38
- raise ArgumentError, "Importer cannot be nil" if importer.nil?
44
+ @layout = "Importance::#{Importance.configuration.layout.to_s.camelize}Layout".constantize
45
+ @importer = Importance.configuration.importers[session[:importer].to_sym]
39
46
 
40
- workbook = Roo::Spreadsheet.open(session[:path], { csv_options: { encoding: "bom|utf-8" } })
41
- worksheet = workbook.sheet(0)
42
- @file_headers = worksheet.row(1)
43
- @samples = worksheet.parse[1..5]
44
- @full_count = worksheet.count - 1
47
+ if @importer.nil?
48
+ flash[:alert] = t("importance.errors.no_importer")
49
+ redirect_to session[:redirect_url] and return
50
+ end
45
51
 
46
- @importer_attributes = importer.attributes
47
- @layout = "Importance::#{Importance.configuration.layout.to_s.camelize}Layout".constantize
52
+ @importer.add_spreadsheet(session[:path])
48
53
  end
49
54
 
50
55
  # Import page. Load the file according to the mapping and import it.
51
56
  # Mappings param is of the form mappings[excel_column_idx] = target_attribute
52
57
  # mappings[0] = "first_name", mappings[1] = "", mappings[2] = "last_name" ...
53
58
  def import
54
- importer = Importance.configuration.importers[session[:importer].to_sym]
55
- mappings = params[:mappings]
56
-
57
- raise ArgumentError, "Mapping cannot be nil" if mappings.nil?
59
+ @layout = "Importance::#{Importance.configuration.layout.to_s.camelize}Layout".constantize
60
+ @importer = Importance.configuration.importers[session[:importer].to_sym]
58
61
 
59
- if importer.setup_callback
60
- instance_exec(&importer.setup_callback)
62
+ if @importer.nil?
63
+ flash[:alert] = t("importance.errors.no_importer")
64
+ render :map and return
61
65
  end
62
66
 
63
- begin
64
- records_to_import = []
67
+ @importer.add_spreadsheet(session[:path])
65
68
 
66
- each_processed_row(mappings) do |record|
67
- records_to_import << record
68
-
69
- if importer.batch && records_to_import.size >= importer.batch
70
- instance_exec(records_to_import, &importer.perform_callback)
71
- records_to_import = []
72
- end
73
- end
69
+ if params[:mappings].nil?
70
+ flash[:alert] = t("importance.errors.no_mappings")
71
+ render :map and return
72
+ end
74
73
 
75
- if records_to_import.any?
76
- instance_exec(records_to_import, &importer.perform_callback)
77
- end
74
+ @mappings = params[:mappings].permit!.to_h.map { |k, v| [ k.to_i, v ] }.to_h
78
75
 
79
- if importer.teardown_callback
80
- instance_exec(&importer.teardown_callback)
81
- else
82
- redirect_to (session[:redirect_url] || main_app.root_path), notice: "Import completed."
83
- end
76
+ @importer.importer_attributes.each do |attribute|
77
+ next if !attribute.options[:required]
78
+ next if @mappings.values.include?(attribute.key.to_s)
84
79
 
85
- rescue => e
86
- if importer.error_callback
87
- instance_exec(e, &importer.error_callback)
88
- end
80
+ flash[:alert] = t("importance.errors.missing_mapping", attribute: attribute.labels.first)
81
+ render :map and return
89
82
  end
90
- end
91
83
 
92
- private
84
+ @mappings.each do |column_index, attribute_name|
85
+ next if attribute_name == ""
86
+ next if @mappings.values.count(attribute_name) <= 1
93
87
 
94
- def csv_file?
95
- File.extname(session[:path]).downcase == ".csv"
96
- end
88
+ flash[:alert] = t("importance.errors.duplicate_mapping", attribute: attribute_name)
89
+ render :map and return
90
+ end
97
91
 
98
- # Yields each processed row (a hash of attribute => value) to the given block.
99
- # Skips empty rows (all values nil or empty).
100
- def each_processed_row(mappings)
101
- workbook = Roo::Spreadsheet.open(session[:path], { csv_options: { encoding: "bom|utf-8" } })
102
- worksheet = workbook.sheet(0)
103
- worksheet.each_with_index do |row, idx|
104
- next if idx == 0 # Skip header row
105
- record = process_row(row, mappings)
106
- next if record.empty? || record.values.all? { |v| v.nil? || v.to_s.strip.empty? }
107
- yield record
92
+ if @importer.setup_callback
93
+ instance_exec(&@importer.setup_callback)
108
94
  end
109
- end
110
95
 
111
- # Turn a row of the form ["Hans", "Robert", 1970, "male", "Apple Inc.", "hr@apple.com"]
112
- # and a mapping of the form {"0"=>"first_name", "1"=>"last_name", "2"=>"", "3"=>"", "4"=>"", "5"=>"email"}
113
- # into a record of the form { first_name: "Hans", last_name: "Robert", email: "hr@apple.com" }
114
- def process_row(row, mappings)
115
- record = {}
96
+ records_to_import = []
116
97
 
117
- mappings.each do |column_index, attribute_name|
118
- next if attribute_name.nil? || attribute_name == ""
119
- value = row[column_index.to_i]
120
- record[attribute_name.to_sym] = value
98
+ @importer.each_processed_row(session[:path], @mappings) do |record|
99
+ records_to_import << record
100
+
101
+ if @importer.batch && records_to_import.size >= @importer.batch
102
+ instance_exec(records_to_import, &@importer.perform_callback)
103
+ records_to_import = []
104
+ end
121
105
  end
122
106
 
123
- record
124
- end
107
+ if records_to_import.any?
108
+ instance_exec(records_to_import, &@importer.perform_callback)
109
+ end
125
110
 
126
- def rails_routes
127
- ::Rails.application.routes.url_helpers
111
+ if @importer.teardown_callback
112
+ instance_exec(&@importer.teardown_callback)
113
+ else
114
+ redirect_to (session[:redirect_url] || main_app.root_path), notice: t("importance.success.import_completed")
115
+ end
128
116
  end
129
117
  end
130
118
  end
@@ -14,8 +14,8 @@ module Importance
14
14
  best_similarity = 1.0
15
15
  break # No need to check further if an exact match is found
16
16
  end
17
- distance = DidYouMean::Levenshtein.distance(header, label)
18
- percentage = distance / header.length.to_f
17
+ distance = DidYouMean::Levenshtein.distance(header.to_s, label.to_s)
18
+ percentage = distance / header.to_s.length.to_f
19
19
  similarity = 1 - percentage
20
20
  if similarity > best_similarity
21
21
  best_similarity = similarity
@@ -4,32 +4,32 @@
4
4
  <table class="importance-table <%= @layout.table_class %>">
5
5
  <thead>
6
6
  <tr>
7
- <% @file_headers.each_with_index do |file_header, file_header_idx| %>
7
+ <% @importer.file_headers.each_with_index do |file_header, file_header_idx| %>
8
8
  <th>
9
9
  <%= t('importance.use_column_as') %>
10
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)
11
+ attribute_mappings = Importance::Header.match_attributes_to_headers(@importer.importer_attributes, @importer.file_headers)
12
+ default_value = (@mappings && @mappings[file_header_idx]) || Importance::Header.default_value_for_header(file_header, attribute_mappings)
13
13
  %>
14
14
  <%= form.select "mappings[#{file_header_idx}]",
15
15
  options_for_select(
16
16
  [[t('importance.ignore'), ""]] +
17
- @importer_attributes.map { |attr| [attr.labels.first, attr.key] },
17
+ @importer.importer_attributes.map { |attr| [attr.labels.first, attr.key] },
18
18
  default_value
19
19
  ), {}, class: @layout.select_class %>
20
20
  </th>
21
21
  <% end %>
22
22
  </tr>
23
23
  <tr>
24
- <% @file_headers.each do |file_header| %>
24
+ <% @importer.file_headers.each do |file_header| %>
25
25
  <th><%= file_header %></th>
26
26
  <% end %>
27
27
  </tr>
28
28
  </thead>
29
29
  <tbody>
30
- <% @samples.each do |sample| %>
30
+ <% @importer.samples.each do |sample| %>
31
31
  <tr>
32
- <% @file_headers.each_with_index do |file_header, file_header_idx| %>
32
+ <% @importer.file_headers.each_with_index do |file_header, file_header_idx| %>
33
33
  <td><%= sample[file_header_idx] %></td>
34
34
  <% end %>
35
35
  </tr>
@@ -38,7 +38,7 @@
38
38
  </table>
39
39
  </div>
40
40
  <p>
41
- <%= t('importance.import_description', count: @samples.count, full_count: @full_count) %>
41
+ <%= t('importance.import_description', count: @importer.samples.count, full_count: @importer.full_count) %>
42
42
  </p>
43
43
  <% end %>
44
44
 
@@ -3,4 +3,14 @@ de:
3
3
  use_column_as: Spalte verwenden als
4
4
  ignore: Ignorieren
5
5
  import: Importieren
6
- import_description: "Es werden %{count} von %{full_count} Beispieldatensätzen angezeigt."
6
+ import_description: Es werden %{count} von %{full_count} Beispieldatensätzen angezeigt.
7
+ errors:
8
+ no_importer: Kein Importer ausgewählt.
9
+ no_file: Keine Datei hochgeladen.
10
+ invalid_file_type: Ungültiger Dateityp. Erlaubt sind %{types}.
11
+ no_mappings: Keine Zuordnungen angegeben.
12
+ duplicate_mapping: Attribut %{attribute} wird mehrfach verwendet.
13
+ import_failed: Import fehlgeschlagen. %{error}.
14
+ missing_mapping: Fehlende Zuordnung für erforderliches Attribut %{attribute}.
15
+ success:
16
+ import_completed: Import abgeschlossen.
@@ -3,4 +3,14 @@ en:
3
3
  use_column_as: Use column as
4
4
  ignore: Ignore
5
5
  import: Import
6
- import_description: "Showing %{count} of %{full_count} sample records."
6
+ import_description: Showing %{count} of %{full_count} sample records.
7
+ errors:
8
+ no_importer: No importer selected.
9
+ no_file: No file uploaded.
10
+ invalid_file_type: Invalid file type. Allowed types are %{types}.
11
+ no_mappings: No mappings provided.
12
+ duplicate_mapping: Attribute %{attribute} is used multiple times.
13
+ import_failed: Import failed. %{error}.
14
+ missing_mapping: Missing mapping for required attribute %{attribute}.
15
+ success:
16
+ import_completed: Import completed.
@@ -3,4 +3,14 @@ fr:
3
3
  use_column_as: Utiliser la colonne comme
4
4
  ignore: Ignorer
5
5
  import: Importer
6
- import_description: "Affichage de %{count} sur %{full_count} exemples d'enregistrements."
6
+ import_description: Affichage de %{count} sur %{full_count} exemples d'enregistrements.
7
+ errors:
8
+ no_importer: Aucun importateur sélectionné.
9
+ no_file: Aucune fichier téléchargé.
10
+ invalid_file_type: Type de fichier invalide. Les types autorisés sont %{types}.
11
+ no_mappings: Aucune correspondance fournie.
12
+ duplicate_mapping: L'attribut %{attribute} est utilisé plusieurs fois.
13
+ import_failed: Import échoué. %{error}.
14
+ missing_mapping: Correspondance manquante pour l'attribut requis %{attribute}.
15
+ success:
16
+ import_completed: Import terminé.
@@ -3,4 +3,14 @@ it:
3
3
  use_column_as: Utilizzare come
4
4
  ignore: Ignora
5
5
  import: Importare
6
- import_description: "Mostrati %{count} di %{full_count} record di esempio."
6
+ import_description: Mostrati %{count} di %{full_count} record di esempio.
7
+ errors:
8
+ no_importer: Nessun importatore selezionato.
9
+ no_file: Nessun file caricato.
10
+ invalid_file_type: Tipo di file non valido. I tipi consentiti sono %{types}.
11
+ no_mappings: Nessuna mappatura fornita.
12
+ duplicate_mapping: L'attributo %{attribute} è utilizzato più volte.
13
+ import_failed: Import fallito. %{error}.
14
+ missing_mapping: Mappatura mancante per l'attributo obbligatorio %{attribute}.
15
+ success:
16
+ import_completed: Import completato.
@@ -29,11 +29,12 @@ module Importance
29
29
  @teardown_callback = nil
30
30
  @error_callback = nil
31
31
  @batch = false
32
+ @worksheet = nil
32
33
  instance_eval(&block) if block_given?
33
34
  end
34
35
 
35
- def attribute(key, labels)
36
- @attributes << OpenStruct.new(key: key, labels: labels)
36
+ def attribute(key, labels, options = {})
37
+ @attributes << OpenStruct.new(key: key, labels: labels, options: options)
37
38
  end
38
39
 
39
40
  def batch_size(size)
@@ -55,6 +56,49 @@ module Importance
55
56
  def error(&block)
56
57
  @error_callback = block
57
58
  end
59
+
60
+ def add_spreadsheet(path)
61
+ workbook = Roo::Spreadsheet.open(path, { csv_options: { encoding: "bom|utf-8" } })
62
+ @worksheet = workbook.sheet(0)
63
+ end
64
+
65
+ def file_headers
66
+ @worksheet.row(1)
67
+ end
68
+
69
+ def samples
70
+ @worksheet.parse[1..5]
71
+ end
72
+
73
+ def full_count
74
+ @worksheet.count - 1
75
+ end
76
+
77
+ # Yields each processed row (a hash of attribute => value) to the given block.
78
+ # Skips empty rows (all values nil or empty).
79
+ def each_processed_row(path, mappings)
80
+ @worksheet.each_with_index do |row, idx|
81
+ next if idx == 0 # Skip header row
82
+ record = process_row(row, mappings)
83
+ next if record.empty? || record.values.all? { |v| v.nil? || v.to_s.strip.empty? }
84
+ yield record
85
+ end
86
+ end
87
+
88
+ # Turn a row of the form ["Hans", "Robert", 1970, "male", "Apple Inc.", "hr@apple.com"]
89
+ # and a mapping of the form {"0"=>"first_name", "1"=>"last_name", "2"=>"", "3"=>"", "4"=>"", "5"=>"email"}
90
+ # into a record of the form { first_name: "Hans", last_name: "Robert", email: "hr@apple.com" }
91
+ def process_row(row, mappings)
92
+ record = {}
93
+
94
+ mappings.each do |column_index, attribute_name|
95
+ next if attribute_name.nil? || attribute_name == ""
96
+ value = row[column_index.to_i]
97
+ record[attribute_name.to_sym] = value
98
+ end
99
+
100
+ record
101
+ end
58
102
  end
59
103
 
60
104
  def self.configure
@@ -1,3 +1,3 @@
1
1
  module Importance
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.6"
3
3
  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.2.4
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lukas_Skywalker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-10 00:00:00.000000000 Z
11
+ date: 2025-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails