cwninja-active_record_lint 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|