tallty_import_export 1.0.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4a9e60e31f0ac0c3d979739c50f9631e4a678e61aa86325cd655bd40b45bc7d1
4
+ data.tar.gz: a75eb5bb8b865c3d1edd470a5704d1bdc0fda9002411dde9816ea1cd3ab44e98
5
+ SHA512:
6
+ metadata.gz: 1d7adcd134a5ead8f4c0eefa8812f5f5578bf2f01dfd396c02c4ce45544e94f97b5397d8e81da5c320117b7034534c65cc0c54569fc66c884d7e4e03bad5e380
7
+ data.tar.gz: 3c36721ef2be7db0df34064fa5b02abd55b3b808644ea8f53f3e982ed90d58a60326ab62ceee365ed351faf0440180bf9d68d637b0a442cc31e4215245dab7ba
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.4.2
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://gems.ruby-china.com"
2
+
3
+ # Specify your gem's dependencies in tallty_import_export.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,89 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ tallty_import_export (0.1.0)
5
+ activesupport
6
+ caxlsx
7
+ redis
8
+ redis-objects
9
+ roo
10
+ roo-xls
11
+ tallty_form
12
+ zip-zip
13
+
14
+ GEM
15
+ remote: https://gems.ruby-china.com/
16
+ specs:
17
+ activemodel (6.0.3.4)
18
+ activesupport (= 6.0.3.4)
19
+ activesupport (6.0.3.4)
20
+ concurrent-ruby (~> 1.0, >= 1.0.2)
21
+ i18n (>= 0.7, < 2)
22
+ minitest (~> 5.1)
23
+ tzinfo (~> 1.1)
24
+ zeitwerk (~> 2.2, >= 2.2.2)
25
+ caxlsx (3.0.2)
26
+ htmlentities (~> 4.3, >= 4.3.4)
27
+ mimemagic (~> 0.3)
28
+ nokogiri (~> 1.10, >= 1.10.4)
29
+ rubyzip (>= 1.3.0, < 3)
30
+ concurrent-ruby (1.1.7)
31
+ diff-lcs (1.4.4)
32
+ htmlentities (4.3.4)
33
+ i18n (1.8.5)
34
+ concurrent-ruby (~> 1.0)
35
+ mimemagic (0.3.5)
36
+ mini_portile2 (2.4.0)
37
+ minitest (5.14.2)
38
+ nokogiri (1.10.10)
39
+ mini_portile2 (~> 2.4.0)
40
+ rake (12.3.3)
41
+ redis (4.2.5)
42
+ redis-objects (1.5.0)
43
+ redis (~> 4.0)
44
+ roo (2.8.3)
45
+ nokogiri (~> 1)
46
+ rubyzip (>= 1.3.0, < 3.0.0)
47
+ roo-xls (1.2.0)
48
+ nokogiri
49
+ roo (>= 2.0.0, < 3)
50
+ spreadsheet (> 0.9.0)
51
+ rspec (3.10.0)
52
+ rspec-core (~> 3.10.0)
53
+ rspec-expectations (~> 3.10.0)
54
+ rspec-mocks (~> 3.10.0)
55
+ rspec-core (3.10.0)
56
+ rspec-support (~> 3.10.0)
57
+ rspec-expectations (3.10.0)
58
+ diff-lcs (>= 1.2.0, < 2.0)
59
+ rspec-support (~> 3.10.0)
60
+ rspec-mocks (3.10.0)
61
+ diff-lcs (>= 1.2.0, < 2.0)
62
+ rspec-support (~> 3.10.0)
63
+ rspec-support (3.10.0)
64
+ ruby-ole (1.2.12.2)
65
+ rubyzip (2.3.0)
66
+ spreadsheet (1.2.6)
67
+ ruby-ole (>= 1.0)
68
+ tallty_duck_record (1.0.2)
69
+ activemodel (~> 6.0.3)
70
+ activesupport (~> 6.0.3)
71
+ tallty_form (1.0.0)
72
+ tallty_duck_record
73
+ thread_safe (0.3.6)
74
+ tzinfo (1.2.8)
75
+ thread_safe (~> 0.1)
76
+ zeitwerk (2.4.1)
77
+ zip-zip (0.3)
78
+ rubyzip (>= 1.0.0)
79
+
80
+ PLATFORMS
81
+ ruby
82
+
83
+ DEPENDENCIES
84
+ rake (~> 12.0)
85
+ rspec (~> 3.0)
86
+ tallty_import_export!
87
+
88
+ BUNDLED WITH
89
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 liyijie
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # TalltyImportExport
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/tallty_import_export`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'tallty_import_export'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install tallty_import_export
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tallty_import_export.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "tallty_import_export"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ require "active_support/core_ext/hash"
2
+ require "tallty_import_export/version"
3
+ require 'axlsx'
4
+ require 'redis'
5
+ require 'tallty_form'
6
+
7
+ module TalltyImportExport
8
+ extend ActiveSupport::Autoload
9
+
10
+ autoload :Common
11
+ autoload :Exportable
12
+ autoload :Export
13
+ autoload :Importable
14
+ autoload :Import
15
+ autoload :Context
16
+ autoload :FormConvert
17
+ autoload :Excel
18
+
19
+ class Error < StandardError; end
20
+
21
+ def self.redis
22
+ @redis ||= Redis.new
23
+ end
24
+
25
+ def self.redis=(redis)
26
+ @redis = redis
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ module TalltyImportExport
2
+ module Common
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ end
7
+
8
+ module ClassMethods
9
+ def model_headers
10
+ columns.map do |column|
11
+ {
12
+ key: column.name,
13
+ name: column.comment || column.name,
14
+ attr_type: column.type,
15
+ format: column.type == :string ? :string : nil,
16
+ primary_key: column.name == 'id' ? true : false
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ module TalltyImportExport
2
+ require "ostruct"
3
+ class Context < OpenStruct
4
+
5
+ end
6
+ end
@@ -0,0 +1,84 @@
1
+ module TalltyImportExport
2
+ class Excel
3
+ require 'redis-objects'
4
+ require 'roo'
5
+
6
+ attr_reader :uid, :cache
7
+
8
+ def initialize uid = SecureRandom.hex(8)
9
+ @uid = uid
10
+ @cache = ::Redis::HashKey.new(@uid, expiration: 60 * 60 * 1, marshal: true)
11
+ end
12
+
13
+ def load file, **opts
14
+ opts = opts.with_indifferent_access
15
+
16
+ file_path = file.is_a?(String) ? file : file.path
17
+ book = ::Roo::Spreadsheet.open(file_path, extension: File.extname(file_path) == '.xls' ? :xls : :xlsx)
18
+
19
+ read_title_from_row = opts[:read_title_from_row] || 1
20
+ read_data_from_row = opts[:read_data_from_row] || 2
21
+ titles = book.row(read_title_from_row)
22
+ rows = []
23
+
24
+ read_data_from_row.upto(book.last_row) do |i|
25
+ row = book.row(i)[0 .. titles.count]
26
+ out = {}
27
+ titles.each_with_index do |title, index|
28
+ out[title] = row[index]
29
+ end
30
+ rows << out
31
+ end
32
+ cache['uid'] = uid
33
+ cache['rows'] = rows
34
+ cache['titles'] = titles
35
+ end
36
+
37
+ def rows
38
+ Rows.new(cache['rows'])
39
+ end
40
+
41
+ def titles
42
+ cache['titles']
43
+ end
44
+
45
+ def data
46
+ cache.all
47
+ end
48
+
49
+ def records_pagination page: 1, per_page: 15
50
+ Pagination.new(rows, page: page, per_page: per_page)
51
+ end
52
+
53
+ class Rows < Array
54
+ # excel_hash { key => excel name }
55
+ def each_with_excel_hash excel_hash
56
+ mapping = excel_hash.invert
57
+ each do |row|
58
+ line_info = row.reduce({}) do |out, (k, v)|
59
+ next out unless mapping[k]
60
+ out[mapping[k]] = v
61
+ out
62
+ end
63
+ yield line_info
64
+ end
65
+ end
66
+ end
67
+
68
+ class Pagination < Array
69
+ attr_reader :current_page, :total_pages
70
+
71
+ def initialize ary, page: ,per_page:
72
+ @raw_ary = ary
73
+ @current_page = page.to_i
74
+ @total_pages = (@raw_ary.count / per_page.to_f).ceil
75
+ parsed_ary = @raw_ary[(page.to_i - 1) * per_page.to_i ... (page.to_i) * per_page.to_i]
76
+ super(parsed_ary)
77
+ end
78
+
79
+ def count
80
+ @raw_ary.count
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,185 @@
1
+ module TalltyImportExport
2
+ class Export
3
+ attr_reader :klass
4
+
5
+ def initialize klass
6
+ @klass = klass
7
+ end
8
+
9
+ # [
10
+ # { key: 'entry_user_name', name: '被考核人', chain: [:entry, :user_name] },
11
+ # { key: 'entry_user_code', name: '被考核人工号' },
12
+ # { key: 'entry_user_department_name', name: '被考核人部门' },
13
+ # { key: 'dimension_name', name: '考核维度' },
14
+ # { key: 'user_name', name: '考核人' },
15
+ # { key: 'user_code', name: '考核人工号' },
16
+ # { key: 'user_department_name', name: '考核人部门' },
17
+ # { key: 'state', name: '考核状态', method: :state_zh },
18
+ # { key: 'score', name: '考核分' },
19
+ # ]
20
+ # export_headers_result / headers
21
+ # key: 属性的英文名,可以支持user.name这样的方式
22
+ # name: 属性的中文名
23
+ # attr_type: 属性的类型
24
+ # format: excel是否需要特定的格式,目前主要是类似于身份证号,可以用string
25
+ # method: 导出时本地调用的方法
26
+ # chain: 导出时对象属性通过链式调用
27
+ # index: 数组方式,需要嵌套拿到里面的
28
+ # merge: true/false,默认false,某一列,如果上下行的内容相同,则直接合并单元格
29
+
30
+ def export_xlsx records, **options
31
+ records = with_scope records
32
+ process_options(options)
33
+
34
+ options = options.with_indifferent_access
35
+
36
+ Axlsx::Package.new do |pack|
37
+ workbook = pack.workbook
38
+
39
+ if @group_by.present?
40
+ if records.is_a?(Array)
41
+ records.group_by { |record| record.send(@group_by)}.each do |key, group_records|
42
+ @group_key = key
43
+ export_workbook workbook, group_records
44
+ end
45
+ else
46
+ records.group(@group_by).count.keys.each do |key|
47
+ @group_key = key
48
+ export_workbook workbook, records.ransack("#{@group_where}" => key).result
49
+ end
50
+ end
51
+ else
52
+ export_workbook workbook, records
53
+ end
54
+
55
+ file_path = File.join(Rails.root, 'public', 'export')
56
+ FileUtils.mkdir_p(file_path) unless Dir.exist?(file_path)
57
+ file_name = "#{Time.now.strftime('%Y%m%d%H%M%S')}#{@filename}.xlsx"
58
+ file_path_with_name = File.join(file_path, file_name)
59
+ pack.serialize(file_path_with_name)
60
+ return file_path_with_name
61
+ end
62
+ end
63
+
64
+ def export_workbook workbook, records
65
+ # excel导出样式
66
+ alignment = { vertical: :center, horizontal: :center }
67
+ border = { color: '969696', style: :thin }
68
+ title1 = workbook.styles.add_style(alignment: alignment, border: border, sz: 12, b: true)
69
+ title2 = workbook.styles.add_style(alignment: alignment, border: border, bg_color: "2a5caa", sz: 12, fg_color: "fffffb")
70
+ title3 = workbook.styles.add_style(alignment: alignment.merge(wrap_text: true), border: border, sz: 10)
71
+ headers = export_headers_result
72
+
73
+ _sheet_name = respond_to?(:sheet_name) ? self.sheet_name : nil
74
+
75
+ workbook.add_worksheet(name: _sheet_name) do |sheet|
76
+ if respond_to?(:first_header)
77
+ row_index = Axlsx.col_ref(headers.size - 1)
78
+ sheet.merge_cells("A1:#{row_index}1")
79
+ sheet.add_row [first_header], style: title1, height: 40
80
+ end
81
+
82
+ sheet.add_row headers.map{|header| header[:name]}, style: title2, height: 39
83
+
84
+ last_row = nil
85
+ merge_column_hash = {}
86
+ first_content_row_index = respond_to?(:first_header) ? 2 : 1
87
+
88
+ each_method = records.is_a?(Array) ? :each : :each
89
+ records.send(each_method).with_index do |record, index|
90
+ row = []
91
+ headers.each_with_index do |header, col_index|
92
+ _data = handle_data(record, header, index)
93
+ if header[:merge].present? && last_row.present? && _data == last_row[col_index]
94
+ # 这里使用二维数组,每个数组里都是列内容相同的各行
95
+ merge_column_hash[col_index] ||= []
96
+ if merge_column_hash[col_index].last&.last == index + first_content_row_index - 1
97
+ # 说明内容和上面的是延续的,继续加入之前的数组
98
+ merge_column_hash[col_index].last << index + first_content_row_index
99
+ else
100
+ merge_column_hash[col_index] << [index + first_content_row_index - 1, index + first_content_row_index]
101
+ end
102
+ end
103
+ row.push(_data)
104
+ end
105
+ sheet.add_row row, style: title3, height: @row_height, types: headers.map{|header| header[:format]&.to_sym}
106
+ last_row = row
107
+ end
108
+ # 需要根据column进行多行的内容合并
109
+ if merge_column_hash.present?
110
+ merge_column_hash.each do |col_index, row_arr|
111
+ row_arr.each do |arr|
112
+ sheet.merge_cells(
113
+ Axlsx::cell_r(col_index, arr.first) + ':' + Axlsx::cell_r(col_index, arr.last)
114
+ )
115
+ end
116
+ end
117
+ end
118
+ sheet.column_widths(*headers.map{|header| (header[:width] || @width).to_f})
119
+ end
120
+ end
121
+
122
+ def process_options options = {}
123
+ options = options.with_indifferent_access
124
+
125
+ @row_height ||= options.delete(:row_height) || 35
126
+ @width ||= (options.delete(:width) || 20).to_f
127
+ @filename ||= options.delete(:filename)
128
+ @group_by ||= options.delete(:group_by)
129
+ @group_where = "#{@group_by}_eq" if @group_by.present?
130
+ @headers ||= options.delete(:headers)
131
+ end
132
+
133
+ def with_scope records
134
+ records
135
+ end
136
+
137
+ def export_headers_result
138
+ @headers || export_headers
139
+ end
140
+
141
+ def export_headers
142
+ @headers || klass.try(:headers) || klass.try(:model_headers)
143
+ end
144
+
145
+ # 处理一个记录的数据
146
+ def handle_data record, header, index=0
147
+ data =
148
+ if header[:key] == '_index'
149
+ index + 1
150
+ elsif header[:method].present?
151
+ send(header[:method], record, header)
152
+ elsif header[:chain].present?
153
+ try_chain(record, header[:chain])
154
+ elsif header[:json]
155
+ record.send(header[:json])[header[:key]]
156
+ else
157
+ try_method(record, header[:key])
158
+ end
159
+ handle_format(data, header)
160
+ rescue
161
+ ''
162
+ end
163
+
164
+ def try_chain record, arr
165
+ arr.inject(record, :try)
166
+ end
167
+
168
+ def try_method record, method
169
+ arr = method.to_s.split(/\./)
170
+ try_chain record, arr
171
+ end
172
+
173
+ # 根据数据类型 attr_type 进行数据的格式化
174
+ def handle_format data, header
175
+ case header[:attr_type].to_s
176
+ when 'datetime'
177
+ data ? data.strftime('%F %H:%M') : nil
178
+ when 'date'
179
+ data ? data.strftime('%F') : nil
180
+ else
181
+ data
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,21 @@
1
+ module TalltyImportExport
2
+ module Exportable
3
+ extend ActiveSupport::Concern
4
+ include Common
5
+
6
+ included do |base|
7
+ # base.include(Common)
8
+ base.const_set('Export', Class.new(TalltyImportExport::Export))
9
+ end
10
+
11
+ module ClassMethods
12
+ def export_instance
13
+ const_get('Export').new(self)
14
+ end
15
+
16
+ def export_xlsx records, **options
17
+ export_instance.export_xlsx(records, **options)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module TalltyImportExport
2
+ class FormConvert
3
+ class << self
4
+ def headers form, column_name: :payload
5
+ form.fields.map do |field|
6
+ {}.tap do |q|
7
+ q[:key] = field.key
8
+ q[:name] = field.name
9
+ q[:json] = column_name.to_sym
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,145 @@
1
+ module TalltyImportExport
2
+ class Import
3
+ require 'roo'
4
+ attr_reader :klass, :context, :headers, :primary_keys, :associations
5
+
6
+ def initialize klass
7
+ @klass = klass
8
+ @headers = import_headers_result.map { |header| header.with_indifferent_access }
9
+ @context = Context.new({})
10
+ end
11
+
12
+ # key: 属性的英文名
13
+ # name: 属性的中文名
14
+ # attr_type: 属性的类型
15
+ # format: excel是否需要特定的格式,目前主要是类似于身份证号,可以用string
16
+ # convert: 导入时候,把excel的内容转换成导入时候代码逻辑需要的内容
17
+ # primary_key: 是否是主键
18
+
19
+ # xlsx_file 为 file path or file object or TalltyImportExport::Excel.new
20
+ def import_xlsx xlsx_file, associations, **options
21
+ @associations = associations
22
+ # 先处理获取出来Excel每行的数据, line_info
23
+ process_options(options)
24
+
25
+ if TalltyImportExport::Excel === xlsx_file
26
+ xlsx_file.rows.each_with_excel_hash(@excel_hash) do |line_info|
27
+ process_line_info(line_info, associations)
28
+ end
29
+ else
30
+ file_path = xlsx_file.is_a?(String) ? xlsx_file : xlsx_file.path
31
+ xlsx = ::Roo::Excelx.new(file_path)
32
+ xlsx.each_with_pagename do |_sheetname, sheet|
33
+ sheet.each(**@excel_hash).with_index do |line_info, index|
34
+ next if index == 0
35
+ process_line_info(line_info, associations)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def import_data data, associations, **options
42
+ process_options(options)
43
+ TalltyImportExport::Excel::Rows.new(data).each_with_excel_hash(@excel_hash) do |line_info|
44
+ process_line_info(line_info, associations)
45
+ end
46
+ end
47
+
48
+ def tallty_excel
49
+ TalltyImportExport::Excel
50
+ end
51
+
52
+ def process_options options
53
+ options = options.with_indifferent_access
54
+ self.headers = options.delete(:headers) || []
55
+ @primary_keys = options.delete(:primary_keys) || headers.map { |header| header[:primary_key] ? header[:key].to_sym : nil }.compact
56
+
57
+ @excel_hash = headers.reduce({}) do |h, header|
58
+ h[header[:key].to_sym] = header[:name]
59
+ h
60
+ end
61
+
62
+ options
63
+ end
64
+
65
+ def process_line_info line_info, associations
66
+ # 转换处理导入的数据格式
67
+ line_info = convert_data(line_info)
68
+
69
+ # 处理每行对于导入的动作,处理line_info
70
+ return unless valid?(line_info)
71
+
72
+ import_record(line_info, associations)
73
+ context.last_line_info = line_info
74
+ end
75
+
76
+ def convert_data line_info
77
+ line_info.with_indifferent_access.reduce({}) do |h, (k, v)|
78
+ header = headers.find do |_header|
79
+ _header[:key].to_sym == k.to_sym
80
+ end
81
+ # header[:convert] = handle_xxx
82
+ # handle_xxx(val, processing_line_info, raw_line_info)
83
+ val = header[:convert] ? send(header[:convert], v, h, line_info) : v
84
+ if header[:json]
85
+ h[header[:json]] ||= {}
86
+ h[header[:json]][k] = val
87
+ else
88
+ h[k.to_sym] = val
89
+ end
90
+ h
91
+ end.with_indifferent_access
92
+ end
93
+
94
+ # 通过转换后,数据是否合法,如果不合法,则直接跳过不处理这个数据
95
+ def valid? line_info
96
+ true
97
+ end
98
+
99
+ def import_headers_result
100
+ @headers || import_headers
101
+ end
102
+
103
+ def import_headers
104
+ @headers || klass.try(:import_headers) || klass.try(:model_headers) || (raise ArgumentError.new('missing import_headers'))
105
+ end
106
+
107
+ # 只保留 key, name, json, 合并到 import_header
108
+ def headers= val
109
+ if val.empty?
110
+ @headers = import_headers_result.map { |header| header.with_indifferent_access }
111
+ return
112
+ end
113
+
114
+ key_to_coming_header = val.reduce({}) do |out, header|
115
+ out[header.with_indifferent_access[:key].to_sym] = header.with_indifferent_access
116
+ out
117
+ end
118
+
119
+ result = []
120
+ val.map do |header|
121
+ if (exist_header = import_headers_result.find { |model_header| model_header[:key] === header[:key] })
122
+ result.push(exist_header.merge(name: header[:name], json: header[:json]))
123
+ else
124
+ result.push({ key: header[:key], name: header[:name], json: header[:json]})
125
+ end
126
+ end
127
+
128
+ @headers = result
129
+ end
130
+
131
+ def skip val, processing_line_info, raw_line_info
132
+ # do nothing there, use for header[:convert]
133
+ end
134
+
135
+ ### 这个方法是可以由复杂业务进行重载的 ###
136
+ def import_record line_info, associations
137
+ if primary_keys.present?
138
+ _record = associations.find_or_initialize_by(line_info.clone.extract!(*primary_keys))
139
+ _record.update!(line_info.clone.except!(*primary_keys))
140
+ else
141
+ associations.create!(line_info)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,29 @@
1
+ module TalltyImportExport
2
+ module Importable
3
+ extend ActiveSupport::Concern
4
+ include Common
5
+
6
+ included do |base|
7
+ # base.include(Common)
8
+ base.const_set('Import', Class.new(TalltyImportExport::Import))
9
+ end
10
+
11
+ module ClassMethods
12
+ def import_instance
13
+ const_get('Import').new(self)
14
+ end
15
+
16
+ def import_xlsx xlsx_file, associations, **opts
17
+ import_instance.import_xlsx(xlsx_file, associations, **opts)
18
+ end
19
+
20
+ def import_data data, associations, **opts
21
+ import_instance.import_data(data, associations, **opts)
22
+ end
23
+
24
+ def import_excel_klass
25
+ import_instance.tallty_excel
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module TalltyImportExport
2
+ VERSION = "1.0.3"
3
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'lib/tallty_import_export/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "tallty_import_export"
5
+ spec.version = TalltyImportExport::VERSION
6
+ spec.authors = ["liyijie"]
7
+ spec.email = ["liyijie825@gmail.com"]
8
+
9
+ spec.summary = %q{ import & export for active_record with simple_controller }
10
+ spec.description = %q{ import & export for active_record with simple_controller }
11
+ spec.homepage = "https://git.tallty.com/open-source/tallty_import_export"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.platform = Gem::Platform::RUBY
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency('zip-zip')
27
+ spec.add_dependency('caxlsx')
28
+ spec.add_dependency('roo')
29
+ spec.add_dependency('roo-xls')
30
+ spec.add_dependency('tallty_form')
31
+ spec.add_dependency('activesupport')
32
+ spec.add_dependency('redis')
33
+ spec.add_dependency('redis-objects')
34
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tallty_import_export
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.3
5
+ platform: ruby
6
+ authors:
7
+ - liyijie
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-05-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: zip-zip
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: caxlsx
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: roo
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: roo-xls
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tallty_form
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: redis
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: redis-objects
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: " import & export for active_record with simple_controller "
126
+ email:
127
+ - liyijie825@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".rspec"
134
+ - ".travis.yml"
135
+ - Gemfile
136
+ - Gemfile.lock
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - bin/console
141
+ - bin/setup
142
+ - lib/tallty_import_export.rb
143
+ - lib/tallty_import_export/common.rb
144
+ - lib/tallty_import_export/context.rb
145
+ - lib/tallty_import_export/excel.rb
146
+ - lib/tallty_import_export/export.rb
147
+ - lib/tallty_import_export/exportable.rb
148
+ - lib/tallty_import_export/form_convert.rb
149
+ - lib/tallty_import_export/import.rb
150
+ - lib/tallty_import_export/importable.rb
151
+ - lib/tallty_import_export/version.rb
152
+ - tallty_import_export.gemspec
153
+ homepage: https://git.tallty.com/open-source/tallty_import_export
154
+ licenses:
155
+ - MIT
156
+ metadata: {}
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: 2.3.0
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubygems_version: 3.2.15
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: import & export for active_record with simple_controller
176
+ test_files: []