foreign_key_checker 0.1.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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: []