schema_linter 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +52 -0
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +61 -0
- data/Rakefile +7 -0
- data/lib/mysql_keywords.txt +741 -0
- data/lib/postgresql_keywords.txt +658 -0
- data/lib/rails_avoid_columns.txt +38 -0
- data/lib/rails_avoid_tables.txt +1 -0
- data/lib/schema_linter.rb +83 -0
- data/lib/tasks/schema_linter.rake +20 -0
- data/schema_linter.gemspec +24 -0
- data/spec/app.rb +34 -0
- data/spec/database.yml +18 -0
- data/spec/schema_linter_spec.rb +55 -0
- data/spec/spec_helper.rb +15 -0
- metadata +122 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
class SchemaLinter
|
2
|
+
VERSION = Gem.loaded_specs['schema_linter'].version.to_s
|
3
|
+
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
rake_tasks do
|
6
|
+
load File.join(File.dirname(__FILE__), "tasks/schema_linter.rake")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@error_table_names = (config["error_table_names"] || []).map { |v| Regexp.new(v) }
|
12
|
+
@error_column_names = (config["error_column_names"] || []).map { |v| Regexp.new(v) }
|
13
|
+
@ignore_table_names = (config["ignore_table_names"] || []).map { |v| Regexp.new(v) }
|
14
|
+
@ignore_column_names = (config["ignore_column_names"] || []).map { |v| Regexp.new(v) }
|
15
|
+
@rails_reserved_table_names = load_keywords("#{root_dir}/lib/rails_avoid_tables.txt")
|
16
|
+
@rails_reserved_column_names = load_keywords("#{root_dir}/lib/rails_avoid_columns.txt")
|
17
|
+
@postgresql_reserved_keywords = load_keywords("#{root_dir}/lib/postgresql_keywords.txt")
|
18
|
+
@mysql_reserved_keywords = load_keywords("#{root_dir}/lib/mysql_keywords.txt")
|
19
|
+
end
|
20
|
+
|
21
|
+
def error_table_names
|
22
|
+
table_names = models.map(&:table_name)
|
23
|
+
table_names.filter do |table_name|
|
24
|
+
table_name_has_errors?(table_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def error_column_names
|
29
|
+
models.each.with_object([]) do |model, error_columns|
|
30
|
+
column_names = model.columns.map(&:name)
|
31
|
+
|
32
|
+
column_names.each do |column_name|
|
33
|
+
if column_name_has_errors?(column_name)
|
34
|
+
error_columns << "#{model.table_name}.#{column_name}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def root_dir
|
43
|
+
File.expand_path('..', File.dirname(__FILE__))
|
44
|
+
end
|
45
|
+
|
46
|
+
def config_filename
|
47
|
+
%w[.schema_linter.yaml .schema_linter.yml].detect { |f| File.exist?(f) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def config
|
51
|
+
@config ||= config_filename ? YAML.load_file(config_filename) : {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def models
|
55
|
+
return @models if @models
|
56
|
+
|
57
|
+
@models ||= ActiveRecord::Base.descendants.reject(&:abstract_class)
|
58
|
+
end
|
59
|
+
|
60
|
+
def table_name_has_errors?(table_name)
|
61
|
+
name = table_name.downcase
|
62
|
+
return false if @ignore_table_names.any? { |regexp| name.match?(regexp) }
|
63
|
+
|
64
|
+
@error_table_names.any? { |regexp| name.match?(regexp) } ||
|
65
|
+
@mysql_reserved_keywords.include?(name) ||
|
66
|
+
@postgresql_reserved_keywords.include?(name) ||
|
67
|
+
@rails_reserved_table_names.include?(name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def column_name_has_errors?(column_name)
|
71
|
+
name = column_name.downcase
|
72
|
+
return false if @ignore_column_names.any? { |regexp| name.match?(regexp) }
|
73
|
+
|
74
|
+
@error_column_names.any? { |regexp| name.match?(regexp) } ||
|
75
|
+
@mysql_reserved_keywords.include?(name) ||
|
76
|
+
@postgresql_reserved_keywords.include?(name) ||
|
77
|
+
@rails_reserved_column_names.include?(name)
|
78
|
+
end
|
79
|
+
|
80
|
+
def load_keywords(file_path)
|
81
|
+
File.readlines(file_path).map(&:strip).map(&:downcase)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
desc 'Perform schema linting and output error table and column names. Raise an error if FAIL_ON_ERROR is set and there are errors.'
|
2
|
+
task schema_linter: :environment do
|
3
|
+
schema_linter = SchemaLinter.new
|
4
|
+
|
5
|
+
error_table_names = schema_linter.error_table_names
|
6
|
+
if error_table_names.present?
|
7
|
+
puts "Error table names (#{error_table_names.size}):"
|
8
|
+
error_table_names.each { |table_name| puts " #{table_name}" }
|
9
|
+
end
|
10
|
+
|
11
|
+
error_column_names = schema_linter.error_column_names
|
12
|
+
if error_column_names.present?
|
13
|
+
puts "Error column names (#{error_column_names.size}):"
|
14
|
+
error_column_names.each { |column_name| puts " #{column_name}" }
|
15
|
+
end
|
16
|
+
|
17
|
+
if ENV['FAIL_ON_ERROR'] && (error_table_names.present? || error_column_names.present?)
|
18
|
+
raise 'Error detected.'
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.push File.expand_path('lib', __dir__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'schema_linter'
|
5
|
+
s.version = '0.0.1'
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ['Akira Kusumoto']
|
8
|
+
s.email = ['akirakusumo10@gmail.com']
|
9
|
+
s.homepage = 'https://github.com/bluerabbit/schema_linter'
|
10
|
+
s.summary = 'A linter for database schema naming conventions'
|
11
|
+
s.description = "The SchemaLinter gem ensures your database schema naming conventions are adhered to by checking both table names and column names against custom-defined rules in a YAML configuration file."
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
16
|
+
s.require_paths = ['lib']
|
17
|
+
|
18
|
+
s.licenses = ['MIT']
|
19
|
+
|
20
|
+
s.add_dependency 'rails', ['>= 6.0.0']
|
21
|
+
s.add_development_dependency 'mysql2'
|
22
|
+
s.add_development_dependency 'pry-byebug'
|
23
|
+
s.add_development_dependency 'rspec', '~> 3.9'
|
24
|
+
end
|
data/spec/app.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/all'
|
4
|
+
require 'action_controller/railtie'
|
5
|
+
|
6
|
+
require 'schema_linter'
|
7
|
+
|
8
|
+
module DummyApp
|
9
|
+
class Application < Rails::Application
|
10
|
+
config.root = File.expand_path(__dir__)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
ActiveRecord::Base.establish_connection(
|
15
|
+
YAML.safe_load(ERB.new(File.read('spec/database.yml')).result, aliases: true)['test']
|
16
|
+
)
|
17
|
+
|
18
|
+
ActiveRecord::Schema.define version: 0 do
|
19
|
+
create_table :users, force: true do |t|
|
20
|
+
t.string :name
|
21
|
+
t.string :role # This column is a reserved word in MySQL.
|
22
|
+
end
|
23
|
+
|
24
|
+
# This table name conflicts with a Rails class.
|
25
|
+
create_table :configurations, force: true do |t|
|
26
|
+
t.string :service_name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class User < ActiveRecord::Base
|
31
|
+
end
|
32
|
+
|
33
|
+
class Configuration < ActiveRecord::Base
|
34
|
+
end
|
data/spec/database.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
default: &default
|
2
|
+
adapter: mysql2
|
3
|
+
encoding: utf8mb4
|
4
|
+
charset: utf8mb4
|
5
|
+
collation: utf8mb4_general_ci
|
6
|
+
pool: 5
|
7
|
+
username: <%= ENV.fetch("DB_USER") { 'root' } %>
|
8
|
+
password: <%= ENV.fetch("DB_PASS") { '' } %>
|
9
|
+
host: <%= ENV.fetch("DB_HOST") { '127.0.0.1' } %>
|
10
|
+
socket: /tmp/mysql.sock
|
11
|
+
|
12
|
+
development:
|
13
|
+
<<: *default
|
14
|
+
database: schema_linter_development
|
15
|
+
|
16
|
+
test:
|
17
|
+
<<: *default
|
18
|
+
database: schema_linter_test
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe SchemaLinter do
|
6
|
+
it 'has a version number' do
|
7
|
+
expect(SchemaLinter::VERSION).not_to be nil
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:schema_linter) { SchemaLinter.new }
|
11
|
+
|
12
|
+
after { File.delete '.schema_linter.yml' if File.exist? '.schema_linter.yml' }
|
13
|
+
|
14
|
+
describe '#error_table_names' do
|
15
|
+
it do
|
16
|
+
expect(schema_linter.error_table_names).to match_array(["configurations"])
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'When a configuration file is present' do
|
20
|
+
before do
|
21
|
+
File.open '.schema_linter.yml', 'w' do |file|
|
22
|
+
file.puts 'error_table_names:'
|
23
|
+
file.puts ' - ^users$' # Treat the users table as an error
|
24
|
+
file.puts 'ignore_table_names:'
|
25
|
+
file.puts ' - ^configurations$' # Ignore the configurations table
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'finds error table names.' do
|
30
|
+
expect(schema_linter.error_table_names).to match_array(%w[users])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#error_column_names' do
|
36
|
+
it 'Database columns that use reserved keywords will result in errors.' do
|
37
|
+
expect(schema_linter.error_column_names).to match_array(["users.role"])
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'When a configuration file is present' do
|
41
|
+
before do
|
42
|
+
File.open '.schema_linter.yml', 'w' do |file|
|
43
|
+
file.puts 'error_column_names:'
|
44
|
+
file.puts ' - ^name$' # Treat the name column as an error
|
45
|
+
file.puts 'ignore_column_names:'
|
46
|
+
file.puts ' - ^role$' # Ignore the role column
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'finds error column names.' do
|
51
|
+
expect(schema_linter.error_column_names).to match_array(%w[users.name])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
2
|
+
require 'pry'
|
3
|
+
require 'rails'
|
4
|
+
require 'schema_linter'
|
5
|
+
require_relative 'app'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.around do |example|
|
9
|
+
ActiveRecord::Base.transaction do
|
10
|
+
example.run
|
11
|
+
|
12
|
+
raise ActiveRecord::Rollback
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: schema_linter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Akira Kusumoto
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-11-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 6.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 6.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mysql2
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-byebug
|
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: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.9'
|
69
|
+
description: The SchemaLinter gem ensures your database schema naming conventions
|
70
|
+
are adhered to by checking both table names and column names against custom-defined
|
71
|
+
rules in a YAML configuration file.
|
72
|
+
email:
|
73
|
+
- akirakusumo10@gmail.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- ".circleci/config.yml"
|
79
|
+
- ".gitignore"
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- lib/mysql_keywords.txt
|
85
|
+
- lib/postgresql_keywords.txt
|
86
|
+
- lib/rails_avoid_columns.txt
|
87
|
+
- lib/rails_avoid_tables.txt
|
88
|
+
- lib/schema_linter.rb
|
89
|
+
- lib/tasks/schema_linter.rake
|
90
|
+
- schema_linter.gemspec
|
91
|
+
- spec/app.rb
|
92
|
+
- spec/database.yml
|
93
|
+
- spec/schema_linter_spec.rb
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
homepage: https://github.com/bluerabbit/schema_linter
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubygems_version: 3.4.22
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: A linter for database schema naming conventions
|
118
|
+
test_files:
|
119
|
+
- spec/app.rb
|
120
|
+
- spec/database.yml
|
121
|
+
- spec/schema_linter_spec.rb
|
122
|
+
- spec/spec_helper.rb
|