ntq_excelsior 0.3.0 → 0.4.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: d1f1c651087df6ca7f9dea48884d8976b210294cb95a2577a3048f57ac8488ee
4
- data.tar.gz: ce39afeae3e37049578a191dea797ce8c7cb06be31c3e60fb726cb823a93140f
3
+ metadata.gz: 4443b707f72d72748af1e8d5c728aef25ea2f7175704f653bebf2b1897ea1784
4
+ data.tar.gz: 7fdc4700b25c1d5773638186fb5e22f2afd9fa956cf755a5f4c6eccf4fb4f5d3
5
5
  SHA512:
6
- metadata.gz: 62c3a791e7fce170b95c2fcd07fa4c9410b57c7aa10570925169cdfa2f38ee603c102a59fafeb49db077ddba9b9644bd6380662105936f12f033c04794d55f33
7
- data.tar.gz: c22d31f130c0d0c3ae5f9a52f346255cb0926ad5c56f93880ba12dc8e622260c12a8e35de0eca1bb2d4a58c50bb4fd3d1d2037c53d377fbf3e618b6ed804db24
6
+ metadata.gz: eb6b8a3fbe5ccf2ef25eb5ae3156e40c33fe8d3d67dda073b44a986fe0d4003e6a13ccd871868f2823259e06bd3b89a8c05fac433410927bdcd4337110aacdda
7
+ data.tar.gz: d0e1c3218ca3f1d85d627a35e594772df18cbe87ba7d6af5e37ad4932c4bb2acfa5823896e6e62457286b9125aefa57435bcdb0f2d450e7ca7a494f68df74b48
data/README.md CHANGED
@@ -70,10 +70,47 @@ class UserExporter < NtqExcelsior::Exporter
70
70
  end
71
71
 
72
72
  exporter = UserExporter.new(@users)
73
- exporter.export
73
+ stream = exporter.export.to_stream.read
74
+
75
+ # In ruby file
74
76
  File.open("export.xlsx", "w") do |tpm|
75
77
  tpm.binmode
76
- tpm.write(file.to_stream.read)
78
+ tpm.write(stream)
79
+ end
80
+
81
+ # In Controller action
82
+ send_data stream, type: 'application/xlsx', filename: "filename.xlsx"
83
+ ```
84
+
85
+ ### Import
86
+
87
+ ```ruby
88
+ class UserImporter < NtqExcelsior::Importer
89
+
90
+ model_klass User
91
+
92
+ primary_key :id
93
+
94
+ schema({
95
+ email: 'Email',
96
+ first_name: /Prénom/i,
97
+ last_name: {
98
+ header: /Nom/i,
99
+ required: true
100
+ },
101
+ active: {
102
+ header: /Actif/i,
103
+ required: false
104
+ }
105
+ })
106
+
107
+ def import_line(line, save: true)
108
+ super do |record, line|
109
+ record.email = line[:email]
110
+ record.first_name = line[:first_name]
111
+ record.last_name = line[:last_name]
112
+ end
113
+ end
77
114
  end
