nullalign 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/.gitignore +4 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +52 -0
- data/Rakefile +2 -0
- data/bin/nullalign +24 -0
- data/lib/nullalign.rb +28 -0
- data/lib/nullalign/introspectors/table_data.rb +17 -0
- data/lib/nullalign/introspectors/validates_presence_of.rb +41 -0
- data/lib/nullalign/models.rb +29 -0
- data/lib/nullalign/nonnull_constraint.rb +15 -0
- data/lib/nullalign/reporter.rb +9 -0
- data/lib/nullalign/reporters/base.rb +79 -0
- data/lib/nullalign/reporters/validates_presence_of.rb +13 -0
- data/lib/nullalign/version.rb +3 -0
- data/nullalign.gemspec +27 -0
- data/spec/models_spec.rb +50 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/active_record.rb +1 -0
- data/spec/support/models/correct_account.rb +3 -0
- data/spec/support/models/new_correct_account.rb +7 -0
- data/spec/support/models/nonexistent.rb +2 -0
- data/spec/support/models/wrong_account.rb +3 -0
- data/spec/support/schema.rb +17 -0
- metadata +123 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 5af4ac1d6d4cfd1977ef54a3f0474dbce25f9fea
|
|
4
|
+
data.tar.gz: bad6def07e87ca11b13fac642d6f937314143f45
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d26458bc42d5646822a211380a7f558a1d99027423cf307df635bc90f7669278d49a9e79ae0cc2388c96a3fcbd9cd94fd3f73917210be8399328077e4a114623
|
|
7
|
+
data.tar.gz: '091cdc67b563263fdf6e04a1fc56037f4bc26fdc1bc1c59c4a6cee2ef0704fcbeff6d818108bc854db6be6947a892701a18cfe2fbfa5999a1d222924fca6ebad'
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby-2.4.2
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2011 Colin Jones
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
21
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Nullalign
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
nullalign is a tool to detect missing non-null constraints in Rails projects.
|
|
6
|
+
|
|
7
|
+
Suppose you have a validation like this:
|
|
8
|
+
|
|
9
|
+
validates :email, presence: true
|
|
10
|
+
|
|
11
|
+
Do you have a non-null constraint in your database to back that up? If not, nullalign will find it for you.
|
|
12
|
+
|
|
13
|
+
Nullalign is based on Colin Jones' [consistency_fail](https://github.com/trptcolin/consistency_fail). I mean really really based on it, as in I copied and pasted over a bunch of the code and changed the module and file names. And a lot of this README, too.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Put this in the `development` group in your `Gemfile`
|
|
18
|
+
|
|
19
|
+
gem 'nullalign'
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Run it like this:
|
|
24
|
+
|
|
25
|
+
bundle exec nullalign
|
|
26
|
+
|
|
27
|
+
## Example output
|
|
28
|
+
|
|
29
|
+
There are presence validators that aren't backed by non-null constraints.
|
|
30
|
+
--------------------------------------------------------------------------------
|
|
31
|
+
Model Table Columns
|
|
32
|
+
--------------------------------------------------------------------------------
|
|
33
|
+
Album albums: name, owner_id
|
|
34
|
+
AttendanceRecord attendance_records: group_id, attended_at
|
|
35
|
+
CheckinLabel checkin_labels: name, xml
|
|
36
|
+
CheckinTime checkin_times: campus
|
|
37
|
+
|
|
38
|
+
## Limitations
|
|
39
|
+
|
|
40
|
+
nullalign depends on being able to find all your `ActiveRecord::Base`
|
|
41
|
+
subclasses with some `$LOAD_PATH` trickery. If any models are in a path either
|
|
42
|
+
not on your project's load path or in a path that doesn't include the word
|
|
43
|
+
"models", nullalign won't be able to find or analyze them. I'm open to
|
|
44
|
+
making the text "models" configurable if people want that. Please open an issue
|
|
45
|
+
or pull request if so!
|
|
46
|
+
|
|
47
|
+
To disable nullalign, I could add a thing that checks column comments for a string
|
|
48
|
+
like 'nonullalign' if people think that would be useful. Just let me know.
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
Released under the MIT License. See the LICENSE file for further details.
|
data/Rakefile
ADDED
data/bin/nullalign
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
base_dir = File.join(Dir.pwd, ARGV.first.to_s)
|
|
4
|
+
puts "\nWarning! You are going out of current directory, ruby version may be wrong and some gems may be missing.\n" unless File.realpath(base_dir).start_with?(Dir.pwd)
|
|
5
|
+
|
|
6
|
+
begin
|
|
7
|
+
|
|
8
|
+
require File.join(base_dir, 'config', 'boot')
|
|
9
|
+
rescue LoadError
|
|
10
|
+
puts "\nUh-oh! You must be in the root directory of a Rails project.\n"
|
|
11
|
+
raise
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require 'active_record'
|
|
15
|
+
require File.join(base_dir, 'config', 'environment')
|
|
16
|
+
|
|
17
|
+
$:<< File.join(File.dirname(__FILE__), '..', 'lib')
|
|
18
|
+
require 'nullalign'
|
|
19
|
+
|
|
20
|
+
if Nullalign.run
|
|
21
|
+
exit 0
|
|
22
|
+
else
|
|
23
|
+
exit 1
|
|
24
|
+
end
|
data/lib/nullalign.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'nullalign/models'
|
|
2
|
+
require 'nullalign/introspectors/table_data'
|
|
3
|
+
require 'nullalign/introspectors/validates_presence_of'
|
|
4
|
+
require 'nullalign/reporter'
|
|
5
|
+
|
|
6
|
+
module Nullalign
|
|
7
|
+
def self.run
|
|
8
|
+
models = Nullalign::Models.new($LOAD_PATH)
|
|
9
|
+
models.preload_all
|
|
10
|
+
|
|
11
|
+
reporter = Nullalign::Reporter.new
|
|
12
|
+
|
|
13
|
+
introspector = Nullalign::Introspectors::ValidatesPresenceOf.new
|
|
14
|
+
problems = problems(models.all, introspector)
|
|
15
|
+
reporter.report_validates_presence_problems(problems)
|
|
16
|
+
problems.empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def self.problems(models, introspector)
|
|
22
|
+
models.map do |m|
|
|
23
|
+
[m, introspector.missing_nonnull_constraints(m)]
|
|
24
|
+
end.reject do |m, columns|
|
|
25
|
+
columns.empty?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'nullalign/nonnull_constraint'
|
|
2
|
+
|
|
3
|
+
module Nullalign
|
|
4
|
+
module Introspectors
|
|
5
|
+
class TableData
|
|
6
|
+
def nonnull_constraints(model)
|
|
7
|
+
return [] if !model.table_exists?
|
|
8
|
+
|
|
9
|
+
nonnull_constraints_by_table(model, model.table_name)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def nonnull_constraints_by_table(model, table_name)
|
|
13
|
+
model.columns.select {|c| !c.null }.map {|c| Nullalign::NonnullConstraint.new(model, table_name, c.name) }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'nullalign/nonnull_constraint'
|
|
2
|
+
|
|
3
|
+
module Nullalign
|
|
4
|
+
module Introspectors
|
|
5
|
+
class ValidatesPresenceOf
|
|
6
|
+
def instances(model)
|
|
7
|
+
model.validators.select do |v|
|
|
8
|
+
v.class == ActiveRecord::Validations::PresenceValidator
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def desired_nonnull_constraints(model)
|
|
13
|
+
instances(model).map do |v|
|
|
14
|
+
v.attributes.map do |attribute|
|
|
15
|
+
# This next bit is to avoid a false positive in the case where a validator uses
|
|
16
|
+
# an association name rather than the field name (i.e. user vs user_id).
|
|
17
|
+
association = model.reflect_on_all_associations.detect {|r| r.name == attribute }
|
|
18
|
+
attribute_value = if association != nil
|
|
19
|
+
association.foreign_key
|
|
20
|
+
else
|
|
21
|
+
attribute
|
|
22
|
+
end
|
|
23
|
+
Nullalign::NonnullConstraint.new(model,
|
|
24
|
+
model.table_name,
|
|
25
|
+
attribute_value)
|
|
26
|
+
end
|
|
27
|
+
end.flatten
|
|
28
|
+
end
|
|
29
|
+
private :desired_nonnull_constraints
|
|
30
|
+
|
|
31
|
+
def missing_nonnull_constraints(model)
|
|
32
|
+
return [] unless model.connection.tables.include? model.table_name
|
|
33
|
+
existing_nonnull_constraints = TableData.new.nonnull_constraints(model)
|
|
34
|
+
|
|
35
|
+
desired_nonnull_constraints(model).reject do |index|
|
|
36
|
+
existing_nonnull_constraints.include?(index)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
|
|
3
|
+
module Nullalign
|
|
4
|
+
class Models
|
|
5
|
+
MODEL_DIRECTORY_REGEXP = /models/
|
|
6
|
+
|
|
7
|
+
attr_reader :load_path
|
|
8
|
+
|
|
9
|
+
def initialize(load_path)
|
|
10
|
+
@load_path = load_path
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def dirs
|
|
14
|
+
load_path.select { |lp| MODEL_DIRECTORY_REGEXP =~ lp.to_s }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def preload_all
|
|
18
|
+
self.dirs.each do |d|
|
|
19
|
+
Dir.glob(File.join(d, "**", "*.rb")).each do |model_filename|
|
|
20
|
+
Kernel.require_dependency model_filename
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def all
|
|
26
|
+
ActiveRecord::Base.descendants.sort_by(&:name)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Nullalign
|
|
2
|
+
class NonnullConstraint
|
|
3
|
+
attr_reader :model, :table_name, :column
|
|
4
|
+
def initialize(model, table_name, column)
|
|
5
|
+
@model = model
|
|
6
|
+
@table_name = table_name
|
|
7
|
+
@column = column.to_s
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def ==(other)
|
|
11
|
+
self.table_name == other.table_name &&
|
|
12
|
+
self.column == other.column
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module Nullalign
|
|
2
|
+
module Reporters
|
|
3
|
+
class Base
|
|
4
|
+
TERMINAL_WIDTH = 80
|
|
5
|
+
|
|
6
|
+
RED = 31
|
|
7
|
+
GREEN = 32
|
|
8
|
+
|
|
9
|
+
def use_color(code)
|
|
10
|
+
print "\e[#{code}m"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def use_default_color
|
|
14
|
+
use_color(0)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def report_success(macro)
|
|
18
|
+
use_color(GREEN)
|
|
19
|
+
# TODO not using 'macro' but leaving it here in case this gets
|
|
20
|
+
# rolled into consistency_fail
|
|
21
|
+
puts "Hooray! All presence validators are backed by a non-null constraint."
|
|
22
|
+
use_default_color
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def divider(pad_to = TERMINAL_WIDTH)
|
|
26
|
+
puts "-" * [pad_to, TERMINAL_WIDTH].max
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def report_failure_header(macro, longest_model_length)
|
|
30
|
+
puts
|
|
31
|
+
use_color(RED)
|
|
32
|
+
# TODO not using 'macro' but leaving it here in case this gets
|
|
33
|
+
# rolled into consistency_fail
|
|
34
|
+
puts "There are presence validators that aren't backed by non-null constraints."
|
|
35
|
+
use_default_color
|
|
36
|
+
divider(longest_model_length * 2)
|
|
37
|
+
|
|
38
|
+
column_1_header, column_2_header = column_headers
|
|
39
|
+
print column_1_header.ljust(longest_model_length + 2)
|
|
40
|
+
puts column_2_header
|
|
41
|
+
|
|
42
|
+
divider(longest_model_length * 2)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def column_1(model)
|
|
46
|
+
model.name
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def column_headers
|
|
50
|
+
["Model", "Table Columns"]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def report(null_constraints_by_model)
|
|
54
|
+
if null_constraints_by_model.empty?
|
|
55
|
+
report_success(macro)
|
|
56
|
+
else
|
|
57
|
+
null_constraints_by_table_name = null_constraints_by_model.map do |model, columns|
|
|
58
|
+
[column_1(model), model, columns]
|
|
59
|
+
end.sort_by(&:first)
|
|
60
|
+
longest_model_length = null_constraints_by_table_name.map(&:first).
|
|
61
|
+
sort_by(&:length).
|
|
62
|
+
last.
|
|
63
|
+
length
|
|
64
|
+
column_1_header_length = column_headers.first.length
|
|
65
|
+
longest_model_length = [longest_model_length, column_1_header_length].max
|
|
66
|
+
|
|
67
|
+
report_failure_header(macro, longest_model_length)
|
|
68
|
+
|
|
69
|
+
null_constraints_by_table_name.each do |table_name, model, columns|
|
|
70
|
+
print model.name.ljust(longest_model_length + 2)
|
|
71
|
+
puts columns.first.table_name + ": " + columns.map {|x| x.column }.join(', ')
|
|
72
|
+
end
|
|
73
|
+
divider(longest_model_length * 2)
|
|
74
|
+
end
|
|
75
|
+
puts
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/nullalign.gemspec
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "nullalign/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "nullalign"
|
|
7
|
+
s.version = Nullalign::VERSION
|
|
8
|
+
s.platform = Gem::Platform::RUBY
|
|
9
|
+
s.authors = ["Tom Copeland"]
|
|
10
|
+
s.email = ["tom@thomasleecopeland.com"]
|
|
11
|
+
s.homepage = "http://github.com/tcopeland/nullalign"
|
|
12
|
+
s.summary = %q{A tool to detect missing non-null constraints}
|
|
13
|
+
s.description = <<-EOF
|
|
14
|
+
If you have a presence validation, you'll probably want a
|
|
15
|
+
non-null constraint to go with it.
|
|
16
|
+
EOF
|
|
17
|
+
s.license = "MIT"
|
|
18
|
+
|
|
19
|
+
s.add_development_dependency "activerecord", "~>3.0"
|
|
20
|
+
s.add_development_dependency "sqlite3", "~>1.3"
|
|
21
|
+
s.add_development_dependency "rspec", "~>3.2"
|
|
22
|
+
|
|
23
|
+
s.files = `git ls-files`.split("\n")
|
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
26
|
+
s.require_paths = ["lib"]
|
|
27
|
+
end
|
data/spec/models_spec.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Nullalign::Models do
|
|
4
|
+
|
|
5
|
+
def models(load_path)
|
|
6
|
+
Nullalign::Models.new(load_path)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "gets the load path" do
|
|
10
|
+
expect(models([:a, :b, :c]).load_path).to eq([:a, :b, :c])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "gets the directories matching /models/" do
|
|
14
|
+
models = models(["foo/bar/baz", "app/models", "some/other/models"])
|
|
15
|
+
expect(models.dirs).to eq(["app/models", "some/other/models"])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "accepts and matches path names as well as strings" do
|
|
19
|
+
models = models([Pathname.new("app/models")])
|
|
20
|
+
expect { models.dirs }.not_to raise_error
|
|
21
|
+
expect(models.dirs).to eq([Pathname.new("app/models")])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "preloads models by calling require_dependency" do
|
|
25
|
+
models = models(["foo/bar/baz", "app/models", "some/other/models"])
|
|
26
|
+
allow(Dir).to receive(:glob).
|
|
27
|
+
with(File.join("app/models", "**", "*.rb")).
|
|
28
|
+
and_return(["app/models/account.rb"])
|
|
29
|
+
allow(Dir).to receive(:glob).
|
|
30
|
+
with(File.join("some/other/models", "**", "*.rb")).
|
|
31
|
+
and_return(["some/other/models/foo.rb"])
|
|
32
|
+
|
|
33
|
+
expect(Kernel).to receive(:require_dependency).with("app/models/account.rb")
|
|
34
|
+
expect(Kernel).to receive(:require_dependency).with("some/other/models/foo.rb")
|
|
35
|
+
|
|
36
|
+
models.preload_all
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "gets all models" do
|
|
40
|
+
# TODO fails, but why?
|
|
41
|
+
# model_a = double(:name => "animal")
|
|
42
|
+
# model_b = double(:name => "cat")
|
|
43
|
+
# model_c = double(:name => "beach_ball")
|
|
44
|
+
#
|
|
45
|
+
# allow(ActiveRecord::Base).to receive(:send).with(:descendants).and_return([model_a, model_b, model_c])
|
|
46
|
+
#
|
|
47
|
+
# expect(models([]).all).to eq([model_a, model_c, model_b])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'bundler/setup'
|
|
2
|
+
|
|
3
|
+
Bundler.require(:default, :test)
|
|
4
|
+
|
|
5
|
+
Dir['./spec/support/**/*.rb'].sort.each { |file| require file }
|
|
6
|
+
|
|
7
|
+
RSpec.configure do |config|
|
|
8
|
+
config.around do |example|
|
|
9
|
+
ActiveRecord::Base.transaction do
|
|
10
|
+
example.run
|
|
11
|
+
raise ActiveRecord::Rollback
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
ActiveRecord::Schema.define(version: 0) do
|
|
2
|
+
self.verbose = false
|
|
3
|
+
|
|
4
|
+
create_table :correct_accounts do |t|
|
|
5
|
+
t.string :email, null: false
|
|
6
|
+
t.timestamps
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
create_table :wrong_accounts do |t|
|
|
10
|
+
t.string :email
|
|
11
|
+
t.timestamps
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
execute 'CREATE VIEW new_correct_people AS '\
|
|
15
|
+
'SELECT * FROM correct_people '\
|
|
16
|
+
'WHERE created_at = updated_at'
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: nullalign
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tom Copeland
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2017-09-28 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activerecord
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: sqlite3
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.3'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.3'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.2'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.2'
|
|
55
|
+
description: |
|
|
56
|
+
If you have a presence validation, you'll probably want a
|
|
57
|
+
non-null constraint to go with it.
|
|
58
|
+
email:
|
|
59
|
+
- tom@thomasleecopeland.com
|
|
60
|
+
executables:
|
|
61
|
+
- nullalign
|
|
62
|
+
extensions: []
|
|
63
|
+
extra_rdoc_files: []
|
|
64
|
+
files:
|
|
65
|
+
- ".gitignore"
|
|
66
|
+
- ".ruby-version"
|
|
67
|
+
- CHANGELOG.md
|
|
68
|
+
- Gemfile
|
|
69
|
+
- LICENSE
|
|
70
|
+
- README.md
|
|
71
|
+
- Rakefile
|
|
72
|
+
- bin/nullalign
|
|
73
|
+
- lib/nullalign.rb
|
|
74
|
+
- lib/nullalign/introspectors/table_data.rb
|
|
75
|
+
- lib/nullalign/introspectors/validates_presence_of.rb
|
|
76
|
+
- lib/nullalign/models.rb
|
|
77
|
+
- lib/nullalign/nonnull_constraint.rb
|
|
78
|
+
- lib/nullalign/reporter.rb
|
|
79
|
+
- lib/nullalign/reporters/base.rb
|
|
80
|
+
- lib/nullalign/reporters/validates_presence_of.rb
|
|
81
|
+
- lib/nullalign/version.rb
|
|
82
|
+
- nullalign.gemspec
|
|
83
|
+
- spec/models_spec.rb
|
|
84
|
+
- spec/spec_helper.rb
|
|
85
|
+
- spec/support/active_record.rb
|
|
86
|
+
- spec/support/models/correct_account.rb
|
|
87
|
+
- spec/support/models/new_correct_account.rb
|
|
88
|
+
- spec/support/models/nonexistent.rb
|
|
89
|
+
- spec/support/models/wrong_account.rb
|
|
90
|
+
- spec/support/schema.rb
|
|
91
|
+
homepage: http://github.com/tcopeland/nullalign
|
|
92
|
+
licenses:
|
|
93
|
+
- MIT
|
|
94
|
+
metadata: {}
|
|
95
|
+
post_install_message:
|
|
96
|
+
rdoc_options: []
|
|
97
|
+
require_paths:
|
|
98
|
+
- lib
|
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - ">="
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '0'
|
|
109
|
+
requirements: []
|
|
110
|
+
rubyforge_project:
|
|
111
|
+
rubygems_version: 2.6.13
|
|
112
|
+
signing_key:
|
|
113
|
+
specification_version: 4
|
|
114
|
+
summary: A tool to detect missing non-null constraints
|
|
115
|
+
test_files:
|
|
116
|
+
- spec/models_spec.rb
|
|
117
|
+
- spec/spec_helper.rb
|
|
118
|
+
- spec/support/active_record.rb
|
|
119
|
+
- spec/support/models/correct_account.rb
|
|
120
|
+
- spec/support/models/new_correct_account.rb
|
|
121
|
+
- spec/support/models/nonexistent.rb
|
|
122
|
+
- spec/support/models/wrong_account.rb
|
|
123
|
+
- spec/support/schema.rb
|