master_data_tool 0.15.0 → 0.18.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: 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: マスタデータの管理ツール