broken_record 0.0.6 → 0.0.7
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 +5 -13
- data/CHANGELOG.md +10 -1
- data/README.md +42 -17
- data/broken_record.gemspec +3 -3
- data/lib/broken_record/config.rb +6 -1
- data/lib/broken_record/job.rb +66 -0
- data/lib/broken_record/job_result.rb +22 -0
- data/lib/broken_record/result_aggregator.rb +50 -0
- data/lib/broken_record/scanner.rb +26 -53
- data/lib/broken_record/tasks.rb +7 -4
- data/lib/broken_record/version.rb +1 -1
- metadata +39 -19
- data/lib/broken_record/logger.rb +0 -85
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
OWM4MTEwYmZjMmI3YmJhMjIzNzk3ZDMwYzg3YzE3ZDM2MGE3NTI0Zg==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0b5338e93e1f7a715a2f8bb0416a4a945d9ad297
|
4
|
+
data.tar.gz: 71a2a195aa089ee10b57675f6b9bd08a58fcffaf
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
OGY3Yjk1Mzc4YjQyOWFlYTc0MDMzZjUwYzNlYzcyODA4ZGVhMTkyYTQzNGE1
|
11
|
-
ODUxZTczNDg5ZTA1YzczM2NlY2NiNDhlOGM1Y2QwNjgzODgwNWU=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ZGYyYmI0ZDlkNjZmMzMxYmZkNGQ0YWUyZTBkMzNiMDAzYjliOWIwNTMzNDY0
|
14
|
-
NTY4YmUwNmE2MmQ4ZjY1ZGIzNDI5MjgxYzBlYzdhZjllOGZlYzk5MWUzYWI0
|
15
|
-
YmUzNjg4NWQ1Nzk4MGY3OTliMzJmYTA1NDY1MjIyMzY2ZjljOTc=
|
6
|
+
metadata.gz: fc9009d880eb5ad5cbae3f63d3f0b8e8dc2a8924b6a5d0a044a9b8bd9142118fa96a91f48b2b82eee1a8c44a09ec1e667c138fa9f2f5f9d67bd7628c0802eb84
|
7
|
+
data.tar.gz: 159098ba5d8b0fc69cadba33b3daf4c80da6fc34e8a62d456134bda83d33eed0247910054fea40c1e178ea0b2d8952a2008634169fd83eb9e6925c241596a853
|
data/CHANGELOG.md
CHANGED
@@ -17,4 +17,13 @@
|
|
17
17
|
|
18
18
|
## v0.0.6
|
19
19
|
|
20
|
-
* Allow classes_to_skip and default_scope keys to be strings as well as classes
|
20
|
+
* Allow classes_to_skip and default_scope keys to be strings as well as classes
|
21
|
+
|
22
|
+
## v0.0.7
|
23
|
+
|
24
|
+
* Make BrokenRecord work with colorize >= 0.5.8
|
25
|
+
* Allow BrokenRecord::Scanner to be used programmatically (i.e. from rails console)
|
26
|
+
* Remove assumption that all models are in app/models/**/*.rb
|
27
|
+
* Show backtrace when exception occurs during scan
|
28
|
+
* Change parallelization strategy to better distribute load across cores
|
29
|
+
* Add an after_fork hook
|
data/README.md
CHANGED
@@ -6,43 +6,68 @@ Provides a rake task for scanning your ActiveRecord models and detecting validat
|
|
6
6
|
|
7
7
|
Add this line to your application's Gemfile:
|
8
8
|
|
9
|
-
|
9
|
+
```ruby
|
10
|
+
gem 'broken_record'
|
11
|
+
```
|
10
12
|
|
11
13
|
And then execute:
|
12
14
|
|
13
|
-
|
15
|
+
```bash
|
16
|
+
bundle
|
17
|
+
```
|
14
18
|
|
15
19
|
Or install it yourself as:
|
16
20
|
|
17
|
-
|
21
|
+
```bash
|
22
|
+
gem install broken_record
|
23
|
+
```
|
18
24
|
|
19
25
|
## Usage
|
20
26
|
|
21
27
|
To scan all records of all models in your project:
|
22
28
|
|
23
|
-
|
29
|
+
```bash
|
30
|
+
rake broken_record:scan
|
31
|
+
```
|
24
32
|
|
25
|
-
If you want to scan all records of a specific model (e.g. the User model)
|
33
|
+
If you want to scan all records of a specific model (e.g. the User model):
|
26
34
|
|
27
|
-
|
35
|
+
```bash
|
36
|
+
rake broken_record:scan[User]
|
37
|
+
```
|
38
|
+
|
39
|
+
You can also specify a list of models to scan:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
rake broken_record:scan[Product,User]
|
43
|
+
```
|
28
44
|
|
29
45
|
## Configuration
|
30
46
|
|
31
|
-
BrokenRecord provides a configure method with
|
47
|
+
BrokenRecord provides a configure method with multiple options. Here's an example:
|
32
48
|
|
33
|
-
|
34
|
-
|
35
|
-
|
49
|
+
```ruby
|
50
|
+
BrokenRecord.configure do |config|
|
51
|
+
# Skip the Foo and Bar models when scanning.
|
52
|
+
config.classes_to_skip = [Foo, Bar]
|
36
53
|
|
37
|
-
|
38
|
-
|
54
|
+
# Set a scope for which models should be validated
|
55
|
+
config.default_scopes = { Foo => proc { with_bars } }
|
56
|
+
|
57
|
+
# The follow block will be called before scanning your records.
|
58
|
+
# This is useful for skipping validations you want to ignore.
|
59
|
+
config.before_scan do
|
60
|
+
User.skip_callback :validate, :before, :user_must_be_active
|
61
|
+
end
|
39
62
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
63
|
+
# BrokenRecord uses the parallelize gem to distribute work across
|
64
|
+
# multiple cores. The following block will be called every time
|
65
|
+
# the process is forked (useful for re-establishing connections).
|
66
|
+
config.after_fork do
|
67
|
+
Rails.cache.reconnect if Rails.cache.respond_to? :reconnect
|
45
68
|
end
|
69
|
+
end
|
70
|
+
```
|
46
71
|
|
47
72
|
## Contributing
|
48
73
|
|
data/broken_record.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
22
22
|
|
23
|
-
spec.add_runtime_dependency 'rake', '~> 10'
|
24
|
-
spec.add_runtime_dependency 'parallel', '~>
|
25
|
-
spec.add_runtime_dependency 'colorize', '0.5.8'
|
23
|
+
spec.add_runtime_dependency 'rake', '~> 10.1', '>= 10.1.10'
|
24
|
+
spec.add_runtime_dependency 'parallel', '~> 1.2', '>= 1.2.3'
|
25
|
+
spec.add_runtime_dependency 'colorize', '~> 0.5', '>= 0.5.8'
|
26
26
|
end
|
data/lib/broken_record/config.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
module BrokenRecord
|
2
2
|
module Config
|
3
3
|
extend self
|
4
|
-
attr_accessor :classes_to_skip, :before_scan_callbacks, :default_scopes
|
4
|
+
attr_accessor :classes_to_skip, :before_scan_callbacks, :after_fork_callbacks, :default_scopes
|
5
5
|
self.before_scan_callbacks = []
|
6
|
+
self.after_fork_callbacks = []
|
6
7
|
self.default_scopes = {}
|
7
8
|
|
8
9
|
def before_scan(&block)
|
9
10
|
self.before_scan_callbacks << block
|
10
11
|
end
|
12
|
+
|
13
|
+
def after_fork(&block)
|
14
|
+
self.after_fork_callbacks << block
|
15
|
+
end
|
11
16
|
end
|
12
17
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'broken_record/job_result'
|
2
|
+
|
3
|
+
module BrokenRecord
|
4
|
+
class Job
|
5
|
+
JOBS_PER_PROCESSOR = 1
|
6
|
+
|
7
|
+
attr_accessor :klass, :index
|
8
|
+
|
9
|
+
def self.jobs_per_class
|
10
|
+
JOBS_PER_PROCESSOR * Parallel.processor_count
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.build_jobs(classes)
|
14
|
+
jobs = []
|
15
|
+
classes.each do |klass|
|
16
|
+
jobs_per_class.times do |index|
|
17
|
+
jobs << Job.new(:klass => klass, :index => index)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
jobs
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(options)
|
24
|
+
options.each { |k, v| send("#{k}=", v) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def perform
|
28
|
+
result = BrokenRecord::JobResult.new(self)
|
29
|
+
result.start_timer
|
30
|
+
|
31
|
+
records.each do |r|
|
32
|
+
begin
|
33
|
+
if !r.valid?
|
34
|
+
message = " Invalid record in #{klass} id=#{r.id}."
|
35
|
+
r.errors.each { |attr,msg| message << "\n #{attr} - #{msg}" }
|
36
|
+
result.add_error message
|
37
|
+
end
|
38
|
+
rescue Exception => e
|
39
|
+
message = " Exception for record in #{klass} id=#{r.id} - #{e}.\n"
|
40
|
+
message << e.backtrace.map { |line| " #{line}"}.join("\n")
|
41
|
+
result.add_error message
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
result.stop_timer
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def records
|
52
|
+
default_scope = BrokenRecord::Config.default_scopes[klass] || BrokenRecord::Config.default_scopes[klass.to_s]
|
53
|
+
|
54
|
+
model_scope = if default_scope
|
55
|
+
klass.instance_exec &default_scope
|
56
|
+
else
|
57
|
+
klass.unscoped
|
58
|
+
end
|
59
|
+
|
60
|
+
records_per_group = model_scope.count / self.class.jobs_per_class
|
61
|
+
scope = model_scope.offset(records_per_group * index)
|
62
|
+
|
63
|
+
index == Parallel.processor_count - 1 ? scope.load : scope.first(records_per_group)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module BrokenRecord
|
2
|
+
class JobResult
|
3
|
+
attr_reader :start_time, :end_time, :errors, :job
|
4
|
+
|
5
|
+
def initialize(job)
|
6
|
+
@job = job
|
7
|
+
@errors = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def start_timer
|
11
|
+
@start_time = Time.now
|
12
|
+
end
|
13
|
+
|
14
|
+
def stop_timer
|
15
|
+
@end_time = Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_error(error)
|
19
|
+
@errors << "#{error.red}\n"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module BrokenRecord
|
2
|
+
class ResultAggregator
|
3
|
+
def initialize
|
4
|
+
@total_errors = 0
|
5
|
+
@aggregated_results = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_result(result)
|
9
|
+
@aggregated_results[result.job.klass] ||= []
|
10
|
+
@aggregated_results[result.job.klass] << result
|
11
|
+
|
12
|
+
if klass_done?(result.job.klass)
|
13
|
+
report_results result.job.klass
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def report_final_results
|
18
|
+
if @total_errors == 0
|
19
|
+
puts "\nAll models validated successfully.".green
|
20
|
+
else
|
21
|
+
puts "\n#{@total_errors} errors were found while running validations.".red
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def klass_done?(klass)
|
29
|
+
@aggregated_results[klass].count == Job.jobs_per_class
|
30
|
+
end
|
31
|
+
|
32
|
+
def report_results(klass)
|
33
|
+
all_errors = @aggregated_results[klass].map(&:errors).flatten
|
34
|
+
start_time = @aggregated_results[klass].map(&:start_time).min
|
35
|
+
end_time = @aggregated_results[klass].map(&:end_time).max
|
36
|
+
duration = (end_time - start_time).round(3)
|
37
|
+
|
38
|
+
@total_errors += all_errors.count
|
39
|
+
|
40
|
+
print "Validating model #{klass}... ".ljust(70)
|
41
|
+
if all_errors.empty?
|
42
|
+
print '[PASS]'.green
|
43
|
+
else
|
44
|
+
print '[FAIL]'.red
|
45
|
+
end
|
46
|
+
print " (#{duration}s)\n"
|
47
|
+
print all_errors.join if all_errors.any?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,78 +1,51 @@
|
|
1
|
-
require
|
1
|
+
require 'broken_record/job'
|
2
|
+
require 'broken_record/result_aggregator'
|
2
3
|
require 'parallel'
|
3
4
|
|
4
5
|
module BrokenRecord
|
5
6
|
class Scanner
|
6
|
-
def run(
|
7
|
-
|
7
|
+
def run(class_names)
|
8
|
+
ResultAggregator.new.tap do |aggregator|
|
9
|
+
classes = classes_to_validate(class_names)
|
8
10
|
|
9
|
-
|
11
|
+
BrokenRecord::Config.before_scan_callbacks.each { |callback| callback.call }
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
result
|
13
|
+
jobs = BrokenRecord::Job.build_jobs(classes)
|
14
|
+
|
15
|
+
callback = proc do |_, _, result|
|
16
|
+
aggregator.add_result result if result.is_a? BrokenRecord::JobResult
|
16
17
|
end
|
17
|
-
end
|
18
18
|
|
19
|
-
|
19
|
+
Parallel.each(jobs, :finish => callback) do |job|
|
20
|
+
ActiveRecord::Base.connection.reconnect!
|
21
|
+
BrokenRecord::Config.after_fork_callbacks.each { |callback| callback.call }
|
22
|
+
job.perform
|
23
|
+
end
|
24
|
+
end
|
20
25
|
end
|
21
26
|
|
22
27
|
private
|
23
28
|
|
24
|
-
def
|
25
|
-
if
|
26
|
-
[ model_name.constantize ]
|
27
|
-
else
|
29
|
+
def classes_to_validate(class_names)
|
30
|
+
if class_names.empty?
|
28
31
|
load_all_active_record_classes
|
32
|
+
else
|
33
|
+
class_names.map(&:strip).map(&:constantize)
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
32
37
|
def load_all_active_record_classes
|
33
|
-
|
38
|
+
Rails.application.eager_load!
|
34
39
|
objects = Set.new
|
35
40
|
# Classes to skip may either be constants or strings. Convert all to strings for easier lookup
|
36
41
|
classes_to_skip = BrokenRecord::Config.classes_to_skip.map(&:to_s)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
objects.add klass.base_class unless classes_to_skip.include?(klass.to_s)
|
42
|
-
end
|
42
|
+
ActiveRecord::Base.descendants.each do |klass|
|
43
|
+
# Use base_class so we don't try to validate abstract classes and so we don't validate
|
44
|
+
# STI classes multiple times. See active_record/inheritance.rb for more details.
|
45
|
+
objects.add klass.base_class unless classes_to_skip.include?(klass.to_s)
|
43
46
|
end
|
44
47
|
|
45
48
|
objects.sort_by(&:name)
|
46
49
|
end
|
47
|
-
|
48
|
-
def validate_model(model)
|
49
|
-
ActiveRecord::Base.connection.reconnect!
|
50
|
-
|
51
|
-
BrokenRecord::Logger.log(model) do |logger|
|
52
|
-
begin
|
53
|
-
default_scope = BrokenRecord::Config.default_scopes[model] || BrokenRecord::Config.default_scopes[model.to_s]
|
54
|
-
|
55
|
-
if default_scope
|
56
|
-
model_scope = model.instance_exec &default_scope
|
57
|
-
else
|
58
|
-
model_scope = model.unscoped
|
59
|
-
end
|
60
|
-
|
61
|
-
model_scope.find_each do |r|
|
62
|
-
begin
|
63
|
-
if !r.valid?
|
64
|
-
message = " Invalid record in #{model} id=#{r.id}."
|
65
|
-
r.errors.each { |attr,msg| message << "\n #{attr} - #{msg}" }
|
66
|
-
logger.log_error message
|
67
|
-
end
|
68
|
-
rescue Exception => msg
|
69
|
-
logger.log_error " Exception for record in #{model} id=#{r.id} - #{msg}."
|
70
|
-
end
|
71
|
-
end
|
72
|
-
rescue Exception => msg
|
73
|
-
logger.log_error " Error querying model #{model} - #{msg}."
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
50
|
end
|
78
|
-
end
|
51
|
+
end
|
data/lib/broken_record/tasks.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'rake'
|
2
2
|
|
3
3
|
namespace :broken_record do
|
4
|
-
desc 'Scans
|
5
|
-
task :scan, [:
|
4
|
+
desc 'Scans models for validation errors'
|
5
|
+
task :scan, [:class_name] => :environment do |t, args|
|
6
6
|
scanner = BrokenRecord::Scanner.new
|
7
|
-
|
7
|
+
class_names = args[:class_name] ? [args[:class_name]] : []
|
8
|
+
class_names += args.extras
|
9
|
+
aggregator = scanner.run(class_names)
|
10
|
+
aggregator.report_final_results
|
8
11
|
end
|
9
|
-
end
|
12
|
+
end
|
metadata
CHANGED
@@ -1,69 +1,87 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: broken_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicholas Gervasi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.3'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '10'
|
33
|
+
version: '10.1'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 10.1.10
|
34
37
|
type: :runtime
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
|
-
- - ~>
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '10.1'
|
44
|
+
- - ">="
|
39
45
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
46
|
+
version: 10.1.10
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: parallel
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
44
50
|
requirements:
|
45
|
-
- - ~>
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.2'
|
54
|
+
- - ">="
|
46
55
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
56
|
+
version: 1.2.3
|
48
57
|
type: :runtime
|
49
58
|
prerelease: false
|
50
59
|
version_requirements: !ruby/object:Gem::Requirement
|
51
60
|
requirements:
|
52
|
-
- - ~>
|
61
|
+
- - "~>"
|
53
62
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
63
|
+
version: '1.2'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 1.2.3
|
55
67
|
- !ruby/object:Gem::Dependency
|
56
68
|
name: colorize
|
57
69
|
requirement: !ruby/object:Gem::Requirement
|
58
70
|
requirements:
|
59
|
-
- -
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0.5'
|
74
|
+
- - ">="
|
60
75
|
- !ruby/object:Gem::Version
|
61
76
|
version: 0.5.8
|
62
77
|
type: :runtime
|
63
78
|
prerelease: false
|
64
79
|
version_requirements: !ruby/object:Gem::Requirement
|
65
80
|
requirements:
|
66
|
-
- -
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.5'
|
84
|
+
- - ">="
|
67
85
|
- !ruby/object:Gem::Version
|
68
86
|
version: 0.5.8
|
69
87
|
description: Detects ActiveRecord models that are not valid.
|
@@ -73,7 +91,7 @@ executables: []
|
|
73
91
|
extensions: []
|
74
92
|
extra_rdoc_files: []
|
75
93
|
files:
|
76
|
-
- .gitignore
|
94
|
+
- ".gitignore"
|
77
95
|
- CHANGELOG.md
|
78
96
|
- Gemfile
|
79
97
|
- LICENSE.txt
|
@@ -82,8 +100,10 @@ files:
|
|
82
100
|
- broken_record.gemspec
|
83
101
|
- lib/broken_record.rb
|
84
102
|
- lib/broken_record/config.rb
|
85
|
-
- lib/broken_record/
|
103
|
+
- lib/broken_record/job.rb
|
104
|
+
- lib/broken_record/job_result.rb
|
86
105
|
- lib/broken_record/railtie.rb
|
106
|
+
- lib/broken_record/result_aggregator.rb
|
87
107
|
- lib/broken_record/scanner.rb
|
88
108
|
- lib/broken_record/tasks.rb
|
89
109
|
- lib/broken_record/version.rb
|
@@ -97,17 +117,17 @@ require_paths:
|
|
97
117
|
- lib
|
98
118
|
required_ruby_version: !ruby/object:Gem::Requirement
|
99
119
|
requirements:
|
100
|
-
- -
|
120
|
+
- - ">="
|
101
121
|
- !ruby/object:Gem::Version
|
102
122
|
version: '0'
|
103
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
124
|
requirements:
|
105
|
-
- -
|
125
|
+
- - ">="
|
106
126
|
- !ruby/object:Gem::Version
|
107
127
|
version: '0'
|
108
128
|
requirements: []
|
109
129
|
rubyforge_project:
|
110
|
-
rubygems_version: 2.
|
130
|
+
rubygems_version: 2.4.6
|
111
131
|
signing_key:
|
112
132
|
specification_version: 4
|
113
133
|
summary: Provides a rake task for scanning your ActiveRecord models and detecting
|
data/lib/broken_record/logger.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
require 'colorize'
|
2
|
-
require 'tempfile'
|
3
|
-
|
4
|
-
module BrokenRecord
|
5
|
-
class Logger
|
6
|
-
|
7
|
-
# Static Methods
|
8
|
-
|
9
|
-
def self.report_output(result, lock = nil)
|
10
|
-
lock.flock File::LOCK_EX if lock
|
11
|
-
$stdout.print result[:stdout]
|
12
|
-
$stdout.flush
|
13
|
-
ensure
|
14
|
-
lock.flock File::LOCK_UN if lock
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.report_results(test_results)
|
18
|
-
total_errors = 0
|
19
|
-
test_results.each { |result| total_errors += result[:error_count] }
|
20
|
-
if total_errors == 0
|
21
|
-
puts "\nAll models validated successfully.".green
|
22
|
-
else
|
23
|
-
puts "\n#{total_errors} errors were found while running validations.".red
|
24
|
-
exit 1
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.parallel
|
29
|
-
Tempfile.open 'broken_record_lock' do |lock|
|
30
|
-
yield lock
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.log(model, &block)
|
35
|
-
logger = new
|
36
|
-
logger.start_log
|
37
|
-
logger.log_header "Validating model #{model}... ".ljust(70)
|
38
|
-
|
39
|
-
yield(logger)
|
40
|
-
|
41
|
-
logger.log_result
|
42
|
-
logger.result
|
43
|
-
end
|
44
|
-
|
45
|
-
# Instance Methods
|
46
|
-
|
47
|
-
def initialize
|
48
|
-
@header = ''
|
49
|
-
@errors = []
|
50
|
-
|
51
|
-
@stdout = ''
|
52
|
-
end
|
53
|
-
|
54
|
-
def log_header(header_message)
|
55
|
-
@header = header_message
|
56
|
-
end
|
57
|
-
|
58
|
-
def start_log
|
59
|
-
@start_time = Time.now
|
60
|
-
end
|
61
|
-
|
62
|
-
def log_error(message)
|
63
|
-
@errors << "#{message.red}\n"
|
64
|
-
end
|
65
|
-
|
66
|
-
def log_result
|
67
|
-
@stdout << @header
|
68
|
-
|
69
|
-
if @errors.empty?
|
70
|
-
@stdout << '[PASS]'.green
|
71
|
-
else
|
72
|
-
@stdout << '[FAIL]'.red
|
73
|
-
end
|
74
|
-
|
75
|
-
duration = (Time.now - @start_time).round(3)
|
76
|
-
@stdout << " (#{duration}s)\n"
|
77
|
-
|
78
|
-
@stdout << @errors.join
|
79
|
-
end
|
80
|
-
|
81
|
-
def result
|
82
|
-
{ stdout: @stdout, error_count: @errors.count}
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|