foreign_key_checker 0.1.1 → 0.4.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: 932660f1dc1e1054ce608b324b8eb2b9dda2f3200b9339106677d7ebf1297b87
4
- data.tar.gz: bf7e22d072393fafe2b3bd65639150448886efd3c5cdd95a3a4617cd4a99023f
3
+ metadata.gz: cf9685280dd2c9ad8232c3b4fe7d4f72dcc56ccdbbaf0b55d826792196185f05
4
+ data.tar.gz: de9a1d21793299c0df12e92b09fd2661b160d9794d6bb01a7080478fecc3db96
5
5
  SHA512:
6
- metadata.gz: 2280e4cff60369551661dc1999d11cdfa8c87b994f68360b6c09c05c2acb7619282f4435d0272ef6b2205b56553d14cc401410dd13d05eb1fdb2f224aff07ff5
7
- data.tar.gz: 1fbe7b6fd37d6f97f75c485085c81121213e4a9b450fc8aba300deaabb09f43cdea41899cf6292d4365360be0cd61f327e5415554bf1e5775d28915cba43571d
6
+ metadata.gz: 7f3b6138b99b780ed15ab03c43c0745bfc9511089e4fe0b4bb29cc962d232f7d942d3e314dc56bc8422e193631872d7415a5a28251281fad1bca704fb3c2ab6b
7
+ data.tar.gz: d7c3c677d063a6e2210d3627caac91431024e940b44e4e2b33c912f48c532a662c81678a1f5aa32f8cccffe19c3bdf0fbd7e10c13796f129de2dc9c60044705d
data/README.md CHANGED
@@ -5,6 +5,13 @@ This gem checks `belongs_to` ActiveRecord relations. It finds
5
5
  3. broken relations: if you try to join such relation (example: `City.joins(:country)`), there is an exception.
6
6
 
7
7
  ## Usage
8
+ Generate migration to fix foreign key problems
9
+ ```bash
10
+ bundle exec rails generate foreign_key_checker:migration
11
+ ```
12
+
13
+
14
+ Print information about foreign key problems
8
15
  ```bash
9
16
  bundle exec rake foreign_key_check
10
17
  ```
@@ -16,6 +23,12 @@ ForeignKeyChecker.check.each do |key, result|
16
23
  end
