cwninja-active_record_lint 0.0.3
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.
- data/README.markdown +23 -0
- data/Rakefile +64 -0
- data/bin/arlint +22 -0
- data/lib/active_record/lint.rb +32 -0
- data/lib/active_record/lint/memoize.rb +28 -0
- data/lib/active_record/lint/pairs.rb +53 -0
- data/lib/active_record/lint/reporter.rb +89 -0
- data/lib/active_record/lint/scanner.rb +61 -0
- data/lib/active_record_lint.rb +1 -0
- data/test/fixtures/criminals/app/models/crime.rb +5 -0
- data/test/fixtures/criminals/app/models/criminal.rb +5 -0
- data/test/fixtures/criminals/app/models/incident.rb +5 -0
- data/test/fixtures/criminals/app/models/victim.rb +3 -0
- data/test/functional/missing_foreign_keys_test.rb +26 -0
- data/test/functional/missing_indexes_test.rb +35 -0
- data/test/functional/missing_tables_test.rb +26 -0
- data/test/functional/reporter_test.rb +85 -0
- data/test/test_helper.rb +87 -0
- data/test/unit/indexes_test.rb +12 -0
- data/test/unit/model_scanning_test.rb +27 -0
- data/test/unit/pair_test.rb +37 -0
- metadata +82 -0
data/README.markdown
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
ActiveRecord::Lint
|
2
|
+
================
|
3
|
+
|
4
|
+
A library to support the automatic checking for the doing of stupid things
|
5
|
+
with ActiveRecord.
|
6
|
+
|
7
|
+
AR::Lint is not big, nor is it clever, nor is it that well written.
|
8
|
+
It makes recommendations based upon what I have found to be good practice, with
|
9
|
+
only anecdotal evidence to support it.
|
10
|
+
|
11
|
+
This is *not* a substitute for a DB admin, nor is it a substitute for knoledge.
|
12
|
+
|
13
|
+
Your mileage may vary, remember to check your tire pressure.
|
14
|
+
|
15
|
+
Usage
|
16
|
+
-----
|
17
|
+
|
18
|
+
You can check a typical rails application by executing:
|
19
|
+
> $ arlint path/to/rails/app
|
20
|
+
|
21
|
+
Support for automated tests and making it work with rake is something I could do to get around to.
|
22
|
+
|
23
|
+
Copyright (c) 2008 Tom Lea, released under the MIT Licence
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'date'
|
7
|
+
|
8
|
+
desc 'Default: run unit tests.'
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
namespace :test do
|
12
|
+
desc 'Functional tests.'
|
13
|
+
Rake::TestTask.new(:functional) do |t|
|
14
|
+
t.libs << 'lib'
|
15
|
+
t.pattern = 'test/functional/*_test.rb'
|
16
|
+
t.verbose = true
|
17
|
+
end
|
18
|
+
desc 'Unit tests.'
|
19
|
+
Rake::TestTask.new(:unit) do |t|
|
20
|
+
t.libs << 'lib'
|
21
|
+
t.pattern = 'test/unit/*_test.rb'
|
22
|
+
t.verbose = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
task :test => ["test:functional", "test:unit"]
|
27
|
+
|
28
|
+
spec = Gem::Specification.new do |s|
|
29
|
+
s.name = %q{active_record_lint}
|
30
|
+
s.version = "0.0.3"
|
31
|
+
s.summary = %q{Rails tool to find foreign keys without indexes and other common issues.}
|
32
|
+
s.description = %q{A library to support the automatic checking for the doing of stupid things with ActiveRecord.}
|
33
|
+
|
34
|
+
s.executables = ["arlint"]
|
35
|
+
s.files = FileList['[A-Z]*', 'lib/**/*.rb', 'test/**/*.rb']
|
36
|
+
s.require_path = 'lib'
|
37
|
+
s.test_files = Dir[*['test/**/*_test.rb']]
|
38
|
+
s.homepage = "http://github.com/cwninja/active_record_lint"
|
39
|
+
|
40
|
+
s.has_rdoc = true
|
41
|
+
s.extra_rdoc_files = ["README.markdown"]
|
42
|
+
s.rdoc_options = ['--line-numbers', '--inline-source', "--main", "README.markdown"]
|
43
|
+
|
44
|
+
s.authors = ["Tom Lea"]
|
45
|
+
s.email = %q{commits@tomlea.co.uk}
|
46
|
+
|
47
|
+
s.platform = Gem::Platform::RUBY
|
48
|
+
end
|
49
|
+
|
50
|
+
Rake::GemPackageTask.new spec do |pkg|
|
51
|
+
pkg.need_tar = true
|
52
|
+
pkg.need_zip = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Clean files generated by rake tasks"
|
56
|
+
task :clobber => [:clobber_rdoc, :clobber_package]
|
57
|
+
|
58
|
+
desc "Generate a gemspec file"
|
59
|
+
task :gemspec do
|
60
|
+
File.open("#{spec.name}.gemspec", 'w') do |f|
|
61
|
+
f.write spec.to_ruby
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
data/bin/arlint
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
path = ARGV.last || Dir.pwd
|
4
|
+
|
5
|
+
boot_path = File.join(path, 'config', 'boot.rb')
|
6
|
+
environment_path = File.join(path, 'config', 'environment.rb')
|
7
|
+
|
8
|
+
unless(File.exists?(boot_path) and File.exists?(environment_path))
|
9
|
+
puts "Usage: #{$0} path/to/rails/root"
|
10
|
+
exit(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
require boot_path
|
14
|
+
require environment_path
|
15
|
+
require 'active_record_lint'
|
16
|
+
|
17
|
+
ActiveRecord::Lint.load_models(path)
|
18
|
+
|
19
|
+
scanner = ActiveRecord::Lint::Scanner.new()
|
20
|
+
reporter = ActiveRecord::Lint::Reporter.new(scanner)
|
21
|
+
|
22
|
+
puts reporter
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module ActiveRecord::Lint
|
5
|
+
def load_models(rails_root)
|
6
|
+
pattern = File.join(rails_root, "app", "models", "*.rb")
|
7
|
+
Dir.glob(pattern){|file|
|
8
|
+
load( file )
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def unload_models
|
13
|
+
models = ActiveRecord::Base.send :class_variable_get, :@@subclasses
|
14
|
+
models = models.keys - [ActiveRecord::Base]
|
15
|
+
models.map(&:name).each do |name|
|
16
|
+
Object.send :remove_const, name unless name.nil? or name.empty?
|
17
|
+
end
|
18
|
+
ActiveRecord::Base.send :class_variable_set, :@@subclasses, {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def missing_indexes(options = {})
|
22
|
+
load_models(options[:rails_root] || RAILS_ROOT)
|
23
|
+
Scanner.new(options[:connection] || ActiveRecord::Base.connection).missing_indexes
|
24
|
+
end
|
25
|
+
|
26
|
+
extend self
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
Dir.glob(File.join(File.dirname(__FILE__), "lint", "*.rb")) do |file|
|
31
|
+
require file
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
unless defined? Memoize
|
2
|
+
module Memoize
|
3
|
+
|
4
|
+
def self.included(other)
|
5
|
+
other.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def memoize(*methods)
|
10
|
+
methods.each do |method|
|
11
|
+
define_method "#{method}_with_memoize" do |*args|
|
12
|
+
memoize_store_for(method)[args] ||= send("#{method}_without_memoize", *args)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method_chain method, :memoize
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def memoize_store_for(method)
|
21
|
+
unless store = instance_variable_get("@_#{method}_memoized")
|
22
|
+
instance_variable_set("@_#{method}_memoized", store = {})
|
23
|
+
end
|
24
|
+
store
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ActiveRecord::Lint
|
2
|
+
class Pair
|
3
|
+
include Comparable
|
4
|
+
attr_accessor :a, :b
|
5
|
+
|
6
|
+
def initialize(a, b)
|
7
|
+
@a,@b=a,b
|
8
|
+
end
|
9
|
+
|
10
|
+
def <=>(other)
|
11
|
+
(a <=> other.a).zero? ? (b <=> other.b) : (a <=> other.a)
|
12
|
+
end
|
13
|
+
|
14
|
+
def eql?(other)
|
15
|
+
(self <=> other) == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def pair_name
|
19
|
+
@pair_name ||= self.class.name.split("::").last
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#{pair_name}[#{a.inspect}, #{b.inspect}]"
|
24
|
+
end
|
25
|
+
|
26
|
+
def hash
|
27
|
+
a.hash ^ b.hash
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_ary
|
31
|
+
[a,b]
|
32
|
+
end
|
33
|
+
|
34
|
+
alias to_a to_ary
|
35
|
+
|
36
|
+
class << self
|
37
|
+
alias [] new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class TableColumnPair < Pair
|
42
|
+
alias table a
|
43
|
+
alias column b
|
44
|
+
end
|
45
|
+
|
46
|
+
class TableKeyPair < TableColumnPair
|
47
|
+
alias key b
|
48
|
+
end
|
49
|
+
|
50
|
+
class TableIndexPair < TableColumnPair
|
51
|
+
alias index b
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "pairs")
|
2
|
+
|
3
|
+
module ActiveRecord::Lint
|
4
|
+
class MissingIndex < TableIndexPair
|
5
|
+
def to_s
|
6
|
+
"The foreign key '#{index}' is not indexed on '#{table}'."
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class MissingTable
|
11
|
+
include Comparable
|
12
|
+
attr_reader :table
|
13
|
+
|
14
|
+
def initialize(table)
|
15
|
+
@table=table
|
16
|
+
end
|
17
|
+
|
18
|
+
def <=> (other)
|
19
|
+
table <=> other.table
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"Expected the table '#{table}' to exist."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MissingForeignKey < TableKeyPair
|
28
|
+
def to_s
|
29
|
+
"Missing foreign key '#{key}' on '#{table}'."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
class Reporter
|
35
|
+
include Memoize
|
36
|
+
SUMMARY_NAMES = ["missing_indexes", "missing_tables", "missing_foreign_keys"]
|
37
|
+
|
38
|
+
def initialize(scanner)
|
39
|
+
@scanner = scanner
|
40
|
+
end
|
41
|
+
|
42
|
+
def missing_indexes
|
43
|
+
@scanner.missing_indexes.map{|pair| MissingIndex[*pair.to_ary] }
|
44
|
+
end
|
45
|
+
memoize :missing_indexes
|
46
|
+
|
47
|
+
def missing_tables
|
48
|
+
tables = @scanner.missing_tables.map{|table_issue| MissingTable.new(table_issue) }
|
49
|
+
if defined?(ActionController::Base) and ActionController::Base.session_store != :active_record_store
|
50
|
+
tables.reject!{|table| table.table}
|
51
|
+
end
|
52
|
+
tables
|
53
|
+
end
|
54
|
+
memoize :missing_tables
|
55
|
+
|
56
|
+
def missing_foreign_keys
|
57
|
+
@scanner.missing_foreign_keys.map{|pair| MissingForeignKey[*pair.to_ary] }
|
58
|
+
end
|
59
|
+
memoize :missing_foreign_keys
|
60
|
+
|
61
|
+
def issues
|
62
|
+
SUMMARY_NAMES.map{|name| send(name) }.sum
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
lines = returning([]){|output|
|
68
|
+
SUMMARY_NAMES.each do |name|
|
69
|
+
issues = send(name)
|
70
|
+
unless issues.empty?
|
71
|
+
output << "== #{name} =="
|
72
|
+
issues.each do |issue|
|
73
|
+
output << "* #{issue}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
}
|
78
|
+
if lines.empty?
|
79
|
+
"No issues found"
|
80
|
+
else
|
81
|
+
lines.join("\n")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
attr_writer *SUMMARY_NAMES
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class ActiveRecord::Lint::Scanner
|
2
|
+
include ActiveRecord::Lint
|
3
|
+
include Memoize
|
4
|
+
|
5
|
+
def initialize(connection = nil)
|
6
|
+
@connection = connection || ActiveRecord::Base.connection
|
7
|
+
end
|
8
|
+
|
9
|
+
def missing_indexes
|
10
|
+
foreign_keys - indexes
|
11
|
+
end
|
12
|
+
|
13
|
+
def missing_tables
|
14
|
+
classes.map{|klass| klass.table_name } - @connection.tables
|
15
|
+
end
|
16
|
+
|
17
|
+
def missing_foreign_keys
|
18
|
+
foreign_keys - columns
|
19
|
+
end
|
20
|
+
|
21
|
+
def foreign_keys
|
22
|
+
returning [] do |foreign_keys|
|
23
|
+
classes.each do |klass|
|
24
|
+
table_name = klass.table_name
|
25
|
+
klass.reflect_on_all_associations(:belongs_to).map(&:primary_key_name).each do |foreign_key|
|
26
|
+
foreign_keys << TableColumnPair.new(table_name, foreign_key)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end.uniq
|
30
|
+
end
|
31
|
+
memoize :foreign_keys
|
32
|
+
|
33
|
+
def columns
|
34
|
+
returning [] do |columns|
|
35
|
+
@connection.tables.each do |table|
|
36
|
+
@connection.columns(table).each do |column|
|
37
|
+
columns << TableColumnPair.new(table, column.name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end.uniq
|
41
|
+
end
|
42
|
+
memoize :columns
|
43
|
+
|
44
|
+
def indexes
|
45
|
+
returning [] do |indexes|
|
46
|
+
@connection.tables.each do |table|
|
47
|
+
column_sets = @connection.indexes(table).map(&:columns)
|
48
|
+
column_sets.select{|col| col.size == 1}.flatten.each do |index|
|
49
|
+
indexes << TableIndexPair.new(table, index)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end.uniq
|
53
|
+
end
|
54
|
+
memoize :indexes
|
55
|
+
|
56
|
+
def classes
|
57
|
+
ActiveRecord::Base.send(:subclasses)
|
58
|
+
end
|
59
|
+
memoize :classes
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "active_record", "lint")
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "test_helper")
|
2
|
+
|
3
|
+
class MissingForeignKeysFunctionalTest < Test::Unit::TestCase
|
4
|
+
include CriminalDatabase
|
5
|
+
include ActiveRecord::Lint
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@rails_root = File.join(File.dirname(__FILE__), "..", "fixtures", "criminals")
|
9
|
+
ActiveRecord::Lint::load_models(@rails_root)
|
10
|
+
super
|
11
|
+
@connection.remove_column :incidents, :criminal_id
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_should_not_error_because_we_are_missing_criminal_id
|
15
|
+
missing_indexes = ActiveRecord::Lint::Scanner.new(@connection).missing_indexes
|
16
|
+
|
17
|
+
assert_array_equal [Pair["incidents", "criminal_id"], Pair["incidents", "crime_id"]], missing_indexes
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_should_report_lack_of_criminal_id_on_incidents_table
|
21
|
+
missing_foreign_keys = ActiveRecord::Lint::Scanner.new(@connection).missing_foreign_keys
|
22
|
+
|
23
|
+
assert_array_equal [Pair["incidents", "criminal_id"]], missing_foreign_keys
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "test_helper")
|
2
|
+
|
3
|
+
class MissingIndexesAssociationsFunctionalTest < Test::Unit::TestCase
|
4
|
+
include CriminalDatabase
|
5
|
+
Pair = ActiveRecord::Lint::Pair
|
6
|
+
def setup
|
7
|
+
ActiveRecord::Lint::unload_models
|
8
|
+
@rails_root = File.join(File.dirname(__FILE__), "..", "fixtures", "criminals")
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_should_know_we_need_an_index_on_victims
|
13
|
+
ActiveRecord::Lint::load_models(@rails_root)
|
14
|
+
|
15
|
+
missing_indexes = ActiveRecord::Lint::Scanner.new(@connection).missing_indexes
|
16
|
+
|
17
|
+
assert_array_equal [Pair["incidents", "criminal_id"], Pair["incidents", "crime_id"]], missing_indexes
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_should_be_able_to_call_missing_indexes_on_module
|
21
|
+
missing_indexes = ActiveRecord::Lint.missing_indexes(:rails_root => @rails_root)
|
22
|
+
|
23
|
+
assert_array_equal [Pair["incidents", "criminal_id"], Pair["incidents", "crime_id"]], missing_indexes
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_should_handle_inherited_models
|
27
|
+
ActiveRecord::Lint::load_models(@rails_root)
|
28
|
+
|
29
|
+
my_model = Class.new(Incident)
|
30
|
+
missing_indexes = ActiveRecord::Lint::Scanner.new(@connection).missing_indexes
|
31
|
+
|
32
|
+
assert_array_equal [Pair["incidents", "criminal_id"], Pair["incidents", "crime_id"]], missing_indexes
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "test_helper")
|
2
|
+
|
3
|
+
class MissingTablesFunctionalTest < Test::Unit::TestCase
|
4
|
+
include CriminalDatabase
|
5
|
+
Pair = ActiveRecord::Lint::Pair
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@rails_root = File.join(File.dirname(__FILE__), "..", "fixtures", "criminals")
|
9
|
+
ActiveRecord::Lint::load_models(@rails_root)
|
10
|
+
super
|
11
|
+
@connection.drop_table :criminals
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_should_not_error_because_we_have_no_criminals_table
|
15
|
+
missing_indexes = ActiveRecord::Lint::Scanner.new(@connection).missing_indexes
|
16
|
+
|
17
|
+
assert_array_equal [Pair["incidents", "criminal_id"], Pair["incidents", "crime_id"]], missing_indexes
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_should_report_lack_of_criminals_table
|
21
|
+
missing_tables = ActiveRecord::Lint::Scanner.new(@connection).missing_tables
|
22
|
+
|
23
|
+
assert_array_equal ["criminals"], missing_tables
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "test_helper")
|
2
|
+
|
3
|
+
class ReporterMissingIndexesFunctionalTest < Test::Unit::TestCase
|
4
|
+
include CriminalDatabase
|
5
|
+
Lint = ActiveRecord::Lint
|
6
|
+
Pair = Lint::Pair
|
7
|
+
|
8
|
+
def setup
|
9
|
+
super
|
10
|
+
@rails_root = File.join(File.dirname(__FILE__), "..", "fixtures", "criminals")
|
11
|
+
Lint::load_models(@rails_root)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_should_report_missing_indexes_on_incidents
|
15
|
+
scanner = Lint::Scanner.new(@connection)
|
16
|
+
reporter = Lint::Reporter.new(scanner)
|
17
|
+
|
18
|
+
assert_array_includes Lint::MissingIndex["incidents", "crime_id"], Lint::MissingIndex["incidents", "criminal_id"], reporter.missing_indexes
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_issues_should_report_missing_indexes_on_incidents
|
22
|
+
scanner = Lint::Scanner.new(@connection)
|
23
|
+
reporter = Lint::Reporter.new(scanner)
|
24
|
+
|
25
|
+
assert_array_includes Lint::MissingIndex["incidents", "crime_id"], Lint::MissingIndex["incidents", "criminal_id"], reporter.issues
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
class ReporterMissingTablesFunctionalTest < Test::Unit::TestCase
|
32
|
+
include CriminalDatabase
|
33
|
+
Lint = ActiveRecord::Lint
|
34
|
+
Pair = Lint::Pair
|
35
|
+
|
36
|
+
def setup
|
37
|
+
super
|
38
|
+
@connection.drop_table :criminals
|
39
|
+
@rails_root = File.join(File.dirname(__FILE__), "..", "fixtures", "criminals")
|
40
|
+
Lint::load_models(@rails_root)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_should_report_missing_table_criminals
|
44
|
+
scanner = Lint::Scanner.new(@connection)
|
45
|
+
reporter = Lint::Reporter.new(scanner)
|
46
|
+
|
47
|
+
assert_array_includes Lint::MissingTable.new("criminals"), reporter.missing_tables
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_issues_should_report_missing_table_criminals
|
51
|
+
scanner = Lint::Scanner.new(@connection)
|
52
|
+
reporter = Lint::Reporter.new(scanner)
|
53
|
+
|
54
|
+
assert_array_includes Lint::MissingTable.new("criminals"), reporter.issues
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
class ReporterMissingForeignKeysFunctionalTest < Test::Unit::TestCase
|
60
|
+
include CriminalDatabase
|
61
|
+
Lint = ActiveRecord::Lint
|
62
|
+
Pair = Lint::Pair
|
63
|
+
|
64
|
+
def setup
|
65
|
+
super
|
66
|
+
@connection.remove_column :incidents, :criminal_id
|
67
|
+
@rails_root = File.join(File.dirname(__FILE__), "..", "fixtures", "criminals")
|
68
|
+
Lint::load_models(@rails_root)
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_should_report_missing_table_criminals
|
72
|
+
scanner = Lint::Scanner.new(@connection)
|
73
|
+
reporter = Lint::Reporter.new(scanner)
|
74
|
+
|
75
|
+
assert_array_includes Lint::MissingForeignKey["incidents", "criminal_id"], reporter.missing_foreign_keys
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_issues_should_report_missing_table_criminals
|
79
|
+
scanner = Lint::Scanner.new(@connection)
|
80
|
+
reporter = Lint::Reporter.new(scanner)
|
81
|
+
|
82
|
+
assert_array_includes Lint::MissingForeignKey["incidents", "criminal_id"], reporter.issues
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), "..", "lib", "active_record_lint")
|
4
|
+
|
5
|
+
module CriminalDatabase
|
6
|
+
def self.included(other)
|
7
|
+
other.with_temp_db do
|
8
|
+
create_table :crimes do |t|
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :criminals do |t|
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table :incidents do |t|
|
15
|
+
t.references :criminal
|
16
|
+
t.references :victim
|
17
|
+
t.references :crime
|
18
|
+
end
|
19
|
+
|
20
|
+
add_index :incidents, :victim_id
|
21
|
+
|
22
|
+
create_table :victims do |t|
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Test::Unit::TestCase
|
29
|
+
def self.with_temp_db(&block)
|
30
|
+
include TempDB
|
31
|
+
@schema = Proc.new(&block) if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
module TempDB
|
35
|
+
DB_PATH = "/tmp/%s.sqlite3"
|
36
|
+
|
37
|
+
def teardown_database!
|
38
|
+
File.delete(DB_PATH % [self.class.name])
|
39
|
+
end
|
40
|
+
|
41
|
+
def setup_database!
|
42
|
+
ActiveRecord::Base.establish_connection(
|
43
|
+
:adapter => "sqlite3",
|
44
|
+
:database => (DB_PATH % [self.class.name])
|
45
|
+
)
|
46
|
+
ActiveRecord::Base.logger = Logger.new(File.open("/dev/null", "w"))
|
47
|
+
@connection = ActiveRecord::Base.connection
|
48
|
+
end
|
49
|
+
|
50
|
+
def load_schema!(&block)
|
51
|
+
ActiveRecord::Schema.verbose = false
|
52
|
+
if block_given?
|
53
|
+
ActiveRecord::Schema.define(&block)
|
54
|
+
else
|
55
|
+
ActiveRecord::Schema.define(&self.class.class_eval{@schema})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def teardown
|
60
|
+
teardown_database!
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup
|
64
|
+
setup_database!
|
65
|
+
load_schema!
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def assert_array_equal(expected, actual, *args)
|
70
|
+
assert_equal(expected.sort, actual.sort, *args)
|
71
|
+
end
|
72
|
+
|
73
|
+
def assert_array_includes(*args)
|
74
|
+
message = "Expected <%s> to include <%s>"
|
75
|
+
expected = args.clone
|
76
|
+
actual = expected.pop
|
77
|
+
|
78
|
+
if actual.is_a? String
|
79
|
+
message = actual
|
80
|
+
actual = expected.pop
|
81
|
+
end
|
82
|
+
|
83
|
+
expected.each do |el|
|
84
|
+
assert actual.include?(el), (message % [actual.inspect, el.inspect])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "test_helper")
|
2
|
+
|
3
|
+
class IndexesUnitTest < Test::Unit::TestCase
|
4
|
+
include CriminalDatabase
|
5
|
+
include ActiveRecord::Lint
|
6
|
+
|
7
|
+
def test_should_have_correct_indexes
|
8
|
+
scanner = Scanner.new(@connection)
|
9
|
+
assert_equal([Pair["incidents", "victim_id"]], scanner.indexes)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "test_helper")
|
2
|
+
|
3
|
+
class ModelScanningUnitTest < Test::Unit::TestCase
|
4
|
+
include CriminalDatabase
|
5
|
+
include ActiveRecord::Lint
|
6
|
+
|
7
|
+
def setup
|
8
|
+
ActiveRecord::Lint::unload_models
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_should_have_correct_indexes
|
13
|
+
rails_root = File.join(File.dirname(__FILE__), "..", "fixtures", "criminals")
|
14
|
+
ActiveRecord::Lint::load_models(rails_root)
|
15
|
+
|
16
|
+
scanner = ActiveRecord::Lint::Scanner.new
|
17
|
+
|
18
|
+
expected = [
|
19
|
+
Pair["incidents", "victim_id"],
|
20
|
+
Pair["incidents", "criminal_id"],
|
21
|
+
Pair["incidents", "crime_id"]
|
22
|
+
]
|
23
|
+
|
24
|
+
assert_array_equal(expected, scanner.foreign_keys)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "test_helper")
|
2
|
+
|
3
|
+
class PairUnitTest < Test::Unit::TestCase
|
4
|
+
include ActiveRecord::Lint
|
5
|
+
def test_comparison
|
6
|
+
assert_equal(Pair[1,2], Pair[1,2])
|
7
|
+
assert_equal(0, Pair[1,2] <=> Pair[1,2])
|
8
|
+
assert_equal(-1, Pair[0,2] <=> Pair[1,2])
|
9
|
+
assert_equal(1, Pair[2,2] <=> Pair[1,2])
|
10
|
+
assert_equal(-1, Pair[1,1] <=> Pair[1,2])
|
11
|
+
assert_equal(1, Pair[1,3] <=> Pair[1,2])
|
12
|
+
|
13
|
+
assert_not_equal(Pair[1,2], Pair[1,3])
|
14
|
+
assert_not_equal(Pair[1,2], Pair[2,2])
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_subtraction_in_an_array
|
18
|
+
a1 = [Pair[1,2], Pair[2,2], Pair[1,1]]
|
19
|
+
a2 = [Pair[1,2], Pair[1,1]]
|
20
|
+
a3 = [Pair[2,2]]
|
21
|
+
|
22
|
+
assert_equal(a2, a1 - a3)
|
23
|
+
assert_equal(a3, a1 - a2)
|
24
|
+
assert_equal([], a1 - a2 - a3)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_hashes
|
28
|
+
assert_equal(Pair[1,2].hash, Pair[1,2].hash)
|
29
|
+
assert_equal(Pair[2,2].hash, Pair[2,2].hash)
|
30
|
+
assert_not_equal(Pair[1,2].hash, Pair[1,1].hash)
|
31
|
+
assert_not_equal(Pair[2,1].hash, Pair[1,1].hash)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_eql
|
35
|
+
assert(Pair[2,2].eql?(Pair[2,2]))
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cwninja-active_record_lint
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Lea
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-08 00:00:00 -08:00
|
13
|
+
default_executable: arlint
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A library to support the automatic checking for the doing of stupid things with ActiveRecord.
|
17
|
+
email: commits@tomlea.co.uk
|
18
|
+
executables:
|
19
|
+
- arlint
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.markdown
|
24
|
+
files:
|
25
|
+
- Rakefile
|
26
|
+
- README.markdown
|
27
|
+
- lib/active_record/lint/memoize.rb
|
28
|
+
- lib/active_record/lint/pairs.rb
|
29
|
+
- lib/active_record/lint/reporter.rb
|
30
|
+
- lib/active_record/lint/scanner.rb
|
31
|
+
- lib/active_record/lint.rb
|
32
|
+
- lib/active_record_lint.rb
|
33
|
+
- test/fixtures/criminals/app/models/crime.rb
|
34
|
+
- test/fixtures/criminals/app/models/criminal.rb
|
35
|
+
- test/fixtures/criminals/app/models/incident.rb
|
36
|
+
- test/fixtures/criminals/app/models/victim.rb
|
37
|
+
- test/functional/missing_foreign_keys_test.rb
|
38
|
+
- test/functional/missing_indexes_test.rb
|
39
|
+
- test/functional/missing_tables_test.rb
|
40
|
+
- test/functional/reporter_test.rb
|
41
|
+
- test/test_helper.rb
|
42
|
+
- test/unit/indexes_test.rb
|
43
|
+
- test/unit/model_scanning_test.rb
|
44
|
+
- test/unit/pair_test.rb
|
45
|
+
- bin/arlint
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/cwninja/active_record_lint
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options:
|
50
|
+
- --line-numbers
|
51
|
+
- --inline-source
|
52
|
+
- --main
|
53
|
+
- README.markdown
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.2.0
|
72
|
+
signing_key:
|
73
|
+
specification_version: 2
|
74
|
+
summary: Rails tool to find foreign keys without indexes and other common issues.
|
75
|
+
test_files:
|
76
|
+
- test/functional/missing_foreign_keys_test.rb
|
77
|
+
- test/functional/missing_indexes_test.rb
|
78
|
+
- test/functional/missing_tables_test.rb
|
79
|
+
- test/functional/reporter_test.rb
|
80
|
+
- test/unit/indexes_test.rb
|
81
|
+
- test/unit/model_scanning_test.rb
|
82
|
+
- test/unit/pair_test.rb
|