rails-crud-tools 0.6.8 → 0.6.10
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 +4 -4
- data/.rspec +3 -0
- data/.rubocop.yml +269 -0
- data/CHANGELOG.md +76 -0
- data/Dockerfile +21 -0
- data/Gemfile +18 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +74 -0
- data/Rakefile +12 -0
- data/docker-compose.yml +11 -0
- data/exe/console +15 -0
- data/exe/crud +30 -0
- data/exe/setup +8 -0
- data/lib/rails/crud/tools/cli.rb +188 -0
- data/lib/rails/crud/tools/constants.rb +11 -0
- data/lib/rails/crud/tools/crud_config.rb +56 -0
- data/lib/rails/crud/tools/crud_data.rb +108 -0
- data/lib/rails/crud/tools/crud_logger.rb +26 -0
- data/lib/rails/crud/tools/crud_notifications.rb +158 -0
- data/lib/rails/crud/tools/crud_operations.rb +54 -0
- data/lib/rails/crud/tools/crud_operations_logger.rb +166 -0
- data/lib/rails/crud/tools/railtie.rb +40 -0
- data/lib/rails/crud/tools/version.rb +9 -0
- data/lib/rails/crud/tools.rb +39 -0
- data/sig/rails/crud.rbs +6 -0
- data/tools/crud_macro.xlsm +0 -0
- metadata +35 -6
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubyXL"
|
4
|
+
require "rubyXL/convenience_methods"
|
5
|
+
require "active_record"
|
6
|
+
require "yaml"
|
7
|
+
require "erb"
|
8
|
+
|
9
|
+
module RailsCrudTools
|
10
|
+
# The CLI class provides command-line interface methods for generating CRUD files and configurations.
|
11
|
+
# It includes methods to generate CRUD files, generate configuration files, and initialize the application.
|
12
|
+
class CLI
|
13
|
+
@application_loaded = false
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def generate_crud_file
|
17
|
+
load_application unless @application_loaded
|
18
|
+
|
19
|
+
# 1. `bundle exec rails routes --expanded`の結果を取得
|
20
|
+
routes_output = `bundle exec rails routes --expanded`
|
21
|
+
config = Rails::Crud::Tools::CrudConfig.instance.config
|
22
|
+
font_name = config.crud_file.font_name
|
23
|
+
|
24
|
+
# 2. 取得した結果を区切り文字で分割
|
25
|
+
routes_lines = routes_output.split("\n").reject(&:empty?)
|
26
|
+
routes_data = []
|
27
|
+
current_route = {}
|
28
|
+
|
29
|
+
routes_lines.each do |line|
|
30
|
+
if line.start_with?("--[ Route")
|
31
|
+
routes_data << current_route unless current_route.empty?
|
32
|
+
current_route = {}
|
33
|
+
else
|
34
|
+
key, value = line.split("|").map(&:strip)
|
35
|
+
current_route[key] = value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
routes_data << current_route unless current_route.empty?
|
39
|
+
|
40
|
+
# 3. 全テーブル名を取得し、アルファベット順にソート
|
41
|
+
table_names = ActiveRecord::Base.connection.tables.sort
|
42
|
+
|
43
|
+
# 4. `rubyXL`を使って`xlsx`ファイルに書き込み
|
44
|
+
workbook = RubyXL::Workbook.new
|
45
|
+
sheet = workbook[0]
|
46
|
+
sheet.sheet_name = config.crud_file.sheet_name
|
47
|
+
|
48
|
+
# ヘッダー行を追加
|
49
|
+
headers = %w[Prefix Verb URI Controller#Action crud_count] + table_names
|
50
|
+
|
51
|
+
headers.each_with_index do |header, index|
|
52
|
+
cell = sheet.add_cell(0, index, header)
|
53
|
+
cell.change_fill("00FFCC")
|
54
|
+
cell.change_font_name(font_name)
|
55
|
+
cell.change_font_bold(true)
|
56
|
+
apply_borders(cell)
|
57
|
+
end
|
58
|
+
|
59
|
+
start_col = "F"
|
60
|
+
end_col = ("A".."ZZ").to_a[table_names.length + 4] # 'F'から始まる列の範囲を計算
|
61
|
+
|
62
|
+
# データ行を追加
|
63
|
+
routes_data.each_with_index do |route, row_index|
|
64
|
+
headers.each_with_index do |header, col_index|
|
65
|
+
cell = sheet.add_cell(row_index + 1, col_index, route[header])
|
66
|
+
cell.change_font_name(font_name)
|
67
|
+
apply_borders(cell)
|
68
|
+
end
|
69
|
+
|
70
|
+
# 追加: crud_count列に式を設定
|
71
|
+
crud_count_formula = "=SUMPRODUCT(LEN(#{start_col}#{row_index + 2}:#{end_col}#{row_index + 2}))"
|
72
|
+
crud_count_cell = sheet.add_cell(row_index + 1, 4, "", crud_count_formula)
|
73
|
+
crud_count_cell.change_font_name(font_name)
|
74
|
+
apply_borders(crud_count_cell)
|
75
|
+
end
|
76
|
+
|
77
|
+
# app/jobs ディレクトリ内のジョブ名を取得
|
78
|
+
job_files = Dir.glob("app/jobs/**/*.rb")
|
79
|
+
job_classes = job_files.map do |file|
|
80
|
+
File.basename(file, ".rb").camelize
|
81
|
+
end.reject { |job| job == "ApplicationJob" }.sort
|
82
|
+
|
83
|
+
# ジョブ名を Controller#Action 列に追加
|
84
|
+
job_classes.each_with_index do |job, index|
|
85
|
+
headers.each_with_index do |header, col_index|
|
86
|
+
if header == "Controller#Action"
|
87
|
+
cell = sheet.add_cell(routes_data.length + 1 + index, col_index, job)
|
88
|
+
cell.change_font_name(font_name)
|
89
|
+
else
|
90
|
+
cell = sheet.add_cell(routes_data.length + 1 + index, col_index, nil)
|
91
|
+
end
|
92
|
+
apply_borders(cell)
|
93
|
+
end
|
94
|
+
|
95
|
+
# 追加: crud_count列に式を設定
|
96
|
+
crud_count_formula = "=SUMPRODUCT(LEN(#{start_col}#{routes_data.length + 2 + index}:#{end_col}#{routes_data.length + 2 + index}))"
|
97
|
+
crud_count_cell = sheet.add_cell(routes_data.length + 1 + index, 4, "", crud_count_formula)
|
98
|
+
crud_count_cell.change_font_name(font_name)
|
99
|
+
apply_borders(crud_count_cell)
|
100
|
+
end
|
101
|
+
|
102
|
+
# ヘッダーの背景色を設定
|
103
|
+
(0..headers.length - 1).each do |col_index|
|
104
|
+
sheet[0][col_index].change_fill(config.crud_file.header_bg_color)
|
105
|
+
end
|
106
|
+
|
107
|
+
# 列幅を設定
|
108
|
+
headers.each_with_index do |header, col_index|
|
109
|
+
max_length = header.length
|
110
|
+
(1..routes_data.length).each do |row_index|
|
111
|
+
cell_value = sheet[row_index][col_index].value.to_s
|
112
|
+
max_length = [max_length, cell_value.length].max
|
113
|
+
end
|
114
|
+
sheet.change_column_width(col_index, max_length + 2)
|
115
|
+
end
|
116
|
+
|
117
|
+
# ファイルを保存
|
118
|
+
crud_file = config.crud_file_path
|
119
|
+
base_dir = File.dirname(crud_file)
|
120
|
+
|
121
|
+
# base_dirが存在しなければ作成
|
122
|
+
FileUtils.mkdir_p(base_dir) unless Dir.exist?(base_dir)
|
123
|
+
workbook.write(crud_file)
|
124
|
+
|
125
|
+
puts "Output: #{crud_file}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def generate_crud_config
|
129
|
+
load_application unless @application_loaded
|
130
|
+
|
131
|
+
table_names = ActiveRecord::Base.connection.tables.sort
|
132
|
+
table_start_col = table_names.any? ? table_names.first : "active_admin_comments"
|
133
|
+
|
134
|
+
config_content = <<~CONFIG
|
135
|
+
enabled: true
|
136
|
+
base_dir: doc
|
137
|
+
crud_file:
|
138
|
+
file_name: crud.xlsx
|
139
|
+
sheet_name: CRUD
|
140
|
+
header_bg_color: 00FFCC
|
141
|
+
font_name: Arial
|
142
|
+
method_col: Verb
|
143
|
+
action_col: Controller#Action
|
144
|
+
table_start_col: #{table_start_col}
|
145
|
+
sql_logging_enabled: true
|
146
|
+
CONFIG
|
147
|
+
|
148
|
+
File.write(".crudconfig.yml", config_content)
|
149
|
+
puts "Generated .crudconfig.yml file"
|
150
|
+
end
|
151
|
+
|
152
|
+
def init
|
153
|
+
generate_crud_config
|
154
|
+
generate_crud_file
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def load_application
|
160
|
+
return if @application_loaded
|
161
|
+
|
162
|
+
path = Dir.pwd
|
163
|
+
$stderr.puts "Loading application in '#{File.basename(path)}'..."
|
164
|
+
environment_path = "#{path}/config/environment.rb"
|
165
|
+
require environment_path
|
166
|
+
|
167
|
+
if defined? Rails
|
168
|
+
Rails.application.eager_load!
|
169
|
+
Rails.application.config.eager_load_namespaces.each(&:eager_load!) if Rails.application.config.respond_to?(:eager_load_namespaces)
|
170
|
+
end
|
171
|
+
|
172
|
+
@application_loaded = true
|
173
|
+
rescue ::LoadError
|
174
|
+
error_message = <<~EOS
|
175
|
+
Tried to load your application environment from '#{environment_path}'.
|
176
|
+
EOS
|
177
|
+
puts error_message
|
178
|
+
rescue TypeError
|
179
|
+
end
|
180
|
+
|
181
|
+
def apply_borders(cell)
|
182
|
+
%i[top bottom left right].each do |side|
|
183
|
+
cell.change_border(side, "thin")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "singleton"
|
5
|
+
|
6
|
+
module Rails
|
7
|
+
module Crud
|
8
|
+
module Tools
|
9
|
+
# The CrudConfig class is a singleton class responsible for loading and managing configuration settings from a YAML file (.crudconfig.yml).
|
10
|
+
# It ensures that the configuration is reloaded if the file is updated.
|
11
|
+
class CrudConfig
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
CONFIG_PATH = File.expand_path(".crudconfig.yml", Dir.pwd)
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@last_loaded_time = Time.at(0)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.config_path
|
21
|
+
CONFIG_PATH
|
22
|
+
end
|
23
|
+
|
24
|
+
def config
|
25
|
+
load_config if @config.nil? || config_file_updated?
|
26
|
+
@config
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_config
|
30
|
+
@config = deep_convert_to_struct(YAML.load_file(CONFIG_PATH))
|
31
|
+
@last_loaded_time = File.mtime(CONFIG_PATH)
|
32
|
+
rescue Errno::ENOENT
|
33
|
+
raise "Configuration file not found: #{CONFIG_PATH}"
|
34
|
+
rescue Psych::SyntaxError => e
|
35
|
+
raise "YAML syntax error occurred while parsing #{CONFIG_PATH}: #{e.message}"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def config_file_updated?
|
41
|
+
File.mtime(CONFIG_PATH) > @last_loaded_time
|
42
|
+
end
|
43
|
+
|
44
|
+
# Recursively convert hash to Struct and add crud_file_path method
|
45
|
+
def deep_convert_to_struct(hash)
|
46
|
+
struct_class = Struct.new(*hash.keys.map(&:to_sym)) do
|
47
|
+
def crud_file_path
|
48
|
+
File.join(base_dir, crud_file.file_name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
struct_class.new(*hash.values.map { |value| value.is_a?(Hash) ? deep_convert_to_struct(value) : value })
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zip"
|
4
|
+
require_relative "crud_logger"
|
5
|
+
require_relative "constants"
|
6
|
+
|
7
|
+
module Rails
|
8
|
+
module Crud
|
9
|
+
module Tools
|
10
|
+
# The CrudData class is responsible for loading and managing CRUD data from a file.
|
11
|
+
# It includes methods to load data, reload if needed, and retrieve specific information from the data.
|
12
|
+
class CrudData
|
13
|
+
include Singleton
|
14
|
+
|
15
|
+
attr_accessor :process_id, :crud_rows, :crud_cols, :workbook, :last_loaded_time
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@process_id = nil
|
19
|
+
@crud_rows = {}
|
20
|
+
@crud_cols = {}
|
21
|
+
@last_loaded_time = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_crud_data
|
25
|
+
config = CrudConfig.instance.config
|
26
|
+
return unless config.enabled
|
27
|
+
|
28
|
+
unless File.exist?(config.crud_file_path)
|
29
|
+
CrudLogger.logger.warn "CRUD file not found: #{config.crud_file_path}"
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
@workbook = RubyXL::Parser.parse(config.crud_file_path)
|
34
|
+
@last_loaded_time = File.mtime(config.crud_file_path)
|
35
|
+
sheet = crud_sheet
|
36
|
+
headers = sheet[0].cells.map(&:value)
|
37
|
+
|
38
|
+
method_col_index = headers.index(config.method_col)
|
39
|
+
action_col_index = headers.index(config.action_col)
|
40
|
+
table_start_col_index = headers.index(config.table_start_col)
|
41
|
+
|
42
|
+
raise "Method column not found" unless method_col_index
|
43
|
+
raise "Action column not found" unless action_col_index
|
44
|
+
raise "Table start column not found" unless table_start_col_index
|
45
|
+
|
46
|
+
headers[table_start_col_index..].each_with_index do |table_name, index|
|
47
|
+
@crud_cols[table_name] = table_start_col_index + index
|
48
|
+
end
|
49
|
+
|
50
|
+
sheet.each_with_index do |row, index|
|
51
|
+
next if index.zero?
|
52
|
+
|
53
|
+
method = row[method_col_index]&.value.to_s.strip
|
54
|
+
method = Constants::DEFAULT_METHOD if method.empty?
|
55
|
+
value = row[action_col_index]&.value
|
56
|
+
split_value = value&.split
|
57
|
+
action = split_value&.first
|
58
|
+
next if action.nil?
|
59
|
+
|
60
|
+
@crud_rows[method] ||= {}
|
61
|
+
@crud_rows[method][action] = index
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# CRUDデータが更新された場合に再読み込みする
|
66
|
+
def reload_if_needed
|
67
|
+
config = CrudConfig.instance.config
|
68
|
+
return unless config.enabled
|
69
|
+
|
70
|
+
return unless @last_loaded_time.nil? || File.mtime(config.crud_file_path) > @last_loaded_time
|
71
|
+
|
72
|
+
last_modified_by = get_last_modified_by(config.crud_file_path)
|
73
|
+
CrudLogger.logger.info "last modified by: #{last_modified_by}. process_id: #{process_id}"
|
74
|
+
return if process_id == last_modified_by
|
75
|
+
|
76
|
+
CrudLogger.logger.info "Reloading CRUD data due to file modification. last_loaded_time = #{@last_loaded_time}"
|
77
|
+
load_crud_data
|
78
|
+
end
|
79
|
+
|
80
|
+
# xlsxファイルの最終更新者を取得する
|
81
|
+
def get_last_modified_by(file_path)
|
82
|
+
last_modified_by = nil
|
83
|
+
|
84
|
+
Zip::File.open(file_path) do |zipfile|
|
85
|
+
doc_props = zipfile.find_entry("docProps/core.xml")
|
86
|
+
if doc_props
|
87
|
+
content = doc_props.get_input_stream.read
|
88
|
+
last_modified_by = content[%r{<cp:lastModifiedBy>(.*?)</cp:lastModifiedBy>}, 1]
|
89
|
+
else
|
90
|
+
CrudLogger.logger.warn "docProps/core.xml が見つかりませんでした。"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
last_modified_by
|
95
|
+
end
|
96
|
+
|
97
|
+
# CRUDシートを取得する
|
98
|
+
def crud_sheet
|
99
|
+
sheet_name = CrudConfig.instance.config.crud_file.sheet_name
|
100
|
+
sheet = @workbook[sheet_name]
|
101
|
+
raise "CRUD sheet '#{sheet_name}' not found" if sheet.nil?
|
102
|
+
|
103
|
+
sheet
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "singleton"
|
5
|
+
|
6
|
+
module Rails
|
7
|
+
module Crud
|
8
|
+
module Tools
|
9
|
+
# The CrudLogger class is responsible for logging CRUD operations to a file.
|
10
|
+
# It uses the Singleton pattern to ensure only one instance of the logger exists.
|
11
|
+
class CrudLogger
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
attr_reader :logger
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@logger = Logger.new("log/crud.log")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.logger
|
21
|
+
instance.logger
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/notifications"
|
4
|
+
|
5
|
+
module Rails
|
6
|
+
module Crud
|
7
|
+
# The Tools module provides utility methods for setting up notifications and processing SQL queries.
|
8
|
+
# It includes methods to subscribe to ActiveSupport notifications and handle different types of SQL operations.
|
9
|
+
module Tools
|
10
|
+
def self.setup_notifications
|
11
|
+
# 初回呼び出し時に @subscribed を false に設定
|
12
|
+
@subscribed ||= false
|
13
|
+
# 既に通知が登録されている場合は処理を中断
|
14
|
+
return if @subscribed
|
15
|
+
|
16
|
+
if CrudConfig.instance.config.enabled
|
17
|
+
# SQL クエリを監視する
|
18
|
+
ActiveSupport::Notifications.subscribe(/sql.active_record/) do |_name, _started, _finished, _unique_id, data|
|
19
|
+
process_sql(data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# 通知の登録が完了した後に @subscribed を true に設定
|
24
|
+
@subscribed = true
|
25
|
+
end
|
26
|
+
|
27
|
+
OPERATION_UNKNOWN = "Unknown"
|
28
|
+
|
29
|
+
def self.process_sql(data)
|
30
|
+
return unless data[:sql] =~ /\A\s*(INSERT|UPDATE|DELETE|SELECT)/i
|
31
|
+
|
32
|
+
case data[:sql]
|
33
|
+
when /\bINSERT INTO\b.*\bSELECT\b/i
|
34
|
+
handle_insert_select(data)
|
35
|
+
when /\bUPDATE\b.*\bSET\b.*\bSELECT\b/i
|
36
|
+
handle_update_select(data)
|
37
|
+
when /\bDELETE\b.*\bEXISTS\b.*\bSELECT\b/i
|
38
|
+
handle_delete_select(data)
|
39
|
+
else
|
40
|
+
handle_general_sql(data)
|
41
|
+
end
|
42
|
+
|
43
|
+
return unless CrudConfig.instance.config.sql_logging_enabled
|
44
|
+
|
45
|
+
# SQL ログを出力
|
46
|
+
CrudLogger.logger.info "#{data[:name]} - #{data[:sql]}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.handle_insert_select(data)
|
50
|
+
# INSERT INTO ... SELECT の特別な処理
|
51
|
+
insert_table = data[:sql].match(/INSERT INTO\s+`?(\w+)`?/i)[1]
|
52
|
+
select_tables = data[:sql].scan(/SELECT .* FROM\s+`?(\w+)`?(?:\s*,\s*`?(\w+)`?)*|JOIN\s+`?(\w+)`?/i).flatten.compact.uniq
|
53
|
+
|
54
|
+
key, method = determine_key_and_method
|
55
|
+
if key.nil? || method.nil?
|
56
|
+
CrudLogger.logger.warn "Request not found. #{data[:name]} - #{data[:sql]}"
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
CrudOperations.instance.add_operation(method, key, insert_table, "C")
|
61
|
+
select_tables.each do |select_table|
|
62
|
+
CrudOperations.instance.add_operation(method, key, select_table, "R")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.handle_update_select(data)
|
67
|
+
# UPDATE ... SET ... SELECT の特別な処理
|
68
|
+
update_table = data[:sql].match(/UPDATE\s+`?(\w+)`?/i)[1]
|
69
|
+
select_tables = data[:sql].scan(/SELECT .* FROM\s+`?(\w+)`?(?:\s*,\s*`?(\w+)`?)*|JOIN\s+`?(\w+)`?/i).flatten.compact.uniq
|
70
|
+
|
71
|
+
key, method = determine_key_and_method
|
72
|
+
if key.nil? || method.nil?
|
73
|
+
CrudLogger.logger.warn "Request not found. #{data[:name]} - #{data[:sql]}"
|
74
|
+
return
|
75
|
+
end
|
76
|
+
|
77
|
+
CrudOperations.instance.add_operation(method, key, update_table, "U")
|
78
|
+
select_tables.each do |select_table|
|
79
|
+
CrudOperations.instance.add_operation(method, key, select_table, "R")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.handle_delete_select(data)
|
84
|
+
# DELETE ... WHERE EXISTS ... SELECT の特別な処理
|
85
|
+
delete_table = data[:sql].match(/DELETE FROM\s+`?(\w+)`?/i)[1]
|
86
|
+
select_tables = data[:sql].scan(/SELECT .* FROM\s+`?(\w+)`?(?:\s*,\s*`?(\w+)`?)*|JOIN\s+`?(\w+)`?/i).flatten.compact.uniq
|
87
|
+
|
88
|
+
key, method = determine_key_and_method
|
89
|
+
if key.nil? || method.nil?
|
90
|
+
CrudLogger.logger.warn "Request not found. #{data[:name]} - #{data[:sql]}"
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
CrudOperations.instance.add_operation(method, key, delete_table, "D")
|
95
|
+
select_tables.each do |select_table|
|
96
|
+
CrudOperations.instance.add_operation(method, key, select_table, "R")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.handle_general_sql(data)
|
101
|
+
operation = if (match = data[:sql].match(/\A\s*(INSERT|UPDATE|DELETE|SELECT)/i))
|
102
|
+
case match[1].upcase
|
103
|
+
when "INSERT" then "C"
|
104
|
+
when "SELECT" then "R"
|
105
|
+
when "UPDATE" then "U"
|
106
|
+
when "DELETE" then "D"
|
107
|
+
else OPERATION_UNKNOWN
|
108
|
+
end
|
109
|
+
else
|
110
|
+
OPERATION_UNKNOWN
|
111
|
+
end
|
112
|
+
|
113
|
+
if operation == OPERATION_UNKNOWN
|
114
|
+
warn "Warning: Unknown SQL operation. #{data[:name]} - #{data[:sql]}"
|
115
|
+
return
|
116
|
+
end
|
117
|
+
|
118
|
+
table_names = data[:sql].scan(/(?:INSERT INTO|UPDATE|DELETE FROM|FROM|JOIN)\s+`?(\w+)`?(?:\s*,\s*`?(\w+)`?)*/i).flatten.compact.uniq
|
119
|
+
if table_names.empty?
|
120
|
+
# テーブル名が見つからない場合は警告を出力
|
121
|
+
CrudLogger.logger.warn "Table name not found in SQL: #{data[:sql]}"
|
122
|
+
return
|
123
|
+
end
|
124
|
+
|
125
|
+
key, method = determine_key_and_method
|
126
|
+
if key.nil? || method.nil?
|
127
|
+
CrudLogger.logger.warn "Request not found. #{data[:name]} - #{data[:sql]}"
|
128
|
+
return
|
129
|
+
end
|
130
|
+
|
131
|
+
# テーブル名を取得して CRUD 操作に追加
|
132
|
+
table_names.each do |table_name|
|
133
|
+
CrudOperations.instance.add_operation(method, key, table_name, operation)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# キーとメソッドを決定する
|
138
|
+
def self.determine_key_and_method
|
139
|
+
request = Thread.current[:crud_request]
|
140
|
+
sidekiq_job_class = Thread.current[:crud_sidekiq_job_class]
|
141
|
+
|
142
|
+
if request
|
143
|
+
method = request.request_method
|
144
|
+
controller = request.params["controller"]
|
145
|
+
action = request.params["action"]
|
146
|
+
key = "#{controller}##{action}"
|
147
|
+
elsif sidekiq_job_class
|
148
|
+
key = sidekiq_job_class
|
149
|
+
method = Constants::DEFAULT_METHOD
|
150
|
+
else
|
151
|
+
return nil
|
152
|
+
end
|
153
|
+
|
154
|
+
[key, method]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "crud_logger"
|
4
|
+
require_relative "constants"
|
5
|
+
|
6
|
+
module Rails
|
7
|
+
module Crud
|
8
|
+
module Tools
|
9
|
+
# The CrudOperations class is responsible for managing CRUD operations for different tables.
|
10
|
+
# It stores operations in a nested hash structure and provides methods to add and log operations.
|
11
|
+
class CrudOperations
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
attr_accessor :table_operations, :logs
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@table_operations = Hash.new do |hash, method|
|
18
|
+
hash[method] = Hash.new do |h, key|
|
19
|
+
h[key] = Hash.new do |hh, table|
|
20
|
+
hh[table] = []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_operation(method, key, table_name, operation)
|
27
|
+
# @table_operations[method]が存在しない場合は初期化
|
28
|
+
@table_operations[method] ||= {}
|
29
|
+
# @table_operations[method][key]が存在しない場合は初期化
|
30
|
+
@table_operations[method][key] ||= {}
|
31
|
+
# @table_operations[method][key][table_name]が存在しない場合は初期化
|
32
|
+
@table_operations[method][key][table_name] ||= []
|
33
|
+
|
34
|
+
@table_operations[method][key][table_name] << operation unless @table_operations[method][key][table_name].include?(operation)
|
35
|
+
end
|
36
|
+
|
37
|
+
def log_operations(method, key)
|
38
|
+
CrudLogger.logger.info "\nSummary: Method: #{method}, Key: #{key}"
|
39
|
+
|
40
|
+
@table_operations[method][key].each do |table_name, operations|
|
41
|
+
CrudLogger.logger.info "#{table_name} - #{operations.join(", ")}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def table_operations_present?(method, key)
|
46
|
+
return false if @table_operations[method].nil?
|
47
|
+
return false if @table_operations[method][key].nil?
|
48
|
+
|
49
|
+
!@table_operations[method][key].empty?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|