78
115
  ```
79
116
 
@@ -0,0 +1,155 @@
1
+ require 'roo'
2
+
3
+ module NtqExcelsior
4
+ class Importer
5
+
6
+ attr_accessor :file, :check, :lines, :options, :status_tracker
7
+
8
+ class << self
9
+
10
+ def spreadsheet_options(value = nil)
11
+ @spreadsheet_options ||= value
12
+ end
13
+
14
+ def primary_key(value = nil)
15
+ @primary_key ||= value
16
+ end
17
+
18
+ def model_klass(value = nil)
19
+ @model_klass ||= value
20
+ end
21
+
22
+ def schema(value = nil)
23
+ @schema ||= value
24
+ end
25
+
26
+ def max_error_count(value = nil)
27
+ @max_error_count ||= value
28
+ end
29
+
30
+ def structure(value = nil)
31
+ @structure ||= value
32
+ end
33
+
34
+ def sample_file(value = nil)
35
+ @sample_file ||= value
36
+ end
37
+ end
38
+
39
+ def spreadsheet
40
+ return @spreadsheet unless @spreadsheet.nil?
41
+
42
+ raise 'File is missing' unless file.present?
43
+
44
+ @spreadsheet = Roo::Spreadsheet.open(file, self.class.spreadsheet_options || {})
45
+ end
46
+
47
+ def required_headers
48
+ return @required_headers if @required_headers
49
+
50
+ @required_columns = self.class.schema.select { |field, column_config| !column_config.is_a?(Hash) || !column_config.has_key?(:required) || column_config[:required] }
51
+ @required_line_keys = @required_columns.map{ |k, v| k }
52
+ @required_headers = @required_columns.map{ |k, column_config| column_config.is_a?(Hash) ? column_config[:header] : column_config }.map{|header| header.is_a?(String) ? Regexp.new(header, "i") : header}
53
+ if self.class.primary_key && !@required_line_keys.include?(self.class.primary_key)
54
+ @required_line_keys = @required_line_keys.unshift(self.class.primary_key)
55
+ @required_headers = @required_headers.unshift(Regexp.new(self.class.primary_key.to_s, "i"))
56
+ end
57
+ @required_headers
58
+ end
59
+
60
+ def spreadsheet_data
61
+ spreadsheet.sheet(spreadsheet.sheets[0]).parse(header_search: required_headers)
62
+ end
63
+
64
+ def detect_header_scheme(line)
65
+ return @header_scheme if @header_scheme
66
+ @header_scheme = {}
67
+ l = line.dup
68
+
69
+ self.class.schema.each do |field, column_config|
70
+ header = column_config.is_a?(Hash) ? column_config[:header] : column_config
71
+
72
+ l.each do |parsed_header, _value|
73
+ next unless header.is_a?(Regexp) && parsed_header.match?(header) || header.is_a?(String) && parsed_header == header
74
+
75
+ l.delete(parsed_header)
76
+ @header_scheme[parsed_header] = field
77
+ end
78
+ end
79
+ @header_scheme[self.class.primary_key.to_s] = self.class.primary_key.to_s if self.class.primary_key && !self.class.schema[self.class.primary_key.to_sym]
80
+
81
+ @header_scheme
82
+ end
83
+
84
+ def parse_line(line)
85
+ parsed_line = {}
86
+ line.each do |header, value|
87
+ header_scheme = detect_header_scheme(line)
88
+ if header.to_s == self.class.primary_key.to_s
89
+ parsed_line[self.class.primary_key] = value
90
+ next
91
+ end
92
+
93
+ header_scheme.each do |header, field|
94
+ parsed_line[field.to_sym] = line[header]
95
+ end
96
+ end
97
+
98
+ raise Roo::HeaderRowNotFoundError unless (@required_line_keys - parsed_line.keys).size == 0
99
+
100
+ parsed_line
101
+ end
102
+
103
+ def lines
104
+ return @lines if @lines
105
+
106
+ @lines = spreadsheet_data.map {|line| parse_line(line) }
107
+ end
108
+
109
+ # id for default query in model
110
+ # line in case an override is needed to find correct record
111
+ def find_or_initialize_record(line)
112
+ raise "Primary key must be set for using the default find_or_initialize" unless self.class.primary_key
113
+
114
+ self.class.model_klass.find_or_initialize_by("#{self.class.primary_key}": line[self.class.primary_key.to_sym])
115
+ end
116
+
117
+ def import_line(line, save: true)
118
+ record = find_or_initialize_record(line)
119
+
120
+ yield(record, line) if block_given?
121
+
122
+ status = {}
123
+ return { status: :success } if record.save
124
+
125
+ return { status: :error, errors: record.errors.full_messages.join(", ") }
126
+ end
127
+
128
+ def import(save: true, status_tracker: nil)
129
+ at = 0
130
+ errors_lines = []
131
+ success_count = 0
132
+ lines.each_with_index do |line, index|
133
+ break if errors_lines.size == self.class.max_error_count
134
+
135
+ result = import_line(line.with_indifferent_access, save: true)
136
+ case result[:status]
137
+ when :success
138
+ success_count += 1
139
+ when :error
140
+ error_line = line.map { |k, v| v }
141
+ error_line << result[:errors]
142
+ errors_lines.push(error_line)
143
+ end
144
+
145
+ if @status_tracker&.is_a?(Proc)
146
+ at = (((index + 1).to_d / lines.size) * 100.to_d)
147
+ @status_tracker.call(at)
148
+ end
149
+ end
150
+
151
+ { success_count: success_count, errors: errors_lines }
152
+ end
153
+
154
+ end
155
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NtqExcelsior
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/ntq_excelsior.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "ntq_excelsior/version"
4
4
  require 'ntq_excelsior/exporter'
5
+ require 'ntq_excelsior/importer'
5
6
  module NtqExcelsior
6
7
  class Error < StandardError; end
7
8
  # Your code goes here...
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ntq_excelsior
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-14 00:00:00.000000000 Z
11
+ date: 2023-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: caxlsx
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "<"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: roo
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "<"
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
27
41
  description: Library use by 9tq for import/export
28
42
  email:
29
43
  - kevin@9troisquarts.com
@@ -41,6 +55,7 @@ files:
41
55
  - Rakefile
42
56
  - lib/ntq_excelsior.rb
43
57
  - lib/ntq_excelsior/exporter.rb
58
+ - lib/ntq_excelsior/importer.rb
44
59
  - lib/ntq_excelsior/version.rb
45
60
  - sig/ntq_excelsior.rbs
46
61
  homepage: https://github.com/9troisquarts/ntq-excelsior