master_data_tool 0.15.0 → 0.18.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: d9ee8634a9d307fdaa7f6cae268d9955a029309dbd020755ecac87ba0f8f6c4a
4
- data.tar.gz: 31c3397ccc5d219fe933b7a0aa856878578fa061c4ab620d4e1dab39312d446c
3
+ metadata.gz: 553fcd688f3ae346cc356961473852142c87f0ee66145b9e1892bfac3e230927
4
+ data.tar.gz: 64813eea92f8afc4612cdeb19f281c11ecef5537a207a736cd38ef68d57d9adf
5
5
  SHA512:
6
- metadata.gz: 8409b81d4129b18b0fa489ca0fbaf8a73f5fac79726ee4fed5745bf9312ac24958c6908735fc7a0fc453184e17609348b68e741939b98105547a4de6e1e02315
7
- data.tar.gz: f1648f8c1123911468988ea553c1fbe5ad38b81893c5bd376c5f29f983d325672ce8c762020e8f91c7b83cca7d30f3da71919d69e24c11b728cdac7ce044a827
6
+ metadata.gz: 9b617aed72fbd9bc0a355e81baef951c4f7f3d4cdf3838cf80b7e35533c98ba4fdfc0c01137d6b28e81f4d60798f21c4b81c94f1c53badba689edfe6d9c0c117
7
+ data.tar.gz: 0d1daa98e982a6bb918d6dc63dde683b8ce65181337a6fb1754a124d7f93d0e6daa7b25e5cfa5f31a16261c8c4624d6544313a72105dff7746d9df69c28277d5
data/README.md CHANGED
@@ -35,17 +35,18 @@ Or install it yourself as:
35
35
 
36
36
  ### マスタデータの投入
37
37
 
38
- | option | default | 内容 |
39
- |----------------------| --- |-----------------------------------|
40
- | --dry-run | true | dry-runモードで実行する(データ変更は行わない) |
41
- | --verify | true | データ投入後に全テーブル・全レコードのバリデーションチェックを行う |
42
- | --only-import-tables | [] | 指定したテーブルのみデータ投入を行う |
43
- | --except-import-tables | [] | 指定したテーブルのデータ投入を行わない |
44
- | --only-verify-tables | [] | 指定したテーブルのみ投入後のバリデーションチェックを行う |
45
- | --except-verify-tables | [] | 指定したテーブルのバリデーションチェックを行わない |
46
- | --skip-no-change | true | CSVファイルに更新がないテーブルをスキップする |
47
- | --silent | false | 結果の出力をやめる |
48
- | --delete-all-ignore-foreign-key | false | 外部キー制約を無視してレコードを消すかどうか |
38
+ | option | default | 内容 |
39
+ |----------------------| --- |-----------------------------------------------------------------|
40
+ | --dry-run | true | dry-runモードで実行する(データ変更は行わない) |
41
+ | --verify | true | データ投入後に全テーブル・全レコードのバリデーションチェックを行う |
42
+ | --only-import-tables | [] | 指定したテーブルのみデータ投入を行う |
43
+ | --except-import-tables | [] | 指定したテーブルのデータ投入を行わない |
44
+ | --only-verify-tables | [] | 指定したテーブルのみ投入後のバリデーションチェックを行う |
45
+ | --except-verify-tables | [] | 指定したテーブルのバリデーションチェックを行わない |
46
+ | --skip-no-change | true | CSVファイルに更新がないテーブルをスキップする |
47
+ | --silent | false | 結果の出力をやめる |
48
+ | --delete-all-ignore-foreign-key | false | 外部キー制約を無視してレコードを消すかどうか |
49
+ | --override_identifier | nil | fixtures/#{override_identifier} のディレクトリにある内容でfixturesを上書きして投入する |
49
50
 