17
24
  ```
18
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
+
19
32
  ## Installation
20
33
  Add this line to your application's Gemfile:
21
34
 
data/Rakefile CHANGED
@@ -4,16 +4,6 @@ rescue LoadError
4
4
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
5
  end
6
6
 
7
- require 'rdoc/task'
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'ForeignKeyChecker'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.md')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
15
- end
16
-
17
7
  require 'bundler/gem_tasks'
18
8
 
19
9
  require 'rake/testtask'
@@ -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,12 +40,30 @@ 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
51
+ attr_reader :scope
40
52
  def message
41
53
  "There is no foreign_key for relation #{human_relation}\n"
42
54
  end
55
+
56
+ def migration
57
+ "add_foreign_key :#{from_table}, :#{to_table}#{
58
+ ", column: :#{from_column}" if from_column.to_s != "#{to_table.singularize}_id"
59
+ }#{
60
+ ", primary_key: :#{to_column}" if to_column.to_s != 'id'
61
+ }"
62
+ end
63
+
64
+ def to_zombie
65
+ ZombieResult.new(scope: scope, model: model, association: association)
66
+ end
43
67
  end
44
68
 
45
69
  class IndexResult < Result
@@ -59,7 +83,26 @@ module ForeignKeyChecker
59
83
  from_c = model.connection.quote_column_name(from_column)
60
84
  to_t = model.connection.quote_table_name(to_table)
61
85
  to_c = model.connection.quote_column_name(to_column)
62
- "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"
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"
87
+ "DELETE FROM #{from_t} WHERE #{from_c} IS NOT NULL AND #{from_c} NOT IN (SELECT #{to_c} FROM #{to_t})"
88
+ end
89
+
90
+ def set_null_sql
91
+ from_t = model.connection.quote_table_name(from_table)
92
+ from_c = model.connection.quote_column_name(from_column)
93
+ to_t = model.connection.quote_table_name(to_table)
94
+ to_c = model.connection.quote_column_name(to_column)
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"
96
+ "UPDATE #{from_t} SET #{from_c} = NULL WHERE #{from_c} IS NOT NULL AND #{from_c} NOT IN (SELECT #{to_c} FROM #{to_t})"
97
+ end
98
+
99
+ def set_null_migration
100
+ "execute('#{set_null_sql}')"
101
+ end
102
+
103
+ def migration(set_null: false)
104
+ return set_null_migration if set_null
105
+ "execute('#{delete_sql}')"
63
106
  end
64
107
 
65
108
  def message
@@ -120,15 +163,32 @@ module ForeignKeyChecker
120
163
 
121
164
  end
122
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
+
123
172
  def check_foreign_key_bt_association(model, association)
124
173
  return if model.name.starts_with?('HABTM_')
125
- related = association.klass
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
126
184
 
127
185
  column_name = model.connection.quote_column_name(association.foreign_key)
128
186
  scope = model.left_outer_joins(association.name).where(
129
187
  "#{related.quoted_table_name}.#{related.quoted_primary_key} IS NULL AND #{model.quoted_table_name}.#{column_name} IS NOT NULL"
130
188
  )
131
189
 
190
+ check_types(model, association)
191
+
132
192
  if zombies
133
193
  number = scope.count
134
194
  if number > 0
@@ -141,10 +201,12 @@ module ForeignKeyChecker
141
201
  end
142
202
  end
143
203
 
144
- 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
145
206
  @result[:foreign_keys] << ForeignKeyResult.new(
146
207
  model: model,
147
- association: association
208
+ association: association,
209
+ scope: scope,
148
210
  )
149
211
  end
150
212
 
@@ -154,7 +216,7 @@ module ForeignKeyChecker
154
216
  association: association,
155
217
  )
156
218
  end
157
- rescue ActiveRecord::InverseOfAssociationNotFoundError, ActiveRecord::StatementInvalid => error
219
+ rescue ActiveRecord::InverseOfAssociationNotFoundError, ActiveRecord::StatementInvalid, TypeMismatch => error
158
220
  @result[:broken] << BrokenRelationResult.new(
159
221
  model: model,
160
222
  association: association,
@@ -162,6 +224,13 @@ module ForeignKeyChecker
162
224
  )
163
225
  end
164
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
+
165
234
  def check
166
235
  Rails.application.eager_load!
167
236
  ActiveRecord::Base.descendants.each do |model|
@@ -169,11 +238,13 @@ module ForeignKeyChecker
169
238
  next if excluded_specification?(model)
170
239
 
171
240
  model.reflect_on_all_associations(:belongs_to).each do |association|
241
+
172
242
  if association.options[:polymorphic] && polymorphic_zombies
173
243
  check_polymorphic_bt_association(model, association)
174
244
  next
175
245
  end
176
246
 
247
+ next if already_done_fk?(model, association)
177
248
  check_foreign_key_bt_association(model, association)
178
249
  end
179
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
@@ -3,5 +3,8 @@ module ForeignKeyChecker
3
3
  rake_tasks do
4
4
  load 'tasks/foreign_key_checker_tasks.rake'
5
5
  end
6
+ generators do
7
+ require 'generators/foreign_key_checker/migration/migration_generator.rb'
8
+ end
6
9
  end
7
10
  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
@@ -1,3 +1,3 @@
1
1
  module ForeignKeyChecker
2
- VERSION = '0.1.1'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -0,0 +1,29 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ForeignKeyChecker
4
+ module Generators
5
+ class MigrationGenerator < ActiveRecord::Generators::Base
6
+ desc "generates the necessary migrations to fix foreign_key problems"
7
+ argument :name, type: :string, default: 'FixForeignKeys'
8
+
9
+ source_root File.expand_path('templates', __dir__)
10
+
11
+ def create_migrations
12
+ @class_name = name.camelcase.tr(':', '')
13
+ file_name = name.underscore.tr('/', '_')
14
+ @done = Dir.glob(Rails.root.join('db', 'migrate', '*.rb')).map do |path|
15
+ File.open(path).readlines.find { |line| line.include?('ActiveRecord::Migration') && line.include?(@class_name) }
16
+ end.compact.size
17
+ @checks = ForeignKeyChecker.check(zombies: false) unless @behavior == :revoke
18
+ if @behavior == :revoke
19
+ @file_suffix = "_v#{@done}" if @done > 1
20
+ @class_suffix = "V#{@done}" if @done > 1
21
+ else
22
+ @file_suffix = "_v#{@done + 1}" if @done > 0
23
+ @class_suffix = "V#{@done + 1}" if @done > 0
24
+ end
25
+ migration_template 'migrations/fix_foreign_keys.rb.erb', "db/migrate/#{file_name}#{@file_suffix}.rb"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ class <%= @class_name %><%= @class_suffix %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version.to_s %>]
2
+ def change
3
+ <% @checks[:foreign_keys].each do |fk| %>
4
+ reversible do |dir|
5
+ dir.up do
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
11
+ <%= fk.to_zombie.migration %>
12
+ <% end %>
13
+ end
14
+ end
15
+ <%= fk.migration %>
16
+ <% end %>
17
+ end
18
+ end
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.1.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - AnatolyShirykalov
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-22 00:00:00.000000000 Z
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,15 +147,20 @@ 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
155
+ - lib/generators/foreign_key_checker/migration/migration_generator.rb
156
+ - lib/generators/foreign_key_checker/migration/templates/migrations/fix_foreign_keys.rb.erb
54
157
  - lib/tasks/foreign_key_checker_tasks.rake
55
158
  homepage: https://gitlab.com/pipocavsobake/foreign_key_checker
56
159
  licenses:
57
160
  - MIT
58
161
  metadata:
59
162
  allowed_push_host: https://rubygems.org
60
- post_install_message:
163
+ post_install_message:
61
164
  rdoc_options: []
62
165
  require_paths:
63
166
  - lib
@@ -72,8 +175,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
175
  - !ruby/object:Gem::Version
73
176
  version: '0'
74
177
  requirements: []
75
- rubygems_version: 3.0.6
76
- signing_key:
178
+ rubygems_version: 3.1.3
179
+ signing_key:
77
180
  specification_version: 4
78
181
  summary: Find problems with relations in active_record models
79
182
  test_files: []