importance 0.2.5 → 0.2.7

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: 4dbfd3e3f5165908c080e2e2f01c41b1d5ec266ca5a06f65d1dac0f67ba441fa
4
- data.tar.gz: 1c191b2a8720eddcc21c84981aa0efb3f80d70b34f9247cbc68041fdfdc14728
3
+ metadata.gz: eaa9c663497a1803a8073c007296562b853d4d1e97d6c8d8336210bd719db481
4
+ data.tar.gz: 43e5fc5fee72a4c57fbbf5838fdc1c3a83d1628433d5bdde457b01197d52b8ca
5
5
  SHA512:
6
- metadata.gz: 39b547bf1e0525d582ad017e23b80ebcceef4d0cae23712062932c76f16f39dd21f94648dc9598fe984823b0364480b66c343754a35cea7ea3cef0e4a9486cea
7
- data.tar.gz: 2657a35594799c90db8f48d331cf8fde189e6d5882397ec076cbccba0898a36ac363ef628d19faa07cba96f7115f2139b45c63b7e1587c64fa00b0cbc9515015
6
+ metadata.gz: 212918817607bb11fd2696f49f4997ff30e5ee92cd2f1f36e577b7346357bb8e0aa917f849e62f42898ba53b78aa513d3efbe7ead1c03b250d349467c5b4c0b2
7
+ data.tar.gz: e1ae1c63063963932f8ebcf67f263e1755bfbf722c1c399bc8a2d709e6705a6e1eb8f13e455994c72be017a6b2651812f3ffb7c1fffe812c8033a96e15f69aec
@@ -6,125 +6,112 @@ 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]
59
+ @layout = "Importance::#{Importance.configuration.layout.to_s.camelize}Layout".constantize
60
+ @importer = Importance.configuration.importers[session[:importer].to_sym]
61
+ @importer.add_spreadsheet(session[:path])
56
62
 
57
- raise ArgumentError, "Mapping cannot be nil" if mappings.nil?
63
+ if @importer.nil?
64
+ flash[:alert] = t("importance.errors.no_importer")
65
+ render :map and return
66
+ end
58
67
 
59
- if importer.setup_callback
60
- instance_exec(&importer.setup_callback)
68
+ if params[:mappings].nil?
69
+ flash[:alert] = t("importance.errors.no_mappings")
70
+ render :map and return
61
71
  end
62
72
 
63
- begin
64
- records_to_import = []
73
+ @mappings = params[:mappings].permit!.to_h.map { |k, v| [ k.to_i, v ] }.to_h
65
74
 
66
- each_processed_row(mappings) do |record|
67
- records_to_import << record
75
+ @importer.importer_attributes.each do |attribute|
76
+ next if !attribute.options[:required]
77
+ next if @mappings.values.include?(attribute.key.to_s)
68
78
 
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
79
+ flash[:alert] = t("importance.errors.missing_mapping", attribute: attribute.labels.first)
80
+ render :map and return
81
+ end
74
82
 
75
- if records_to_import.any?
76
- instance_exec(records_to_import, &importer.perform_callback)
77
- end
83
+ @mappings.each do |column_index, attribute_name|
84
+ next if attribute_name == ""
85
+ next if @mappings.values.count(attribute_name) <= 1
78
86
 
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
87
+ flash[:alert] = t("importance.errors.duplicate_mapping", attribute: attribute_name)
88
+ render :map and return
89
+ end
84
90
 
85
- rescue => e
86
- if importer.error_callback
87
- instance_exec(e, &importer.error_callback)
88
- end
91
+ if @importer.setup_callback
92
+ instance_exec(&@importer.setup_callback)
89
93
  end
90
- end
91
94
 
92
- private
95
+ records_to_import = []
93
96
 
94
- def csv_file?
95
- File.extname(session[:path]).downcase == ".csv"
96
- end
97
+ @importer.each_processed_row(session[:path], @mappings) do |record|
98
+ records_to_import << record
97
99
 
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
100
+ if @importer.batch && records_to_import.size >= @importer.batch
101
+ instance_exec(records_to_import, &@importer.perform_callback)
102
+ records_to_import = []
103
+ end
108
104
  end
109
- end
110
-
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 = {}
116
105
 
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
106
+ if records_to_import.any?
107
+ instance_exec(records_to_import, &@importer.perform_callback)
121
108
  end
122
109
 
123
- record
124
- end
125
-
126
- def rails_routes
127
- ::Rails.application.routes.url_helpers
110
+ if @importer.teardown_callback
111
+ instance_exec(&@importer.teardown_callback)
112
+ else
113
+ redirect_to (session[:redirect_url] || main_app.root_path), notice: t("importance.success.import_completed")
114
+ end
128
115
  end
129
116
  end
130
117
  end
@@ -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.5"
2
+ VERSION = "0.2.7"
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.5
4
+ version: 0.2.7
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