50
51
  ```bash
51
52
  bundle exec master_data_tool import
data/exe/master_data_tool CHANGED
@@ -17,6 +17,7 @@ module MasterDataTool
17
17
  option :except_verify_tables, default: nil, type: :array
18
18
  option :skip_no_change, default: nil, type: :boolean
19
19
  option :silent, default: nil, type: :boolean
20
+ option :override_identifier, default: nil, type: :string
20
21
  option :delete_all_ignore_foreign_key, default: nil, type: :boolean
21
22
  desc 'import', 'import'
22
23
  def import
@@ -9,12 +9,16 @@ module MasterDataTool
9
9
  config_accessor :dump_ignore_columns
10
10
  config_accessor :default_import_options
11
11
  config_accessor :logger
12
+ config_accessor :preload_associations
13
+ config_accessor :eager_load_associations
12
14
 
13
15
  def initialize
14
16
  self.master_data_dir = nil
15
17
  self.dump_ignore_tables = %w[]
16
18
  self.dump_ignore_columns = %w[]
17
19
  self.default_import_options = {}
20
+ self.preload_associations = {} # key: Class, value: associations
21
+ self.eager_load_associations = {} # key: Class, value: associations
18
22
  self.logger = Logger.new(nil)
19
23
  end
20
24
  end
@@ -12,6 +12,7 @@ module MasterDataTool
12
12
  skip_no_change: true,
13
13
  silent: false,
14
14
  delete_all_ignore_foreign_key: false,
15
+ override_identifier: nil,
15
16
  report_printer: MasterDataTool::Report::DefaultPrinter.new)
16
17
 
17
18
  @dry_run = dry_run
@@ -23,6 +24,7 @@ module MasterDataTool
23
24
  @skip_no_change = skip_no_change
24
25
  @silent = silent
25
26
  @delete_all_ignore_foreign_key = delete_all_ignore_foreign_key
27
+ @override_identifier = override_identifier
26
28
  @report_printer = report_printer
27
29
  @report_printer.silent = silent
28
30
  end
@@ -31,17 +33,17 @@ module MasterDataTool
31
33
  ApplicationRecord.transaction do
32
34
  print_execute_options
33
35
 
34
- master_data_list = build_master_data_list
36
+ master_data_collection = build_master_data_collection
35
37
 
36
- import_all!(master_data_list)
37
- verify_all!(master_data_list) if @verify
38
- save_master_data_statuses!(master_data_list)
38
+ import_all!(master_data_collection)
39
+ verify_all!(master_data_collection) if @verify
40
+ save_master_data_statuses!(master_data_collection)
39
41
 
40
- print_affected_tables(master_data_list)
42
+ print_affected_tables(master_data_collection)
41
43
 
42
44
  raise DryRunError if @dry_run
43
45
 
44
- master_data_list
46
+ master_data_collection
45
47
  end
46
48
  rescue DryRunError
47
49
  puts "[DryRun] end"
@@ -59,23 +61,18 @@ module MasterDataTool
59
61
  puts "================="
60
62
  end
61
63
 
62
- def build_master_data_list
63
- [].tap do |master_data_list|
64
- extract_master_data_csv_paths.each do |path|
65
- table_name = MasterDataTool.resolve_table_name(path)
66
- load_skip = load_skip_table?(table_name, path)
67
-
68
- model_klass = Object.const_get(table_name.classify)
69
- master_data = MasterData.new(path, model_klass)
70
- master_data.load unless load_skip
71
-
72
- master_data_list << master_data
64
+ def build_master_data_collection
65
+ MasterDataCollection.new.tap do |collection|
66
+ MasterDataTool::MasterDataFileCollection.new(override_identifier: @override_identifier).each do |master_data_file|
67
+ load_skip = load_skip_table?(master_data_file)
68
+ master_data = MasterData.build(master_data_file, load: !load_skip)
69
+ collection.append(master_data)
73
70
  end
74
- end.sort_by { |m| m.csv_path } # 外部キー制約などがある場合には先に入れておかないといけないデータなどがある。なので、プレフィックスを付けて順序を指定して貰う
71
+ end
75
72
  end
76
73
 
77
- def import_all!(master_data_list)
78
- master_data_list.each do |master_data|
74
+ def import_all!(master_data_collection)
75
+ master_data_collection.each do |master_data|
79
76
  next unless master_data.loaded?
80
77
  next if import_skip_table?(master_data.table_name)
81
78
 
@@ -84,8 +81,8 @@ module MasterDataTool
84
81
  end
85
82
  end
86
83
 
87
- def verify_all!(master_data_list)
88
- master_data_list.each do |master_data|
84
+ def verify_all!(master_data_collection)
85
+ master_data_collection.each do |master_data|
89
86
  next if verify_skip_table?(master_data.table_name)
90
87
 
91
88
  report = master_data.verify!(ignore_fail: @dry_run)
@@ -93,19 +90,19 @@ module MasterDataTool
93
90
  end
94
91
  end
95
92
 
96
- def save_master_data_statuses!(master_data_list)
93
+ def save_master_data_statuses!(master_data_collection)
97
94
  records = []
98
- master_data_list.each do |master_data|
95
+ master_data_collection.each do |master_data|
99
96
  next unless master_data.loaded?
100
97
 
101
- records << MasterDataTool::MasterDataStatus.build(master_data.csv_path)
98
+ records << MasterDataTool::MasterDataStatus.build(master_data.master_data_file)
102
99
  end
103
100
 
104
101
  MasterDataTool::MasterDataStatus.import_records!(records, dry_run: @dry_run)
105
102
  end
106
103
 
107
- def print_affected_tables(master_data_list)
108
- master_data_list.each do |master_data|
104
+ def print_affected_tables(master_data_collection)
105
+ master_data_collection.each do |master_data|
109
106
  next unless master_data.loaded?
110
107
  next unless master_data.affected?
111
108
 
@@ -114,11 +111,11 @@ module MasterDataTool
114
111
  end
115
112
  end
116
113
 
117
- def load_skip_table?(table_name, csv_path)
118
- return true if import_skip_table?(table_name)
114
+ def load_skip_table?(master_data_file)
115
+ return true if import_skip_table?(master_data_file.table_name)
119
116
  return false unless @skip_no_change
120
117
 
121
- !MasterDataTool::MasterDataStatus.master_data_will_change?(csv_path)
118
+ !MasterDataTool::MasterDataStatus.master_data_will_change?(master_data_file)
122
119
  end
123
120
 
124
121
  def import_skip_table?(table_name)
@@ -148,6 +145,13 @@ module MasterDataTool
148
145
  pattern = Pathname.new(MasterDataTool.config.master_data_dir).join('*.csv').to_s
149
146
  Pathname.glob(pattern).select(&:file?)
150
147
  end
148
+
149
+ def overridden_master_data_csv_paths
150
+ return [] unless @override_identifier
151
+
152
+ pattern = Pathname.new(MasterDataTool.config.master_data_dir).join(@override_identifier).join('*.csv').to_s
153
+ Pathname.glob(pattern).select(&:file?)
154
+ end
151
155
  end
152
156
  end
153
157
  end
@@ -2,11 +2,12 @@
2
2
 
3
3
  module MasterDataTool
4
4
  class MasterData
5
- attr_reader :csv_path, :model_klass, :columns, :new_records, :updated_records, :no_change_records, :deleted_records
5
+ attr_reader :master_data_file, :model_klass, :columns, :new_records, :updated_records, :no_change_records, :deleted_records
6
6
  attr_reader :before_count, :after_count
7
7
 
8
- def initialize(csv_path, model_klass)
9
- @csv_path = csv_path
8
+ # @param [MasterDataTool::MasterDataFile] master_data_file
9
+ def initialize(master_data_file, model_klass)
10
+ @master_data_file = master_data_file
10
11
  @model_klass = model_klass
11
12
 
12
13
  @loaded = false
@@ -18,8 +19,21 @@ module MasterDataTool
18
19
  @deleted_records = []
19
20
  end
20
21
 
22
+ class << self
23
+ def build(master_data_file, load: false)
24
+ model_klass = Object.const_get(master_data_file.table_name.classify)
25
+ new(master_data_file, model_klass).tap do |record|
26
+ record.load if load
27
+ end
28
+ end
29
+ end
30
+
31
+ def basename
32
+ @master_data_file.basename
33
+ end
34
+
21
35
  def load
22
- csv = CSV.read(@csv_path, headers: true, skip_blanks: true)
36
+ csv = CSV.read(@master_data_file.path, headers: true, skip_blanks: true)
23
37
  old_records_by_id = @model_klass.all.index_by(&:id)
24
38
 
25
39
  csv_records_by_id = build_records_from_csv(csv, old_records_by_id)
@@ -121,7 +135,11 @@ module MasterDataTool
121
135
 
122
136
  def verify!(ignore_fail: false)
123
137
  MasterDataTool::Report::VerifyReport.new(self).tap do |report|
124
- @model_klass.all.find_each do |record|
138
+ scoped = @model_klass.all
139
+ scoped = scoped.preload(preload_associations) if preload_associations
140
+ scoped = scoped.eager_load(eager_load_associations) if eager_load_associations
141
+
142
+ scoped.find_each do |record|
125
143
  valid = record.valid?
126
144
  report.append(MasterDataTool::Report::VerifyReport.build_verify_record_report(self, record, valid))
127
145
  next if ignore_fail
@@ -140,6 +158,14 @@ module MasterDataTool
140
158
 
141
159
  private
142
160
 
161
+ def preload_associations
162
+ @preload_associations ||= MasterDataTool.config.preload_associations.dig(@model_klass.to_s.to_sym)
163
+ end
164
+
165
+ def eager_load_associations
166
+ @eager_load_associations ||= MasterDataTool.config.eager_load_associations.dig(@model_klass.to_s.to_sym)
167
+ end
168
+
143
169
  def build_records_from_csv(csv, old_records_by_id)
144
170
  {}.tap do |records|
145
171
  csv.each do |row|
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MasterDataTool
4
+ class MasterDataCollection
5
+ def initialize
6
+ @collection = []
7
+ end
8
+
9
+ def append(master_data)
10
+ @collection << master_data
11
+ end
12
+
13
+ def each
14
+ return enum_for(:each) unless block_given?
15
+
16
+ @collection.sort_by(&:basename).each do |master_data|
17
+ yield master_data
18
+ end
19
+ end
20
+
21
+ def to_a
22
+ each.to_a
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module MasterDataTool
3
+ class MasterDataFile
4
+ attr_reader :table_name, :path, :override_identifier
5
+
6
+ def initialize(table_name, path, override_identifier)
7
+ @table_name = table_name
8
+ @path = path
9
+ @override_identifier = override_identifier
10
+ freeze
11
+ end
12
+
13
+ class << self
14
+ def build(path, override_identifier)
15
+ table_name = MasterDataTool.resolve_table_name(path, override_identifier)
16
+ new(table_name, path, override_identifier)
17
+ end
18
+ end
19
+
20
+ def basename
21
+ @path.basename
22
+ end
23
+
24
+ def ==(other)
25
+ other.class === self &&
26
+ other.hash == hash
27
+ end
28
+
29
+ alias eql? ==
30
+
31
+ def hash
32
+ [@table_name, @path, @override_identifier].join.hash
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MasterDataTool
4
+ class MasterDataFileCollection
5
+ def initialize(override_identifier: nil)
6
+ @override_identifier = override_identifier
7
+ @collection = build
8
+ freeze
9
+ end
10
+
11
+ def each
12
+ return enum_for(:each) unless block_given?
13
+
14
+ @collection.each do |file|
15
+ yield file
16
+ end
17
+ end
18
+
19
+ def to_a
20
+ each.to_a
21
+ end
22
+
23
+ private
24
+
25
+ def build
26
+ files = extract_master_data_csv_paths.presence&.index_by(&:table_name)
27
+ overridden_files = overridden_master_data_csv_paths.presence&.index_by(&:table_name) || {}
28
+
29
+ table_names = (files.keys + overridden_files.keys).uniq
30
+ table_names.map do |table_name|
31
+ overridden_files[table_name] || files[table_name]
32
+ end
33
+ end
34
+
35
+ def extract_master_data_csv_paths
36
+ pattern = Pathname.new(MasterDataTool.config.master_data_dir).join('*.csv').to_s
37
+ Pathname.glob(pattern).select(&:file?).map do |path|
38
+ MasterDataFile.build(path, nil)
39
+ end
40
+ end
41
+
42
+ def overridden_master_data_csv_paths
43
+ return [] if @override_identifier.blank?
44
+
45
+ pattern = Pathname.new(MasterDataTool.config.master_data_dir).join(@override_identifier).join('*.csv').to_s
46
+ Pathname.glob(pattern).select(&:file?).map do |path|
47
+ MasterDataFile.build(path, @override_identifier)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -15,9 +15,9 @@ module MasterDataTool
15
15
  presence: true
16
16
 
17
17
  class << self
18
- def build(csv_path)
19
- version = decide_version(csv_path)
20
- new(name: MasterDataTool.resolve_table_name(csv_path), version: version)
18
+ def build(master_data_file)
19
+ version = decide_version(master_data_file.path)
20
+ new(name: MasterDataTool.resolve_table_name(master_data_file.path, master_data_file.override_identifier), version: version)
21
21
  end
22
22
 
23
23
  def import_records!(records, dry_run: true)
@@ -28,9 +28,10 @@ module MasterDataTool
28
28
  end
29
29
  end
30
30
 
31
- def master_data_will_change?(csv_path)
32
- new_version = decide_version(csv_path)
33
- !where(name: MasterDataTool.resolve_table_name(csv_path), version: new_version).exists?
31
+ # @param [MasterDataTool::MasterDataFile] master_data_file
32
+ def master_data_will_change?(master_data_file)
33
+ new_version = decide_version(master_data_file.path)
34
+ !where(name: master_data_file.table_name, version: new_version).exists?
34
35
  end
35
36
 
36
37
  def decide_version(csv_path)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MasterDataTool
4
- VERSION = "0.15.0"
4
+ VERSION = "0.18.0"
5
5
  end
@@ -4,7 +4,10 @@ require 'csv'
4
4
  require_relative "master_data_tool/version"
5
5
  require_relative "master_data_tool/config"
6
6
  require_relative "master_data_tool/master_data_status"
7
+ require_relative "master_data_tool/master_data_file"
8
+ require_relative "master_data_tool/master_data_file_collection"
7
9
  require_relative "master_data_tool/master_data"
10
+ require_relative "master_data_tool/master_data_collection"
8
11
  require_relative "master_data_tool/report"
9
12
  require_relative "master_data_tool/dump/executor"
10
13
  require_relative "master_data_tool/import"
@@ -24,9 +27,11 @@ module MasterDataTool
24
27
  yield config
25
28
  end
26
29
 
27
- def resolve_table_name(csv_path)
30
+ def resolve_table_name(csv_path, override_identifier)
28
31
  # 0001_table_nameのように投入順序を制御可能にする
29
- csv_path.relative_path_from(config.master_data_dir).to_s.gsub(/^\d+_/, '').delete_suffix('.csv')
32
+ relative_path = config.master_data_dir
33
+ relative_path = "#{relative_path}/#{override_identifier}" if override_identifier.present?
34
+ csv_path.relative_path_from(relative_path).to_s.gsub(/^\d+_/, '').delete_suffix('.csv')
30
35
  end
31
36
  end
32
37
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/master_data_tool/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "master_data_tool"
7
+ spec.version = MasterDataTool::VERSION
8
+ spec.authors = ["Takahiro Ooishi"]
9
+ spec.email = ["taka0125@gmail.com"]
10
+
11
+ spec.summary = "マスタデータの管理ツール"
12
+ spec.description = "システムが稼働する上で最初から必要なデータ(マスタデータ)を管理するツールです。"
13
+ spec.homepage = "https://github.com/taka0125/master_data_tool"
14
+ spec.required_ruby_version = ">= 2.6.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
24
+ end
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency 'rspec'
31
+ spec.add_development_dependency 'mysql2'
32
+ spec.add_development_dependency 'psych', '~> 3.1'
33
+ spec.add_development_dependency 'appraisal'
34
+ spec.add_development_dependency 'ridgepole'
35
+ spec.add_development_dependency 'database_cleaner-active_record'
36
+ spec.add_development_dependency 'standalone_activerecord_boot_loader'
37
+
38
+ spec.add_dependency 'activerecord', '>= 5.1.7'
39
+ spec.add_dependency 'activesupport'
40
+ spec.add_dependency 'thor'
41
+ spec.add_dependency 'activerecord-import'
42
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: master_data_tool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takahiro Ooishi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-06 00:00:00.000000000 Z
11
+ date: 2022-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -195,6 +195,9 @@ files:
195
195
  - lib/master_data_tool/import.rb
196
196
  - lib/master_data_tool/import/executor.rb
197
197
  - lib/master_data_tool/master_data.rb
198
+ - lib/master_data_tool/master_data_collection.rb
199
+ - lib/master_data_tool/master_data_file.rb
200
+ - lib/master_data_tool/master_data_file_collection.rb
198
201
  - lib/master_data_tool/master_data_status.rb
199
202
  - lib/master_data_tool/report.rb
200
203
  - lib/master_data_tool/report/core.rb
@@ -205,6 +208,7 @@ files:
205
208
  - lib/master_data_tool/report/verify_report.rb
206
209
  - lib/master_data_tool/version.rb
207
210
  - log/test.log
211
+ - master_data_tool.gemspec
208
212
  - scripts/setup.sh
209
213
  - sig/master_data_tool.rbs
210
214
  homepage: https://github.com/taka0125/master_data_tool
@@ -227,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
231
  - !ruby/object:Gem::Version
228
232
  version: '0'
229
233
  requirements: []
230
- rubygems_version: 3.0.3
234
+ rubygems_version: 3.2.33
231
235
  signing_key:
232
236
  specification_version: 4
233
237
  summary: マスタデータの管理ツール