ntq_excelsior 0.3.0 → 0.4.0

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: 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