yeet_dba 0.1.1 → 0.1.2
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/.DS_Store +0 -0
- data/.rubocop +159 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +11 -2
- data/Gemfile.lock +118 -0
- data/README.md +105 -11
- data/Rakefile +3 -3
- data/bin/console +3 -3
- data/lib/.DS_Store +0 -0
- data/lib/generators/.DS_Store +0 -0
- data/lib/generators/yeet_dba/foreign_key_migration_generator.rb +7 -7
- data/lib/yeet_dba/Rakefile +5 -0
- data/{yeet_dba/ar_column.rb → lib/yeet_dba/column.rb} +8 -8
- data/lib/yeet_dba/missing_foreign_keys.rb +27 -0
- data/{yeet_dba → lib/yeet_dba/models}/foreign_key.rb +1 -1
- data/lib/yeet_dba/models/invalid_column.rb +20 -0
- data/lib/yeet_dba/railtie.rb +12 -0
- data/lib/yeet_dba/table.rb +67 -0
- data/lib/yeet_dba/tasks/add_foreign_keys.rake +20 -0
- data/lib/yeet_dba/tasks/bad_data/find_orphaned_rows.rake +55 -0
- data/lib/yeet_dba/verify_data.rb +37 -0
- data/lib/yeet_dba/version.rb +3 -0
- data/lib/yeet_dba.rb +8 -6
- data/yeet_dba.gemspec +25 -20
- metadata +77 -10
- data/lib/generators/yeet_dba/generator_helpers.rb +0 -5
- data/yeet_dba/ar_table.rb +0 -47
- data/yeet_dba/missing_foreign_keys.rb +0 -11
- data/yeet_dba/verify_data.rb +0 -35
- data/yeet_dba/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d312f90603469c805f7d69d49ae5ed3fe8f825ef64c0a324f74a49557934804
|
4
|
+
data.tar.gz: faf652821f509fee8db20df69f426ac5e80af5e4be0d8bea4a578ce2d4130839
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94aa0ebb9b45927eacb6ec29ce75678ea83fbbb26b48503d72cf77e9581e3d8e8d321f1b360ada978bd54aabd54d3cb94023241e61fd6968908f86def82e0b8c
|
7
|
+
data.tar.gz: fb1e94beb1c6a44d9debac764069cbdfceecf490c3f5d5fc24396a0f4c5529b5b065cf8575a698db0c9e11193df01a00065070ba7ae01180ebbb6ec31f08644d
|
data/.DS_Store
CHANGED
Binary file
|
data/.rubocop
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
AllCops:
|
2
|
+
DisplayCopNames: true
|
3
|
+
TargetRubyVersion: 2.5
|
4
|
+
TargetRailsVersion: 5.1
|
5
|
+
|
6
|
+
Rails:
|
7
|
+
Enabled: true
|
8
|
+
|
9
|
+
Style/FormatStringToken:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Style/FrozenStringLiteralComment:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
Metrics/LineLength:
|
16
|
+
Max: 100
|
17
|
+
|
18
|
+
Metrics/AbcSize:
|
19
|
+
Max: 40
|
20
|
+
|
21
|
+
Rails/SkipsModelValidations:
|
22
|
+
Enabled: true
|
23
|
+
|
24
|
+
Style/GuardClause:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Style/AsciiComments:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Metrics/MethodLength:
|
31
|
+
Max: 25
|
32
|
+
|
33
|
+
Metrics/CyclomaticComplexity:
|
34
|
+
Max: 7
|
35
|
+
|
36
|
+
Metrics/PerceivedComplexity:
|
37
|
+
Max: 8
|
38
|
+
|
39
|
+
# Relaxed.Ruby.Style
|
40
|
+
|
41
|
+
Style/Alias:
|
42
|
+
Enabled: false
|
43
|
+
StyleGuide: http://relaxed.ruby.style/#stylealias
|
44
|
+
|
45
|
+
Metrics/BlockLength:
|
46
|
+
Enabled: true
|
47
|
+
|
48
|
+
Style/BeginBlock:
|
49
|
+
Enabled: false
|
50
|
+
StyleGuide: http://relaxed.ruby.style/#stylebeginblock
|
51
|
+
|
52
|
+
Style/BlockDelimiters:
|
53
|
+
Enabled: false
|
54
|
+
StyleGuide: http://relaxed.ruby.style/#styleblockdelimiters
|
55
|
+
|
56
|
+
Style/Documentation:
|
57
|
+
Enabled: false
|
58
|
+
StyleGuide: http://relaxed.ruby.style/#styledocumentation
|
59
|
+
|
60
|
+
Layout/DotPosition:
|
61
|
+
Enabled: false
|
62
|
+
StyleGuide: http://relaxed.ruby.style/#styledotposition
|
63
|
+
|
64
|
+
Style/DoubleNegation:
|
65
|
+
Enabled: false
|
66
|
+
StyleGuide: http://relaxed.ruby.style/#styledoublenegation
|
67
|
+
|
68
|
+
Style/EndBlock:
|
69
|
+
Enabled: false
|
70
|
+
StyleGuide: http://relaxed.ruby.style/#styleendblock
|
71
|
+
|
72
|
+
Style/FormatString:
|
73
|
+
Enabled: false
|
74
|
+
StyleGuide: http://relaxed.ruby.style/#styleformatstring
|
75
|
+
|
76
|
+
Style/IfUnlessModifier:
|
77
|
+
Enabled: false
|
78
|
+
StyleGuide: http://relaxed.ruby.style/#styleifunlessmodifier
|
79
|
+
|
80
|
+
Style/Lambda:
|
81
|
+
Enabled: false
|
82
|
+
StyleGuide: http://relaxed.ruby.style/#stylelambda
|
83
|
+
|
84
|
+
Style/ModuleFunction:
|
85
|
+
Enabled: false
|
86
|
+
StyleGuide: http://relaxed.ruby.style/#stylemodulefunction
|
87
|
+
|
88
|
+
Style/MultilineBlockChain:
|
89
|
+
Enabled: false
|
90
|
+
StyleGuide: http://relaxed.ruby.style/#stylemultilineblockchain
|
91
|
+
|
92
|
+
Style/NegatedIf:
|
93
|
+
Enabled: false
|
94
|
+
StyleGuide: http://relaxed.ruby.style/#stylenegatedif
|
95
|
+
|
96
|
+
Style/NegatedWhile:
|
97
|
+
Enabled: false
|
98
|
+
StyleGuide: http://relaxed.ruby.style/#stylenegatedwhile
|
99
|
+
|
100
|
+
Style/ParallelAssignment:
|
101
|
+
Enabled: false
|
102
|
+
StyleGuide: http://relaxed.ruby.style/#styleparallelassignment
|
103
|
+
|
104
|
+
Style/PercentLiteralDelimiters:
|
105
|
+
Enabled: false
|
106
|
+
StyleGuide: http://relaxed.ruby.style/#stylepercentliteraldelimiters
|
107
|
+
|
108
|
+
Style/PerlBackrefs:
|
109
|
+
Enabled: false
|
110
|
+
StyleGuide: http://relaxed.ruby.style/#styleperlbackrefs
|
111
|
+
|
112
|
+
Style/Semicolon:
|
113
|
+
Enabled: false
|
114
|
+
StyleGuide: http://relaxed.ruby.style/#stylesemicolon
|
115
|
+
|
116
|
+
Style/SignalException:
|
117
|
+
Enabled: false
|
118
|
+
StyleGuide: http://relaxed.ruby.style/#stylesignalexception
|
119
|
+
|
120
|
+
Style/SingleLineBlockParams:
|
121
|
+
Enabled: false
|
122
|
+
StyleGuide: http://relaxed.ruby.style/#stylesinglelineblockparams
|
123
|
+
|
124
|
+
Style/SingleLineMethods:
|
125
|
+
Enabled: false
|
126
|
+
StyleGuide: http://relaxed.ruby.style/#stylesinglelinemethods
|
127
|
+
|
128
|
+
Layout/SpaceBeforeBlockBraces:
|
129
|
+
Enabled: false
|
130
|
+
StyleGuide: http://relaxed.ruby.style/#stylespacebeforeblockbraces
|
131
|
+
|
132
|
+
Layout/SpaceInsideParens:
|
133
|
+
Enabled: false
|
134
|
+
StyleGuide: http://relaxed.ruby.style/#stylespaceinsideparens
|
135
|
+
|
136
|
+
Style/SpecialGlobalVars:
|
137
|
+
Enabled: false
|
138
|
+
StyleGuide: http://relaxed.ruby.style/#stylespecialglobalvars
|
139
|
+
|
140
|
+
Style/TrailingCommaInArrayLiteral:
|
141
|
+
Enabled: false
|
142
|
+
|
143
|
+
Style/TrailingCommaInHashLiteral:
|
144
|
+
Enabled: false
|
145
|
+
|
146
|
+
Style/WhileUntilModifier:
|
147
|
+
Enabled: false
|
148
|
+
StyleGuide: http://relaxed.ruby.style/#stylewhileuntilmodifier
|
149
|
+
|
150
|
+
Style/RegexpLiteral:
|
151
|
+
Enabled: false
|
152
|
+
|
153
|
+
Lint/AmbiguousRegexpLiteral:
|
154
|
+
Enabled: false
|
155
|
+
StyleGuide: http://relaxed.ruby.style/#lintambiguousregexpliteral
|
156
|
+
|
157
|
+
Lint/AssignmentInCondition:
|
158
|
+
Enabled: false
|
159
|
+
StyleGuide: http://relaxed.ruby.style/#lintassignmentincondition
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
# 0.1.2
|
2
|
+
|
3
|
+
## Major changes
|
4
|
+
|
5
|
+
Added rake task to find invalid data
|
6
|
+
Added rake task to nullify and delete invalid data
|
7
|
+
|
8
|
+
## Minor changes
|
9
|
+
|
10
|
+
Add rubocop
|
11
|
+
Add rspec tests
|
12
|
+
|
13
|
+
# 0.1.1
|
14
|
+
|
15
|
+
Patch bug with skipping invalid columns that have orphaned data
|
16
|
+
|
1
17
|
# 0.1.0
|
2
18
|
|
3
19
|
Initial release
|
data/Gemfile
CHANGED
@@ -1,6 +1,15 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
3
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in yeet_dba.gemspec
|
6
6
|
gemspec
|
7
|
+
|
8
|
+
gem 'actionpack'
|
9
|
+
gem 'activerecord'
|
10
|
+
gem 'railties'
|
11
|
+
gem 'rspec-rails'
|
12
|
+
gem 'sqlite3'
|
13
|
+
gem 'test-unit'
|
14
|
+
|
15
|
+
gem 'pry', require: true
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
yeet_dba (0.1.2)
|
5
|
+
actionpack (>= 3.0, < 6.0)
|
6
|
+
activerecord (>= 3.0, < 6.0)
|
7
|
+
railties (>= 3.0, < 6.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
actionpack (5.2.3)
|
13
|
+
actionview (= 5.2.3)
|
14
|
+
activesupport (= 5.2.3)
|
15
|
+
rack (~> 2.0)
|
16
|
+
rack-test (>= 0.6.3)
|
17
|
+
rails-dom-testing (~> 2.0)
|
18
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
19
|
+
actionview (5.2.3)
|
20
|
+
activesupport (= 5.2.3)
|
21
|
+
builder (~> 3.1)
|
22
|
+
erubi (~> 1.4)
|
23
|
+
rails-dom-testing (~> 2.0)
|
24
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
25
|
+
activemodel (5.2.3)
|
26
|
+
activesupport (= 5.2.3)
|
27
|
+
activerecord (5.2.3)
|
28
|
+
activemodel (= 5.2.3)
|
29
|
+
activesupport (= 5.2.3)
|
30
|
+
arel (>= 9.0)
|
31
|
+
activesupport (5.2.3)
|
32
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
33
|
+
i18n (>= 0.7, < 2)
|
34
|
+
minitest (~> 5.1)
|
35
|
+
tzinfo (~> 1.1)
|
36
|
+
arel (9.0.0)
|
37
|
+
builder (3.2.3)
|
38
|
+
coderay (1.1.2)
|
39
|
+
concurrent-ruby (1.1.5)
|
40
|
+
crass (1.0.4)
|
41
|
+
diff-lcs (1.3)
|
42
|
+
erubi (1.8.0)
|
43
|
+
i18n (1.6.0)
|
44
|
+
concurrent-ruby (~> 1.0)
|
45
|
+
loofah (2.2.3)
|
46
|
+
crass (~> 1.0.2)
|
47
|
+
nokogiri (>= 1.5.9)
|
48
|
+
method_source (0.9.2)
|
49
|
+
mini_portile2 (2.4.0)
|
50
|
+
minitest (5.11.3)
|
51
|
+
nokogiri (1.10.2)
|
52
|
+
mini_portile2 (~> 2.4.0)
|
53
|
+
power_assert (1.1.4)
|
54
|
+
pry (0.12.2)
|
55
|
+
coderay (~> 1.1.0)
|
56
|
+
method_source (~> 0.9.0)
|
57
|
+
rack (2.0.6)
|
58
|
+
rack-test (1.1.0)
|
59
|
+
rack (>= 1.0, < 3)
|
60
|
+
rails-dom-testing (2.0.3)
|
61
|
+
activesupport (>= 4.2.0)
|
62
|
+
nokogiri (>= 1.6)
|
63
|
+
rails-html-sanitizer (1.0.4)
|
64
|
+
loofah (~> 2.2, >= 2.2.2)
|
65
|
+
railties (5.2.3)
|
66
|
+
actionpack (= 5.2.3)
|
67
|
+
activesupport (= 5.2.3)
|
68
|
+
method_source
|
69
|
+
rake (>= 0.8.7)
|
70
|
+
thor (>= 0.19.0, < 2.0)
|
71
|
+
rake (10.5.0)
|
72
|
+
rspec (3.8.0)
|
73
|
+
rspec-core (~> 3.8.0)
|
74
|
+
rspec-expectations (~> 3.8.0)
|
75
|
+
rspec-mocks (~> 3.8.0)
|
76
|
+
rspec-core (3.8.0)
|
77
|
+
rspec-support (~> 3.8.0)
|
78
|
+
rspec-expectations (3.8.2)
|
79
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
80
|
+
rspec-support (~> 3.8.0)
|
81
|
+
rspec-mocks (3.8.0)
|
82
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
83
|
+
rspec-support (~> 3.8.0)
|
84
|
+
rspec-rails (3.8.2)
|
85
|
+
actionpack (>= 3.0)
|
86
|
+
activesupport (>= 3.0)
|
87
|
+
railties (>= 3.0)
|
88
|
+
rspec-core (~> 3.8.0)
|
89
|
+
rspec-expectations (~> 3.8.0)
|
90
|
+
rspec-mocks (~> 3.8.0)
|
91
|
+
rspec-support (~> 3.8.0)
|
92
|
+
rspec-support (3.8.0)
|
93
|
+
sqlite3 (1.4.0)
|
94
|
+
test-unit (3.3.1)
|
95
|
+
power_assert
|
96
|
+
thor (0.20.3)
|
97
|
+
thread_safe (0.3.6)
|
98
|
+
tzinfo (1.2.5)
|
99
|
+
thread_safe (~> 0.1)
|
100
|
+
|
101
|
+
PLATFORMS
|
102
|
+
ruby
|
103
|
+
|
104
|
+
DEPENDENCIES
|
105
|
+
actionpack
|
106
|
+
activerecord
|
107
|
+
bundler (~> 1.17)
|
108
|
+
pry
|
109
|
+
railties
|
110
|
+
rake (~> 10.0)
|
111
|
+
rspec (~> 3.0)
|
112
|
+
rspec-rails
|
113
|
+
sqlite3
|
114
|
+
test-unit
|
115
|
+
yeet_dba!
|
116
|
+
|
117
|
+
BUNDLED WITH
|
118
|
+
1.17.3
|
data/README.md
CHANGED
@@ -1,12 +1,26 @@
|
|
1
1
|

|
2
2
|
|
3
3
|
# yeet_dba - find missing foreign key constraints
|
4
|
+
[](https://badge.fury.io/rb/yeet_dba) <a href="https://codeclimate.com/github/KevinColemanInc/yeet_dba/maintainability"><img src="https://api.codeclimate.com/v1/badges/a0baa6373d4be7f0d630/maintainability" /></a>[](https://travis-ci.com/KevinColemanInc/yeet_dba)
|
4
5
|
|
5
6
|
yeet_dba scans your rails tables for missing foreign key constraints. If there are no dangling records, it will create a migration to add the foreign key constraints on all the table it is safe.
|
6
7
|
|
7
8
|
If you have dangling migrations, check the generator logs to see where you have invalid orphaned rows. Orphaned row meaning a row with an id that doesn't exist in the associated table.
|
8
9
|
|
9
|
-
|
10
|
+
### But why should I use foreign keys?
|
11
|
+
|
12
|
+
You can save yourself an N+1 call by checking if the id has a value instead of loading up the object.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
user.company.id # bad - N+1
|
16
|
+
user.company_id # good
|
17
|
+
```
|
18
|
+
|
19
|
+
But this doesn't work if you don't nullify the `company_id` when the company is deleted. Foreign key constraints prevent you from deleting a record without cleaning out the associated tables.
|
20
|
+
|
21
|
+
### But what is the difference between yeet_db and [lol_dba](https://github.com/plentz/lol_dba)?
|
22
|
+
|
23
|
+
lol_dba will only add indexes for RoR models. yeet_dba looks at every table (including join tables) to add foreign key constraints, which also add indexes.
|
10
24
|
|
11
25
|
## Installation
|
12
26
|
|
@@ -22,6 +36,8 @@ And then execute:
|
|
22
36
|
|
23
37
|
## Usage
|
24
38
|
|
39
|
+
### Foriegn keys migration
|
40
|
+
|
25
41
|
This probably should run against the production database so you can know if there are dangling records. If there are records with a value, but not the corresponding table does not have an id, then the migration will fail.
|
26
42
|
|
27
43
|
```
|
@@ -30,27 +46,97 @@ $ RAILS_ENV=production rails g yeet_dba:foreign_key_migration
|
|
30
46
|
|
31
47
|
This will create a new migration with for every foreign_key that can safely be added without running into orphaned data errors. We also warn you if active_record models that are missing association declarations (`has_many`, `belongs_to`, etc.)`
|
32
48
|
|
33
|
-
`WARNING - cannot find association for alternative_housings . supplier_id | suppliers`
|
49
|
+
`WARNING - cannot find an association for alternative_housings . supplier_id | suppliers`
|
34
50
|
|
35
|
-
We also warn if we have tables that don't have existing models attached to them. This can be safe to ignore
|
51
|
+
We also warn if we have tables that don't have existing models attached to them. This can be safe to ignore because join tables on many to many relations don't need models, but ideally, everything should have an AR model backing it.
|
36
52
|
|
37
|
-
`WARNING - cannot find model for alternative_housings . supplier_id | suppliers`
|
53
|
+
`WARNING - cannot find a model for alternative_housings . supplier_id | suppliers`
|
38
54
|
|
39
55
|
Finnally, if there is a table that we think should have a foreign key constraint, but there are dangling values we warn you against that too.
|
40
56
|
|
41
57
|
`WARNING - orphaned rows alternative_housings . supplier_id | suppliers`
|
42
58
|
|
43
|
-
|
59
|
+
### Invalid rows
|
60
|
+
|
61
|
+
If a row has an id, but there doesn't exist an id the expected associated table, then the row has bad data and should either be fixed by nulling the orphaned row or assigning it to an existing row.
|
62
|
+
|
63
|
+
This rake task will scan every column for orphaned rows.
|
64
|
+
|
65
|
+
```
|
66
|
+
$ RAILS_ENV=production rake yeet_dba:find_invalid_columns
|
67
|
+
```
|
68
|
+
|
69
|
+
Sample output:
|
70
|
+
|
71
|
+
```
|
72
|
+
---RESULTS---
|
73
|
+
|
74
|
+
🚨Houston, we have a problem 🚨. We found 1 invalid column.
|
75
|
+
|
76
|
+
-> notifications.primary_image_id
|
77
|
+
Invalid rows: 83
|
78
|
+
Foreign table: active_storage_attachments
|
79
|
+
|
80
|
+
This query should return no results:
|
81
|
+
SELECT "notifications".* FROM "notifications" left join active_storage_attachments as association_table on association_table.id = notifications.primary_image_id WHERE "notifications"."primary_image_id" IS NOT NULL AND (association_table.id is null)
|
82
|
+
|
83
|
+
```
|
84
|
+
|
85
|
+
### Fix invalid rows
|
86
|
+
|
87
|
+
If a row has an id, but there doesn't exist an id the expected associated table, then the row has bad data and should either be fixed by nulling the orphaned row or assigning it to an existing row.
|
88
|
+
|
89
|
+
This rake task will scan every column for orphaned rows.
|
90
|
+
|
91
|
+
```
|
92
|
+
$ RAILS_ENV=production rake yeet_dba:fix_invalid_columns
|
93
|
+
```
|
94
|
+
|
95
|
+
Sample output:
|
96
|
+
|
97
|
+
```
|
98
|
+
---RESULTS---
|
99
|
+
|
100
|
+
🚨Houston, we have a problem 🚨. We found 1 invalid column.
|
101
|
+
|
102
|
+
-> notifications.primary_image_id
|
103
|
+
Invalid rows: 83
|
104
|
+
Foreign table: active_storage_attachments
|
44
105
|
|
45
|
-
|
106
|
+
This query should return no results:
|
107
|
+
SELECT "notifications".* FROM "notifications" left join active_storage_attachments as association_table on association_table.id = notifications.primary_image_id WHERE "notifications"."primary_image_id" IS NOT NULL AND (association_table.id is null)
|
108
|
+
|
109
|
+
```
|
110
|
+
|
111
|
+
### Add missing foriegn keys as a rake task
|
112
|
+
|
113
|
+
You might want to add foreign keys outside of your regular deployment flow in case there are failures and deployment would be blocked by bad data. This would be especially obnoxious for MySql users since you can't rollback migrations.
|
114
|
+
|
115
|
+
```
|
116
|
+
$ RAILS_ENV=production rake yeet_dba:add_foreign_keys
|
117
|
+
```
|
118
|
+
|
119
|
+
Sample output
|
120
|
+
|
121
|
+
```
|
122
|
+
ERROR - users . profile_id failed to add key
|
123
|
+
```
|
124
|
+
|
125
|
+
This rake task is idempotent (safe to run as many times as you need).
|
126
|
+
|
127
|
+
## Compatibility
|
128
|
+
|
129
|
+
- Rails 5.2 (but it may work with 5.0+)
|
130
|
+
- Ruby 2.4+
|
46
131
|
|
47
132
|
## Road map to v1
|
48
133
|
|
49
|
-
- [
|
50
|
-
- [
|
51
|
-
- [
|
52
|
-
- [
|
134
|
+
- [x] rspec tests
|
135
|
+
- [x] add rake task identify all dangling records
|
136
|
+
- [x] add rake task to automatically nullify or destroy dangling records
|
137
|
+
- [x] run adding foreign keys as rake task instead of generating a migration
|
53
138
|
- [ ] support "soft delete" gems
|
139
|
+
- [ ] Use rails associations to find columns that should be "not null" to [improve performance](https://stackoverflow.com/questions/1017239/how-do-null-values-affect-performance-in-a-database-search)
|
54
140
|
|
55
141
|
|
56
142
|
## Development
|
@@ -72,4 +158,12 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
72
158
|
Everyone interacting in the YeetDb project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/kevincolemaninc/yeet_dba/blob/master/CODE_OF_CONDUCT.md).
|
73
159
|
|
74
160
|
## Logo design attribute
|
75
|
-
Foreign Key by Ary Prasetyo from the Noun Project
|
161
|
+
Foreign Key by Ary Prasetyo from the Noun Project
|
162
|
+
|
163
|
+
## Thanks
|
164
|
+
|
165
|
+
[AvoVietnam - Chat with Vietnamese](https://www.avovietnam.com)
|
166
|
+
|
167
|
+
## Author
|
168
|
+
|
169
|
+
Kevin Coleman, [https://kcoleman.me/](https://kcoleman.me)
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'yeet_dba'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +10,5 @@ require "yeet_dbaa"
|
|
10
10
|
# require "pry"
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start(__FILE__)
|
data/lib/.DS_Store
CHANGED
Binary file
|
Binary file
|
@@ -4,11 +4,11 @@ module YeetDba
|
|
4
4
|
# Custom scaffolding generator
|
5
5
|
class ForeignKeyMigrationGenerator < Rails::Generators::Base
|
6
6
|
include Rails::Generators::Migration
|
7
|
-
source_root File.expand_path('
|
8
|
-
desc
|
7
|
+
source_root File.expand_path('templates', __dir__)
|
8
|
+
desc 'Generates migration for adding foreign key constraints.'
|
9
9
|
|
10
10
|
def copy_migration_and_spec_files
|
11
|
-
migration_template
|
11
|
+
migration_template 'add_foreign_keys_yeet_dba.rb',
|
12
12
|
migration_file,
|
13
13
|
migration_version: migration_version
|
14
14
|
end
|
@@ -16,15 +16,15 @@ module YeetDba
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def migration_file
|
19
|
-
File.join(
|
19
|
+
File.join('db/migrate', 'add_foreign_keys_yeet_dba.rb')
|
20
20
|
end
|
21
21
|
|
22
22
|
def migration_version
|
23
23
|
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
24
24
|
end
|
25
25
|
|
26
|
-
def self.next_migration_number(
|
27
|
-
Time.now.utc.strftime(
|
26
|
+
def self.next_migration_number(_path)
|
27
|
+
Time.now.utc.strftime('%Y%m%d%H%M%S%L')
|
28
28
|
end
|
29
29
|
end
|
30
|
-
end
|
30
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
module YeetDba
|
2
|
-
class
|
3
|
-
attr_accessor :
|
2
|
+
class Column
|
3
|
+
attr_accessor :db_column, :table_name, :tables
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(db_column:, table_name:, tables:)
|
6
|
+
@db_column = db_column
|
7
7
|
@table_name = table_name
|
8
8
|
@tables = tables
|
9
9
|
end
|
10
10
|
|
11
11
|
def is_association?
|
12
|
-
|
12
|
+
db_column.name =~ /_id\z/
|
13
13
|
end
|
14
14
|
|
15
15
|
def association_klass
|
16
|
-
|
16
|
+
association&.klass
|
17
17
|
end
|
18
18
|
|
19
19
|
def association_table_name
|
@@ -21,7 +21,7 @@ module YeetDba
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def association_name
|
24
|
-
|
24
|
+
db_column.name.gsub(/_id\z/, '')
|
25
25
|
end
|
26
26
|
|
27
27
|
def model
|
@@ -37,7 +37,7 @@ module YeetDba
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def foreign_key_exists?
|
40
|
-
ActiveRecord::Migration.foreign_key_exists?(table_name, column:
|
40
|
+
ActiveRecord::Migration.foreign_key_exists?(table_name, column: db_column.name)
|
41
41
|
end
|
42
42
|
|
43
43
|
def guessed_table_name
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module YeetDba
|
2
|
+
class MissingForeignKeys
|
3
|
+
def self.foreign_keys
|
4
|
+
eager_load!
|
5
|
+
tables.map do |table_name|
|
6
|
+
Table.new(table_name: table_name,
|
7
|
+
tables: tables).missing_keys
|
8
|
+
end.flatten
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.invalid_columns
|
12
|
+
eager_load!
|
13
|
+
tables.map do |table_name|
|
14
|
+
Table.new(table_name: table_name,
|
15
|
+
tables: tables).invalid_columns
|
16
|
+
end.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.eager_load!
|
20
|
+
Rails.application.eager_load! if defined?(Rails) && !Rails.env.test?
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.tables
|
24
|
+
ActiveRecord::Base.connection.tables
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module YeetDba
|
2
|
+
class InvalidColumn
|
3
|
+
attr_accessor :table_name,
|
4
|
+
:column,
|
5
|
+
:verify_data
|
6
|
+
|
7
|
+
def initialize(table_name:, column:, verify_data:)
|
8
|
+
@table_name = table_name
|
9
|
+
@column = column
|
10
|
+
@verify_data = verify_data
|
11
|
+
end
|
12
|
+
|
13
|
+
delegate :association_table_name, :db_column, :association, to: :column
|
14
|
+
delegate :orphaned_rows_count, :query, to: :verify_data
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"#{table_name} . #{db_column.name} has #{orphaned_rows_count} invalid rows with foreign table #{association_table_name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module YeetDba
|
2
|
+
class Table
|
3
|
+
attr_accessor :table_name, :tables
|
4
|
+
|
5
|
+
def initialize(table_name:, tables:)
|
6
|
+
@table_name = table_name
|
7
|
+
@tables = tables
|
8
|
+
end
|
9
|
+
|
10
|
+
def invalid_columns
|
11
|
+
missing_keys_array = []
|
12
|
+
columns.each do |db_column|
|
13
|
+
column = Column.new(db_column: db_column, table_name: table_name, tables: tables)
|
14
|
+
next unless column.is_association?
|
15
|
+
next if column.polymorphic_association?
|
16
|
+
next if column.foreign_key_exists?
|
17
|
+
next if column.association_table_name.blank?
|
18
|
+
verify_data = VerifyData.new(column: column)
|
19
|
+
next unless verify_data.orphaned_rows?
|
20
|
+
|
21
|
+
invalid_column = InvalidColumn.new(table_name: table_name,
|
22
|
+
column: column,
|
23
|
+
verify_data: verify_data)
|
24
|
+
missing_keys_array.push(invalid_column)
|
25
|
+
|
26
|
+
end
|
27
|
+
missing_keys_array
|
28
|
+
end
|
29
|
+
|
30
|
+
def missing_keys
|
31
|
+
missing_keys_array = []
|
32
|
+
columns.each do |db_column|
|
33
|
+
column = Column.new(db_column: db_column, table_name: table_name, tables: tables)
|
34
|
+
next unless column.is_association?
|
35
|
+
|
36
|
+
unless column.model
|
37
|
+
puts "WARNING - cannot find a model for #{table_name} . #{db_column.name} | #{column&.association_table_name}"
|
38
|
+
end
|
39
|
+
|
40
|
+
unless column.association
|
41
|
+
puts "WARNING - cannot find an association for #{table_name} . #{db_column.name} | #{column&.association_table_name}"
|
42
|
+
end
|
43
|
+
|
44
|
+
next if column.polymorphic_association?
|
45
|
+
next if column.foreign_key_exists?
|
46
|
+
next if column.association_table_name.blank?
|
47
|
+
|
48
|
+
if VerifyData.new(column: column).orphaned_rows?
|
49
|
+
puts "YeetDba - orphaned rows. Skipping #{table_name} . #{db_column.name} | #{column&.association_table_name}"
|
50
|
+
next
|
51
|
+
end
|
52
|
+
|
53
|
+
foreign_key = ForeignKey.new(table_a: table_name,
|
54
|
+
table_b: column&.association_table_name,
|
55
|
+
column: db_column.name)
|
56
|
+
missing_keys_array.push(foreign_key)
|
57
|
+
end
|
58
|
+
missing_keys_array
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def columns
|
64
|
+
ActiveRecord::Base.connection.columns(table_name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
namespace :yeet_dba do
|
2
|
+
desc 'Add foreign keys in a rake migration'
|
3
|
+
task add_foreign_keys: :environment do
|
4
|
+
foreign_keys = YeetDba::MissingForeignKeys.foreign_keys
|
5
|
+
|
6
|
+
puts "Trying to add #{foreign_keys.length}"
|
7
|
+
puts
|
8
|
+
foreign_keys.each do |foreign_key|
|
9
|
+
|
10
|
+
begin
|
11
|
+
ActiveRecord::Migration.add_foreign_key(foreign_key.table_a,
|
12
|
+
foreign_key.table_b,
|
13
|
+
column: foreign_key.column)
|
14
|
+
|
15
|
+
rescue ActiveRecord::InvalidForeignKey
|
16
|
+
puts "ERROR - #{foreign_key.table_a} . #{foreign_key.column} failed to add key"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
namespace :yeet_dba do
|
2
|
+
desc 'Show all of the tables.columns with bad data'
|
3
|
+
task find_invalid_columns: :environment do
|
4
|
+
columns = YeetDba::MissingForeignKeys.invalid_columns
|
5
|
+
puts
|
6
|
+
puts '---RESULTS---'
|
7
|
+
puts
|
8
|
+
if columns.empty?
|
9
|
+
puts 'All good here. 👍'
|
10
|
+
else
|
11
|
+
puts "🚨Houston, we have a problem 🚨. We found #{columns.length} invalid column#{columns.length == 1 ? '' : 's'}."
|
12
|
+
puts
|
13
|
+
columns.each do |invalid_column|
|
14
|
+
puts "-> #{invalid_column.table}.#{invalid_column.column}"
|
15
|
+
puts "Invalid rows: #{invalid_column.orphaned_rows_count}"
|
16
|
+
puts "Foreign table: #{invalid_column.association_table_name}"
|
17
|
+
puts
|
18
|
+
puts 'This query should return no results:'
|
19
|
+
puts invalid_column.query
|
20
|
+
puts
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Set all of the rows to null if there is bad data'
|
26
|
+
task nullify_or_destroy_invalid_rows: :environment do
|
27
|
+
columns = YeetDba::MissingForeignKeys.invalid_columns
|
28
|
+
next puts "Your data looks good!" if columns.empty?
|
29
|
+
|
30
|
+
columns.each do |column|
|
31
|
+
puts column.to_s
|
32
|
+
end
|
33
|
+
puts
|
34
|
+
puts "WARNING - THIS MAY CAUSE PERM DATA LOSS"
|
35
|
+
puts
|
36
|
+
puts "I am going to give you 8s to change your mind"
|
37
|
+
sleep 8
|
38
|
+
puts "ok, here we go..."
|
39
|
+
sleep 1
|
40
|
+
|
41
|
+
columns.each do |invalid_column|
|
42
|
+
required = invalid_column.column.association.options&.key?(:optional) ? !invalid_column.column.association.options[:optional] : invalid_column.column.model.belongs_to_required_by_default
|
43
|
+
nullable = invalid_column.column.db_column.null
|
44
|
+
if required
|
45
|
+
# delete
|
46
|
+
invalid_column.verify_data.orphaned_rows.destroy_all
|
47
|
+
elsif nullable
|
48
|
+
# null it out
|
49
|
+
invalid_column.verify_data.orphaned_rows.update_all(invalid_column.db_column.name => nil)
|
50
|
+
else
|
51
|
+
puts "WARNING - #{invalid_column.table_name} . #{invalid_column.db_column.name} is not nullable"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
module YeetDba
|
4
|
+
class VerifyData
|
5
|
+
attr_accessor :column
|
6
|
+
|
7
|
+
def initialize(column:)
|
8
|
+
@column = column
|
9
|
+
end
|
10
|
+
|
11
|
+
def orphaned_rows?
|
12
|
+
orphaned_rows.first
|
13
|
+
end
|
14
|
+
|
15
|
+
def orphaned_rows_count
|
16
|
+
orphaned_rows.count
|
17
|
+
end
|
18
|
+
|
19
|
+
def query
|
20
|
+
orphaned_rows.to_sql
|
21
|
+
end
|
22
|
+
|
23
|
+
def orphaned_rows
|
24
|
+
association = column.association
|
25
|
+
|
26
|
+
column_name = column.db_column.name
|
27
|
+
table_name = column.table_name
|
28
|
+
association_table = column.association_table_name
|
29
|
+
model = column.model
|
30
|
+
|
31
|
+
# Check to see there could be rows with bad data
|
32
|
+
model.joins("left join #{association_table} as association_table on association_table.id = #{table_name}.#{column_name}")
|
33
|
+
.where.not(column_name => nil)
|
34
|
+
.where('association_table.id is null')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/yeet_dba.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
1
|
+
require 'yeet_dba/version'
|
2
|
+
require 'yeet_dba/table'
|
3
|
+
require 'yeet_dba/missing_foreign_keys'
|
4
|
+
require 'yeet_dba/verify_data'
|
5
|
+
require 'yeet_dba/railtie' if defined?(Rails)
|
6
|
+
require 'yeet_dba/column'
|
7
|
+
require 'yeet_dba/models/foreign_key'
|
8
|
+
require 'yeet_dba/models/invalid_column'
|
7
9
|
|
8
10
|
module YeetDba
|
9
11
|
class Error < StandardError; end
|
data/yeet_dba.gemspec
CHANGED
@@ -1,40 +1,45 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path("../lib", __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require 'yeet_dba/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
6
|
+
spec.name = 'yeet_dba'
|
8
7
|
spec.version = YeetDba::VERSION
|
9
|
-
spec.
|
10
|
-
spec.
|
8
|
+
spec.platform = Gem::Platform::RUBY
|
9
|
+
spec.authors = ['Kevin Coleman']
|
10
|
+
spec.email = ['kevin.coleman@sparkstart.io']
|
11
11
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
12
|
+
spec.summary = 'Generates foreign key constraint migrations for rails databases'
|
13
|
+
spec.description = "This scan every ActiveRecord model looking for relationships ('has_many', 'belongs_to', etc.) and adds foreign key constraints."
|
14
14
|
spec.homepage = 'http://rubygems.org/gems/yeet_dba'
|
15
|
-
spec.license =
|
15
|
+
spec.license = 'MIT'
|
16
16
|
|
17
17
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
18
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
19
|
if spec.respond_to?(:metadata)
|
20
|
-
spec.metadata[
|
21
|
-
spec.metadata[
|
22
|
-
spec.metadata[
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/kevincolemaninc/yeet_dba'
|
22
|
+
spec.metadata['changelog_uri'] = 'https://github.com/kevincolemaninc/yeet_dba/master/CHANGELOG.md'
|
23
23
|
else
|
24
|
-
raise
|
25
|
-
|
24
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
25
|
+
'public gem pushes.'
|
26
26
|
end
|
27
27
|
|
28
28
|
# Specify which files should be added to the gem when it is released.
|
29
29
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
30
|
-
spec.files
|
30
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
31
31
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
32
32
|
end
|
33
|
-
spec.bindir =
|
33
|
+
spec.bindir = 'exe'
|
34
34
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
35
|
-
spec.require_paths = [
|
35
|
+
spec.require_paths = ['lib']
|
36
|
+
|
37
|
+
spec.add_development_dependency 'bundler', '~> 1.17'
|
38
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
39
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
36
40
|
|
37
|
-
spec.
|
38
|
-
spec.
|
39
|
-
spec.
|
41
|
+
spec.required_ruby_version = '>= 2.4.0'
|
42
|
+
spec.add_dependency 'actionpack', '>= 3.0', '< 6.0'
|
43
|
+
spec.add_dependency 'activerecord', '>= 3.0', '< 6.0'
|
44
|
+
spec.add_dependency 'railties', '>= 3.0', '< 6.0'
|
40
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yeet_dba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Coleman
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,66 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: actionpack
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
- - "<"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '6.0'
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '3.0'
|
72
|
+
- - "<"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '6.0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: activerecord
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.0'
|
82
|
+
- - "<"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '6.0'
|
85
|
+
type: :runtime
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '3.0'
|
92
|
+
- - "<"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '6.0'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: railties
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '3.0'
|
102
|
+
- - "<"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '6.0'
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '3.0'
|
112
|
+
- - "<"
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '6.0'
|
55
115
|
description: This scan every ActiveRecord model looking for relationships ('has_many',
|
56
116
|
'belongs_to', etc.) and adds foreign key constraints.
|
57
117
|
email:
|
@@ -63,28 +123,35 @@ files:
|
|
63
123
|
- ".DS_Store"
|
64
124
|
- ".gitignore"
|
65
125
|
- ".rspec"
|
126
|
+
- ".rubocop"
|
66
127
|
- ".travis.yml"
|
67
128
|
- CHANGELOG.md
|
68
129
|
- CODE_OF_CONDUCT.md
|
69
130
|
- Gemfile
|
131
|
+
- Gemfile.lock
|
70
132
|
- LICENSE.txt
|
71
133
|
- README.md
|
72
134
|
- Rakefile
|
73
135
|
- bin/console
|
74
136
|
- bin/setup
|
75
137
|
- lib/.DS_Store
|
138
|
+
- lib/generators/.DS_Store
|
76
139
|
- lib/generators/yeet_dba/foreign_key_migration_generator.rb
|
77
|
-
- lib/generators/yeet_dba/generator_helpers.rb
|
78
140
|
- lib/generators/yeet_dba/templates/add_foreign_keys_yeet_dba.rb
|
79
141
|
- lib/yeet_dba.rb
|
142
|
+
- lib/yeet_dba/Rakefile
|
143
|
+
- lib/yeet_dba/column.rb
|
144
|
+
- lib/yeet_dba/missing_foreign_keys.rb
|
145
|
+
- lib/yeet_dba/models/foreign_key.rb
|
146
|
+
- lib/yeet_dba/models/invalid_column.rb
|
147
|
+
- lib/yeet_dba/railtie.rb
|
148
|
+
- lib/yeet_dba/table.rb
|
149
|
+
- lib/yeet_dba/tasks/add_foreign_keys.rake
|
150
|
+
- lib/yeet_dba/tasks/bad_data/find_orphaned_rows.rake
|
151
|
+
- lib/yeet_dba/verify_data.rb
|
152
|
+
- lib/yeet_dba/version.rb
|
80
153
|
- yeet_dba.gemspec
|
81
154
|
- yeet_dba.png
|
82
|
-
- yeet_dba/ar_column.rb
|
83
|
-
- yeet_dba/ar_table.rb
|
84
|
-
- yeet_dba/foreign_key.rb
|
85
|
-
- yeet_dba/missing_foreign_keys.rb
|
86
|
-
- yeet_dba/verify_data.rb
|
87
|
-
- yeet_dba/version.rb
|
88
155
|
homepage: http://rubygems.org/gems/yeet_dba
|
89
156
|
licenses:
|
90
157
|
- MIT
|
@@ -100,7 +167,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
100
167
|
requirements:
|
101
168
|
- - ">="
|
102
169
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
170
|
+
version: 2.4.0
|
104
171
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
172
|
requirements:
|
106
173
|
- - ">="
|
data/yeet_dba/ar_table.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
module YeetDba
|
2
|
-
class ArTable
|
3
|
-
attr_accessor :table_name, :tables
|
4
|
-
|
5
|
-
def initialize(table_name:, tables:)
|
6
|
-
@table_name = table_name
|
7
|
-
@tables = tables
|
8
|
-
end
|
9
|
-
|
10
|
-
def missing_keys
|
11
|
-
missing_keys_array = []
|
12
|
-
columns.each do |column_name|
|
13
|
-
column = ArColumn.new(column_name: column_name, table_name: table_name, tables: tables)
|
14
|
-
next unless column.is_association?
|
15
|
-
|
16
|
-
unless column.model
|
17
|
-
puts "YeetDba - cannot find model for #{table_name} . #{column_name.name} | #{column&.association_table_name}"
|
18
|
-
end
|
19
|
-
|
20
|
-
unless column.association
|
21
|
-
puts "YeetDba - cannot find association for #{table_name} . #{column_name.name} | #{column&.association_table_name}"
|
22
|
-
end
|
23
|
-
|
24
|
-
next if column.polymorphic_association?
|
25
|
-
next if column.foreign_key_exists?
|
26
|
-
next if column.association_table_name.blank?
|
27
|
-
|
28
|
-
if VerifyData.new(column: column).orphaned_rows?
|
29
|
-
puts "YeetDba - orphaned rows. Skipping #{table_name} . #{column_name.name} | #{column&.association_table_name}"
|
30
|
-
next
|
31
|
-
end
|
32
|
-
|
33
|
-
foreign_key = ForeignKey.new(table_a: table_name,
|
34
|
-
table_b: column&.association_table_name,
|
35
|
-
column: column_name.name)
|
36
|
-
missing_keys_array.push(foreign_key)
|
37
|
-
end
|
38
|
-
missing_keys_array
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def columns
|
44
|
-
ActiveRecord::Base.connection.columns(table_name)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,11 +0,0 @@
|
|
1
|
-
module YeetDba
|
2
|
-
class MissingForeignKeys
|
3
|
-
def self.foreign_keys
|
4
|
-
Rails.application.eager_load!
|
5
|
-
tables = ActiveRecord::Base.connection.tables
|
6
|
-
tables.map do |table_name|
|
7
|
-
ArTable.new(table_name: table_name, tables: tables).missing_keys
|
8
|
-
end.flatten
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
data/yeet_dba/verify_data.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
module YeetDba
|
2
|
-
class VerifyData
|
3
|
-
attr_accessor :column
|
4
|
-
|
5
|
-
def initialize(column:)
|
6
|
-
@column = column
|
7
|
-
end
|
8
|
-
|
9
|
-
def orphaned_rows?
|
10
|
-
orphaned_rows.first
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
def orphaned_rows
|
16
|
-
association = column.association
|
17
|
-
binding.pry if column.column_name.is_a?(String)
|
18
|
-
|
19
|
-
column_name = column.column_name.name
|
20
|
-
table_name = column.table_name
|
21
|
-
association_table = column.association_table_name
|
22
|
-
model = column.model
|
23
|
-
|
24
|
-
# Check to see there could be rows with bad data
|
25
|
-
bad_ids = model.joins("left join #{association_table} as association_table on association_table.id = #{table_name}.#{column_name}")
|
26
|
-
.where.not(column_name => nil)
|
27
|
-
.where('association_table.id is null')
|
28
|
-
.pluck(:id)
|
29
|
-
|
30
|
-
# select the rows with the invalid ids
|
31
|
-
# AR doesn't support joins with update, so the bad_ids are selected in a different query (above ^)
|
32
|
-
model.where(id: bad_ids)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
data/yeet_dba/version.rb
DELETED