ntq_excelsior 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1f1c651087df6ca7f9dea48884d8976b210294cb95a2577a3048f57ac8488ee
4
- data.tar.gz: ce39afeae3e37049578a191dea797ce8c7cb06be31c3e60fb726cb823a93140f
3
+ metadata.gz: 2f5bdfecae33f17efa14aaac26fcfd4d2ebdc9e0117e5dc924e10c1b36022970
4
+ data.tar.gz: 8771fc83edec065d107912d57e45e8982a804c303ccea0b035bf206f2feaec51
5
5
  SHA512:
6
- metadata.gz: 62c3a791e7fce170b95c2fcd07fa4c9410b57c7aa10570925169cdfa2f38ee603c102a59fafeb49db077ddba9b9644bd6380662105936f12f033c04794d55f33
7
- data.tar.gz: c22d31f130c0d0c3ae5f9a52f346255cb0926ad5c56f93880ba12dc8e622260c12a8e35de0eca1bb2d4a58c50bb4fd3d1d2037c53d377fbf3e618b6ed804db24
6
+ metadata.gz: af4d4102f6ab0ad3d2cd82e75c2cb98bd114b84b798b3736b8de5bb32029da9754e7f7b378533f8c849ad99c7339a5ea170f9e4f8127f0d97dbc0e72d0537cf1
7
+ data.tar.gz: b0e9d976d84e39e76f47e5c60ae21aa7194e54c66ab910cbc6d568d4d6d3773a4235edfdc8844310e1a4ebc37f1e361e3b241aea9bfe03e55bff7414835d88a6
data/README.md CHANGED
@@ -70,11 +70,53 @@ 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)
77
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
114
+ end
115
+
116
+ importer = UserImporter.new
117
+ importer.file = file
118
+ result = importer.import
119
+ # { success_count: 2, error_lines: [] }
78
120
  ```
79
121
 
80
122
  ## Development
@@ -0,0 +1,171 @@
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 autosave(value = nil)
11
+ @autosave ||= value
12
+ end
13
+
14
+ def spreadsheet_options(value = nil)
15
+ @spreadsheet_options ||= value
16
+ end
17
+
18
+ def primary_key(value = nil)
19
+ @primary_key ||= value
20
+ end
21
+
22
+ def model_klass(value = nil)
23
+ @model_klass ||= value
24
+ end
25
+
26
+ def schema(value = nil)
27
+ @schema ||= value
28
+ end
29
+
30
+ def max_error_count(value = nil)
31
+ @max_error_count ||= value
32
+ end
33
+
34
+ def structure(value = nil)
35
+ @structure ||= value
36
+ end
37
+
38
+ def sample_file(value = nil)
39
+ @sample_file ||= value
40
+ end
41
+ end
42
+
43
+ def spreadsheet
44
+ return @spreadsheet unless @spreadsheet.nil?
45
+
46
+ raise 'File is missing' unless file.present?
47
+
48
+ @spreadsheet = Roo::Spreadsheet.open(file, self.class.spreadsheet_options || {})
49
+ end
50
+
51
+ def required_headers
52
+ return @required_headers if @required_headers
53
+
54
+ @required_columns = self.class.schema.select { |field, column_config| !column_config.is_a?(Hash) || !column_config.has_key?(:required) || column_config[:required] }
55
+ @required_line_keys = @required_columns.map{ |k, v| k }
56
+ @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}
57
+ if self.class.primary_key && !@required_line_keys.include?(self.class.primary_key)
58
+ @required_line_keys = @required_line_keys.unshift(self.class.primary_key)
59
+ @required_headers = @required_headers.unshift(Regexp.new(self.class.primary_key.to_s, "i"))
60
+ end
61
+ @required_headers
62
+ end
63
+
64
+ def spreadsheet_data
65
+ spreadsheet.sheet(spreadsheet.sheets[0]).parse(header_search: required_headers)
66
+ end
67
+
68
+ def detect_header_scheme
69
+ return @header_scheme if @header_scheme
70
+ @header_scheme = {}
71
+ l = spreadsheet_data[0].dup
72
+
73
+ self.class.schema.each do |field, column_config|
74
+ header = column_config.is_a?(Hash) ? column_config[:header] : column_config
75
+
76
+ l.each do |parsed_header, _value|
77
+ next unless header.is_a?(Regexp) && parsed_header.match?(header) || header.is_a?(String) && parsed_header == header
78
+
79
+ l.delete(parsed_header)
80
+ @header_scheme[parsed_header] = field
81
+ end
82
+ end
83
+ @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]
84
+
85
+ @header_scheme
86
+ end
87
+
88
+ def parse_line(line)
89
+ parsed_line = {}
90
+ line.each do |header, value|
91
+ header_scheme = detect_header_scheme
92
+ if header.to_s == self.class.primary_key.to_s
93
+ parsed_line[self.class.primary_key] = value
94
+ next
95
+ end
96
+
97
+ header_scheme.each do |header, field|
98
+ parsed_line[field.to_sym] = line[header]
99
+ end
100
+ end
101
+
102
+ raise Roo::HeaderRowNotFoundError unless (@required_line_keys - parsed_line.keys).size == 0
103
+
104
+ parsed_line
105
+ end
106
+
107
+ def lines
108
+ return @lines if @lines
109
+
110
+ @lines = spreadsheet_data.map {|line| parse_line(line) }
111
+ end
112
+
113
+ # id for default query in model
114
+ # line in case an override is needed to find correct record
115
+ def find_or_initialize_record(line)
116
+ return nil unless self.class.primary_key && self.class.model_klass
117
+
118
+ self.class.model_klass.constantize.find_or_initialize_by("#{self.class.primary_key}": line[self.class.primary_key.to_sym])
119
+ end
120
+
121
+ def import_line(line, save: true)
122
+ record = find_or_initialize_record(line)
123
+ @success = false
124
+ @action = nil
125
+ @errors = []
126
+
127
+ yield(record, line) if block_given?
128
+
129
+ if (self.class.autosave.nil? || self.class.autosave)
130
+ @action = record.persisted? ? 'update' : 'create'
131
+ if save
132
+ @success = record.save
133
+ else
134
+ @success = record.valid?
135
+ end
136
+ @errors = record.errors.full_messages.concat(@errors) if record.errors.any?
137
+ end
138
+
139
+ return { status: :success, action: @action } if @success
140
+
141
+ return { status: :error, errors: @errors.join(", ") }
142
+ end
143
+
144
+ def import(save: true, status_tracker: nil)
145
+ at = 0
146
+ errors_lines = []
147
+ success_count = 0
148
+ lines.each_with_index do |line, index|
149
+ break if errors_lines.size == self.class.max_error_count
150
+
151
+ result = import_line(line.with_indifferent_access, save: true)
152
+ case result[:status]
153
+ when :success
154
+ success_count += 1
155
+ when :error
156
+ error_line = line.map { |k, v| v }
157
+ error_line << result[:errors]
158
+ errors_lines.push(error_line)
159
+ end
160
+
161
+ if @status_tracker&.is_a?(Proc)
162
+ at = (((index + 1).to_d / lines.size) * 100.to_d)
163
+ @status_tracker.call(at)
164
+ end
165
+ end
166
+
167
+ { success_count: success_count, errors: errors_lines }
168
+ end
169
+
170
+ end
171
+ 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.1"
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.1
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-27 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