foreign_key_checker 0.2.0 → 0.4.1
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/README.md +6 -0
- data/lib/foreign_key_checker.rb +43 -5
- data/lib/foreign_key_checker/checkers/relations.rb +122 -0
- data/lib/foreign_key_checker/checkers/tables.rb +88 -0
- data/lib/foreign_key_checker/utils.rb +126 -0
- data/lib/foreign_key_checker/version.rb +1 -1
- data/lib/generators/foreign_key_checker/migration/migration_generator.rb +1 -1
- data/lib/generators/foreign_key_checker/migration/templates/migrations/fix_foreign_keys.rb.erb +6 -1
- metadata +107 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7420be9666625e34337a8a40eefd53f36acc704b2e222d563bd5811d9b5cc7b
|
4
|
+
data.tar.gz: 5abb9fc0b244a85a61022fe24f7810cb39c497197caa8c526d5099fb043add9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76a2abcb77030614a6c0a7b9c8951273796ca1ed09ef0c07ee75ec0e222a7bd8ad636d1409ee3e852e9cdfb37c93a7c8f4dee7ce433ade8a6c842a46a55ee95f
|
7
|
+
data.tar.gz: 400ef91dba5578aa3e84b96875a2317066501ec8b49994c9287606e5c77e8e57b02f09d0860d38fab465d3e90c4754e43a39e0d1abda4a86be46f200821bdd65
|
data/README.md
CHANGED
@@ -23,6 +23,12 @@ ForeignKeyChecker.check.each do |key, result|
|
|
23
23
|
end
|
24
24
|
```
|
25
25
|
|
26
|
+
Get general information about foreign keys
|
27
|
+
```ruby
|
28
|
+
ForeignKeyChecker::Utils.get_foreign_keys_hash
|
29
|
+
# => {"users"=>[#<ForeignKeyChecker::Utils::Result:0x00005645e51756e8 @from_table="user_rating_changes", @from_column="user_id", @to_table="users", @to_column="id">]}
|
30
|
+
```
|
31
|
+
|
26
32
|
## Installation
|
27
33
|
Add this line to your application's Gemfile:
|
28
34
|
|
data/lib/foreign_key_checker.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
require "foreign_key_checker/railtie"
|
2
|
+
require 'foreign_key_checker/utils'
|
3
|
+
module ForeignKeyChecker::Checkers
|
4
|
+
end
|
5
|
+
require 'foreign_key_checker/checkers/relations'
|
6
|
+
require 'foreign_key_checker/checkers/tables'
|
2
7
|
|
3
8
|
module ForeignKeyChecker
|
9
|
+
class TypeMismatch < StandardError; end
|
4
10
|
class Result
|
5
11
|
attr_reader :model, :association
|
6
12
|
def initialize(data)
|
@@ -34,6 +40,11 @@ module ForeignKeyChecker
|
|
34
40
|
def inspect
|
35
41
|
"#<#{self.class.name}:#{self.object_id} #{message}>"
|
36
42
|
end
|
43
|
+
|
44
|
+
def nullable?
|
45
|
+
model.columns_hash[from_column.to_s].null
|
46
|
+
end
|
47
|
+
|
37
48
|
end
|
38
49
|
|
39
50
|
class ForeignKeyResult < Result
|
@@ -73,7 +84,7 @@ module ForeignKeyChecker
|
|
73
84
|
to_t = model.connection.quote_table_name(to_table)
|
74
85
|
to_c = model.connection.quote_column_name(to_column)
|
75
86
|
#"DELETE #{from_t} FROM #{from_t}.#{from_c} LEFT OUTER JOIN #{to_t} ON #{to_t}.#{to_c} = #{from_t}.#{from_c} WHERE #{to_t}.#{to_c} IS NULL"
|
76
|
-
"DELETE FROM #{from_t} WHERE #{from_c} IS NOT NULL AND #{from_c} NOT IN (SELECT #{to_c} FROM #{to_t})"
|
87
|
+
"DELETE FROM #{from_t} WHERE #{from_c} IS NOT NULL AND #{from_c} NOT IN (SELECT * FROM (SELECT #{to_c} FROM #{to_t}) AS t )"
|
77
88
|
end
|
78
89
|
|
79
90
|
def set_null_sql
|
@@ -82,7 +93,7 @@ module ForeignKeyChecker
|
|
82
93
|
to_t = model.connection.quote_table_name(to_table)
|
83
94
|
to_c = model.connection.quote_column_name(to_column)
|
84
95
|
#"UPDATE #{from_t} SET #{from_t}.#{from_c} = NULL FROM #{from_t} LEFT OUTER JOIN #{to_t} ON #{to_t}.#{to_c} = #{from_t}.#{from_c} WHERE #{to_t}.#{to_c} IS NULL"
|
85
|
-
"UPDATE #{from_t} SET #{from_c} = NULL WHERE #{from_c} IS NOT NULL AND #{from_c} NOT IN (SELECT #{to_c} FROM #{to_t})"
|
96
|
+
"UPDATE #{from_t} SET #{from_c} = NULL WHERE #{from_c} IS NOT NULL AND #{from_c} NOT IN (SELECT * FROM (SELECT #{to_c} FROM #{to_t}) AS t )"
|
86
97
|
end
|
87
98
|
|
88
99
|
def set_null_migration
|
@@ -152,15 +163,32 @@ module ForeignKeyChecker
|
|
152
163
|
|
153
164
|
end
|
154
165
|
|
166
|
+
def check_types(model, association)
|
167
|
+
type_from = model.columns_hash[association.foreign_key.to_s].sql_type
|
168
|
+
type_to = association.klass.columns_hash[association.klass.primary_key.to_s].sql_type
|
169
|
+
raise TypeMismatch, "TypeMissMatch for relation #{model}##{association.name} #{type_from} != #{type_to}" if type_from != type_to
|
170
|
+
end
|
171
|
+
|
155
172
|
def check_foreign_key_bt_association(model, association)
|
156
173
|
return if model.name.starts_with?('HABTM_')
|
157
|
-
|
174
|
+
begin
|
175
|
+
related = association.klass
|
176
|
+
rescue NameError => error
|
177
|
+
@result[:broken] << BrokenRelationResult.new(
|
178
|
+
model: model,
|
179
|
+
association: association,
|
180
|
+
error: error,
|
181
|
+
)
|
182
|
+
return
|
183
|
+
end
|
158
184
|
|
159
185
|
column_name = model.connection.quote_column_name(association.foreign_key)
|
160
186
|
scope = model.left_outer_joins(association.name).where(
|
161
187
|
"#{related.quoted_table_name}.#{related.quoted_primary_key} IS NULL AND #{model.quoted_table_name}.#{column_name} IS NOT NULL"
|
162
188
|
)
|
163
189
|
|
190
|
+
check_types(model, association)
|
191
|
+
|
164
192
|
if zombies
|
165
193
|
number = scope.count
|
166
194
|
if number > 0
|
@@ -173,7 +201,8 @@ module ForeignKeyChecker
|
|
173
201
|
end
|
174
202
|
end
|
175
203
|
|
176
|
-
if foreign_keys && !model.connection.foreign_key_exists?(model.table_name, related.table_name)
|
204
|
+
if foreign_keys && !model.connection.foreign_key_exists?(model.table_name, related.table_name, column: association.foreign_key, primary_key: related.primary_key)
|
205
|
+
scope.first
|
177
206
|
@result[:foreign_keys] << ForeignKeyResult.new(
|
178
207
|
model: model,
|
179
208
|
association: association,
|
@@ -187,7 +216,7 @@ module ForeignKeyChecker
|
|
187
216
|
association: association,
|
188
217
|
)
|
189
218
|
end
|
190
|
-
rescue ActiveRecord::InverseOfAssociationNotFoundError, ActiveRecord::StatementInvalid => error
|
219
|
+
rescue ActiveRecord::InverseOfAssociationNotFoundError, ActiveRecord::StatementInvalid, TypeMismatch => error
|
191
220
|
@result[:broken] << BrokenRelationResult.new(
|
192
221
|
model: model,
|
193
222
|
association: association,
|
@@ -195,6 +224,13 @@ module ForeignKeyChecker
|
|
195
224
|
)
|
196
225
|
end
|
197
226
|
|
227
|
+
def already_done_fk?(model, association)
|
228
|
+
@_done ||= {}
|
229
|
+
ret = @_done[[model.table_name, association.foreign_key]]
|
230
|
+
@_done[[model.table_name, association.foreign_key]] = true
|
231
|
+
ret
|
232
|
+
end
|
233
|
+
|
198
234
|
def check
|
199
235
|
Rails.application.eager_load!
|
200
236
|
ActiveRecord::Base.descendants.each do |model|
|
@@ -202,11 +238,13 @@ module ForeignKeyChecker
|
|
202
238
|
next if excluded_specification?(model)
|
203
239
|
|
204
240
|
model.reflect_on_all_associations(:belongs_to).each do |association|
|
241
|
+
|
205
242
|
if association.options[:polymorphic] && polymorphic_zombies
|
206
243
|
check_polymorphic_bt_association(model, association)
|
207
244
|
next
|
208
245
|
end
|
209
246
|
|
247
|
+
next if already_done_fk?(model, association)
|
210
248
|
check_foreign_key_bt_association(model, association)
|
211
249
|
end
|
212
250
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'foreign_key_checker/utils'
|
2
|
+
|
3
|
+
module ForeignKeyChecker::Checkers::Relations
|
4
|
+
class Result
|
5
|
+
attr_reader :ok, :model, :association, :fk, :error, :table
|
6
|
+
def initialize(**args)
|
7
|
+
%i[model association ok error fk table].each do |key|
|
8
|
+
instance_variable_set("@#{key}", args[key])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
class JoinResult < Result
|
13
|
+
def message
|
14
|
+
"expect { #{model.to_s}.joins(:#{association.name}).first }.to_not raise_exception" if !ok
|
15
|
+
end
|
16
|
+
end
|
17
|
+
class ErrorResult < Result
|
18
|
+
def message
|
19
|
+
"`#{model.to_s}##{association.name}` is broken\n#{error.class.to_s}\n#{error.message}\n#{error.backtrace.join("\n")}" if !ok
|
20
|
+
end
|
21
|
+
end
|
22
|
+
class HasOneOrManyResult < Result
|
23
|
+
def message
|
24
|
+
"expected has_many or has_one association for #{fk.inspect}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
class HasOneOrManyDependentResult < Result
|
28
|
+
def message
|
29
|
+
"expected has_many or has_one association with dependent option for #{fk.inspect}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
class NoModelResult < Result
|
33
|
+
def message
|
34
|
+
"expected find model for table #{table}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
def self.check_by_join
|
38
|
+
Rails.application.eager_load!
|
39
|
+
models = ActiveRecord::Base.descendants
|
40
|
+
models.each_with_object([]) do |model, results|
|
41
|
+
next if model.to_s.include?('HABTM')
|
42
|
+
model.reflect_on_all_associations.each do |association|
|
43
|
+
begin
|
44
|
+
next if association.options[:polymorphic]
|
45
|
+
next if association.scope && association.scope.is_a?(Proc) && association.scope.arity > 0
|
46
|
+
next if model.connection_specification_name != association.klass.connection_specification_name
|
47
|
+
|
48
|
+
model.joins(association.name).first
|
49
|
+
result = JoinResult.new(model: model, association: association, ok: true)
|
50
|
+
yield result if block_given?
|
51
|
+
results << result
|
52
|
+
rescue => e
|
53
|
+
result = JoinResult.new(model: model, association: association, ok: false, error: e)
|
54
|
+
yield result if block_given?
|
55
|
+
results << result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.check_by_fks
|
62
|
+
Rails.application.eager_load!
|
63
|
+
models = ActiveRecord::Base.descendants.group_by(&:table_name)
|
64
|
+
check_relation = proc do |model, fk, association|
|
65
|
+
begin
|
66
|
+
pk = association.options[:primary_key] || model.primary_key
|
67
|
+
association.klass.table_name.to_s == fk.from_table.to_s && association.foreign_key.to_s == fk.from_column.to_s && pk.to_s == fk.to_column.to_s
|
68
|
+
rescue => e
|
69
|
+
if block_given?
|
70
|
+
yield ErrorResult.new(model: model, association: association, fk: fk, ok: false, error: e)
|
71
|
+
else
|
72
|
+
p model
|
73
|
+
p fk
|
74
|
+
p association
|
75
|
+
raise e
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
ForeignKeyChecker::Utils.get_foreign_keys_hash.each do |to_table, fks|
|
80
|
+
fks.each do |fk|
|
81
|
+
unless models.key?(fk.to_table)
|
82
|
+
result = NoModelResult.new(ok: false, table: fk.to_table)
|
83
|
+
if block_given?
|
84
|
+
yield result
|
85
|
+
else
|
86
|
+
raise result.message
|
87
|
+
end
|
88
|
+
next
|
89
|
+
end
|
90
|
+
models[fk.to_table].each do |model|
|
91
|
+
next if model.connection_specification_name != 'primary'
|
92
|
+
ok = false
|
93
|
+
ok ||= !!model.reflect_on_all_associations(:has_many).find do |association|
|
94
|
+
check_relation.call(model, fk, association)
|
95
|
+
end
|
96
|
+
ok ||= !!model.reflect_on_all_associations(:has_one).find do |association|
|
97
|
+
check_relation.call(model, fk, association)
|
98
|
+
end
|
99
|
+
if block_given?
|
100
|
+
yield HasOneOrManyResult.new(model: model, fk: fk, ok: ok)
|
101
|
+
else
|
102
|
+
raise "expected has_many or has_one association for #{fk.inspect}" if !ok
|
103
|
+
end
|
104
|
+
next if !ok
|
105
|
+
|
106
|
+
dep = false
|
107
|
+
dep ||= !!model.reflect_on_all_associations(:has_many).find do |association|
|
108
|
+
check_relation.call(model, fk, association) && association.options[:dependent]
|
109
|
+
end
|
110
|
+
dep ||= !!model.reflect_on_all_associations(:has_one).find do |association|
|
111
|
+
check_relation.call(model, fk, association) && association.options[:dependent]
|
112
|
+
end
|
113
|
+
if block_given?
|
114
|
+
yield HasOneOrManyDependentResult.new(model: model, fk: fk, ok: ok)
|
115
|
+
else
|
116
|
+
raise "expected has_many or has_one association with dependent option for #{fk.inspect}" if !dep
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'foreign_key_checker/utils'
|
2
|
+
|
3
|
+
# Стоит использовать в тех случаях, когда подключается rails к уже существующей базе
|
4
|
+
# Можно проверить, для каких таблиц всё ещё нет ActiveRecord-моделей
|
5
|
+
module ForeignKeyChecker::Checkers::Tables
|
6
|
+
SPECIAL_TABLES = ['schema_migrations', 'ar_internal_metadata'].freeze
|
7
|
+
# Список таблиц, не описанных моделями (в том числе HABTM-моделями, сгенерированными автоматически rails)
|
8
|
+
def self.without_models(specification_name = 'primary')
|
9
|
+
Rails.application.eager_load!
|
10
|
+
|
11
|
+
actual_tables = ForeignKeyChecker::Utils.get_tables
|
12
|
+
actual_tables - modelised_tables(specification_name) - SPECIAL_TABLES
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.without_foreign_keys(specification_name = 'primary')
|
16
|
+
all_fks = ForeignKeyChecker::Utils.get_foreign_keys
|
17
|
+
results = []
|
18
|
+
models(specification_name).each do |model|
|
19
|
+
model.column_names.each do |column_name|
|
20
|
+
next unless column_name.ends_with?('_id')
|
21
|
+
next if all_fks.find { |fk| fk.from_table = model.table_name && fk.from_column == column_name }
|
22
|
+
|
23
|
+
table_name = column_name.delete_suffix('_id')
|
24
|
+
results << ["#{table_name}.#{column_name}"]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.modelised_tables(specification_name = 'primary')
|
30
|
+
models(specification_name).map(&:table_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.models(specification_name = 'primary')
|
34
|
+
ActiveRecord::Base.descendants.select do |model|
|
35
|
+
model.connection_specification_name == specification_name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def self.common_tables
|
41
|
+
ForeignKeyChecker::Utils.get_tables - SPECIAL_TABLES
|
42
|
+
end
|
43
|
+
|
44
|
+
class Result
|
45
|
+
attr_reader :table_name, :foreign_keys, :internal_references
|
46
|
+
def initialize(**args)
|
47
|
+
%i[table_name foreign_keys internal_references].each do |key|
|
48
|
+
instance_variable_set("@#{key}", args[key] || args[key].to_s)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def ok?
|
53
|
+
foreign_keys.blank?
|
54
|
+
end
|
55
|
+
|
56
|
+
def referenced?
|
57
|
+
foreign_keys.present?
|
58
|
+
end
|
59
|
+
|
60
|
+
def ext_ref?
|
61
|
+
!internal_references
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.check
|
67
|
+
tables = without_models
|
68
|
+
fks = ForeignKeyChecker::Utils.get_foreign_keys_hash
|
69
|
+
fks.default = []
|
70
|
+
tables.map do |table|
|
71
|
+
Result.new(
|
72
|
+
table_name: table,
|
73
|
+
foreign_keys: fks[table] || [],
|
74
|
+
internal_references: (fks[table].map(&:from_table) - tables).empty?,
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# TODO вспомнить, что я тут задумал
|
80
|
+
def self.ordered(results = check)
|
81
|
+
return results if results.size == 0
|
82
|
+
oks = results.select(&:ok?)
|
83
|
+
if oks.size == 0
|
84
|
+
raise "no ok"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module ForeignKeyChecker
|
2
|
+
module Utils
|
3
|
+
class Result
|
4
|
+
attr_reader :from_table, :to_table, :from_column, :to_column
|
5
|
+
def initialize(args)
|
6
|
+
args.each { |k, v| instance_variable_set("@#{k}", v) }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
class UnsupportedConnectionAdapter < StandardError; end
|
10
|
+
def self.get_foreign_keys(model = ActiveRecord::Base)
|
11
|
+
adapter = model.connection_config[:adapter]
|
12
|
+
raise(UnsupportedConnectionAdapter, adapter) unless %w[postgresql mysql2 sqlserver sqlite3].include?(adapter)
|
13
|
+
|
14
|
+
connection = model.connection
|
15
|
+
send("get_#{adapter}_foreign_keys", connection)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.get_foreign_keys_hash(model = ActiveRecord::Base)
|
19
|
+
get_foreign_keys(model).to_a.each_with_object({}) do |datum, obj|
|
20
|
+
obj[datum.to_table] ||= []
|
21
|
+
obj[datum.to_table].push(datum)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.get_sqlite3_foreign_keys(connection)
|
26
|
+
res = connection.select_all <<-SQL
|
27
|
+
SELECT
|
28
|
+
m.name as from_table,
|
29
|
+
p."from" as from_column,
|
30
|
+
p."table" as to_table,
|
31
|
+
p."to" as to_column
|
32
|
+
FROM
|
33
|
+
sqlite_master m
|
34
|
+
JOIN pragma_foreign_key_list(m.name) p ON 1
|
35
|
+
WHERE m.type = 'table'
|
36
|
+
ORDER BY m.name ;
|
37
|
+
SQL
|
38
|
+
res.to_a.map{|i| Result.new(i) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.get_mysql2_foreign_keys(connection)
|
42
|
+
res = connection.select_all <<-SQL
|
43
|
+
SELECT
|
44
|
+
fks.TABLE_NAME AS from_table,
|
45
|
+
fks.COLUMN_NAME AS from_column,
|
46
|
+
fks.REFERENCED_TABLE_NAME AS to_table,
|
47
|
+
fks.REFERENCED_COLUMN_NAME AS to_column
|
48
|
+
FROM information_schema.KEY_COLUMN_USAGE AS fks
|
49
|
+
INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS rules ON rules.CONSTRAINT_NAME = fks.CONSTRAINT_NAME
|
50
|
+
WHERE
|
51
|
+
fks.CONSTRAINT_SCHEMA = DATABASE()
|
52
|
+
AND rules.CONSTRAINT_SCHEMA = DATABASE();
|
53
|
+
SQL
|
54
|
+
res.to_a.map{|i| Result.new(i) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.get_postgresql_foreign_keys(connection)
|
58
|
+
res = connection.select_all <<-SQL
|
59
|
+
SELECT
|
60
|
+
tc.table_name AS from_table,
|
61
|
+
kcu.column_name AS from_column,
|
62
|
+
ccu.table_name AS to_table,
|
63
|
+
ccu.column_name AS to_column
|
64
|
+
FROM
|
65
|
+
information_schema.table_constraints AS tc
|
66
|
+
JOIN information_schema.key_column_usage AS kcu
|
67
|
+
ON tc.constraint_name = kcu.constraint_name
|
68
|
+
AND tc.table_schema = kcu.table_schema
|
69
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
70
|
+
ON ccu.constraint_name = tc.constraint_name
|
71
|
+
AND ccu.table_schema = tc.table_schema
|
72
|
+
WHERE tc.constraint_type = 'FOREIGN KEY';
|
73
|
+
SQL
|
74
|
+
res.to_a.map{ |i| Result.new(i) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.get_sqlserver_foreign_keys(connection)
|
78
|
+
res = connection.select_all <<-SQL
|
79
|
+
SELECT obj.name AS FK_NAME,
|
80
|
+
sch.name AS [schema_name],
|
81
|
+
tab1.name AS [from_table],
|
82
|
+
col1.name AS [from_column],
|
83
|
+
tab2.name AS [to_table],
|
84
|
+
col2.name AS [to_column]
|
85
|
+
FROM sys.foreign_key_columns fkc
|
86
|
+
INNER JOIN sys.objects obj
|
87
|
+
ON obj.object_id = fkc.constraint_object_id
|
88
|
+
INNER JOIN sys.tables tab1
|
89
|
+
ON tab1.object_id = fkc.parent_object_id
|
90
|
+
INNER JOIN sys.schemas sch
|
91
|
+
ON tab1.schema_id = sch.schema_id
|
92
|
+
INNER JOIN sys.columns col1
|
93
|
+
ON col1.column_id = parent_column_id AND col1.object_id = tab1.object_id
|
94
|
+
INNER JOIN sys.tables tab2
|
95
|
+
ON tab2.object_id = fkc.referenced_object_id
|
96
|
+
INNER JOIN sys.columns col2
|
97
|
+
ON col2.column_id = referenced_column_id AND col2.object_id = tab2.object_id
|
98
|
+
SQL
|
99
|
+
res.to_a.map { |i| Result.new(i) }
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.get_tables(model = ActiveRecord::Base)
|
103
|
+
adapter = model.connection_config[:adapter]
|
104
|
+
raise(UnsupportedConnectionAdapter, adapter) unless %w[postgresql mysql2 sqlite3 sqlserver].include?(adapter)
|
105
|
+
|
106
|
+
connection = model.connection
|
107
|
+
send("get_#{adapter}_tables", connection)
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.get_mysql2_tables(connection)
|
111
|
+
connection.select_all("SELECT table_name FROM information_schema.tables WHERE TABLE_SCHEMA = '#{connection.current_database}'").to_a.pluck('table_name')
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.get_postgresql_tables(connection)
|
115
|
+
connection.select_all("SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname != 'pg_catalog' AND schemaname != 'information_schema'").to_a.pluck('tablename')
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.get_sqlite3_tables(connection)
|
119
|
+
connection.select_all("SELECT name FROM sqlite_master WHERE type='table'").to_a.pluck('name') - ['sqlite_sequence']
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.get_sqlserver_tables(connection)
|
123
|
+
connection.tables
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -14,7 +14,7 @@ module ForeignKeyChecker
|
|
14
14
|
@done = Dir.glob(Rails.root.join('db', 'migrate', '*.rb')).map do |path|
|
15
15
|
File.open(path).readlines.find { |line| line.include?('ActiveRecord::Migration') && line.include?(@class_name) }
|
16
16
|
end.compact.size
|
17
|
-
@checks = ForeignKeyChecker.check(zombies: false
|
17
|
+
@checks = ForeignKeyChecker.check(zombies: false) unless @behavior == :revoke
|
18
18
|
if @behavior == :revoke
|
19
19
|
@file_suffix = "_v#{@done}" if @done > 1
|
20
20
|
@class_suffix = "V#{@done}" if @done > 1
|
data/lib/generators/foreign_key_checker/migration/templates/migrations/fix_foreign_keys.rb.erb
CHANGED
@@ -3,8 +3,13 @@ class <%= @class_name %><%= @class_suffix %> < ActiveRecord::Migration[<%= Activ
|
|
3
3
|
<% @checks[:foreign_keys].each do |fk| %>
|
4
4
|
reversible do |dir|
|
5
5
|
dir.up do
|
6
|
-
|
6
|
+
<% if fk.nullable? %>
|
7
|
+
# <%= fk.to_zombie.migration %>
|
8
|
+
<%= fk.to_zombie.migration(set_null: true) %>
|
9
|
+
<% else %>
|
10
|
+
# column <%= fk.from_column %> can't be set to null
|
7
11
|
<%= fk.to_zombie.migration %>
|
12
|
+
<% end %>
|
8
13
|
end
|
9
14
|
end
|
10
15
|
<%= fk.migration %>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreign_key_checker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- AnatolyShirykalov
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -38,6 +38,104 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pg
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
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: mysql2
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
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: tiny_tds
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
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: activerecord-sqlserver-adapter
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
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: irb
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
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: e2mmap
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: annotate
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
41
139
|
description: Run task to obtain problems with your database
|
42
140
|
email:
|
43
141
|
- pipocavsobake@gmail.com
|
@@ -49,7 +147,10 @@ files:
|
|
49
147
|
- README.md
|
50
148
|
- Rakefile
|
51
149
|
- lib/foreign_key_checker.rb
|
150
|
+
- lib/foreign_key_checker/checkers/relations.rb
|
151
|
+
- lib/foreign_key_checker/checkers/tables.rb
|
52
152
|
- lib/foreign_key_checker/railtie.rb
|
153
|
+
- lib/foreign_key_checker/utils.rb
|
53
154
|
- lib/foreign_key_checker/version.rb
|
54
155
|
- lib/generators/foreign_key_checker/migration/migration_generator.rb
|
55
156
|
- lib/generators/foreign_key_checker/migration/templates/migrations/fix_foreign_keys.rb.erb
|
@@ -59,7 +160,7 @@ licenses:
|
|
59
160
|
- MIT
|
60
161
|
metadata:
|
61
162
|
allowed_push_host: https://rubygems.org
|
62
|
-
post_install_message:
|
163
|
+
post_install_message:
|
63
164
|
rdoc_options: []
|
64
165
|
require_paths:
|
65
166
|
- lib
|
@@ -74,8 +175,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
175
|
- !ruby/object:Gem::Version
|
75
176
|
version: '0'
|
76
177
|
requirements: []
|
77
|
-
rubygems_version: 3.
|
78
|
-
signing_key:
|
178
|
+
rubygems_version: 3.1.3
|
179
|
+
signing_key:
|
79
180
|
specification_version: 4
|
80
181
|
summary: Find problems with relations in active_record models
|
81
182
|
test_files: []
|