consistency_fail 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -19,16 +19,14 @@ subject](http://blog.8thlight.com/articles/2011/6/11/winning-at-consistency).
19
19
 
20
20
  ## Installation
21
21
 
22
- For Rails 3:
22
+ I'm currently only maintaining consistency\_fail for Rails 3. If you need or
23
+ want Rails 2.3 support, I'd be happy to take patches based off the 0.1.1
24
+ branch.
23
25
 
24
26
  gem install consistency_fail
25
27
 
26
- For Rails 2.3:
27
-
28
- gem install consistency_fail -v=0.1.1
29
-
30
28
  Or, even better if you're using bundler, add it to your Gemfile, with the
31
- appropriate version number for your Rails version.
29
+ appropriate version number to match your Rails version.
32
30
 
33
31
  ## Limitations
34
32
 
@@ -36,7 +34,8 @@ consistency\_fail depends on being able to find all your `ActiveRecord::Base`
36
34
  subclasses with some `$LOAD_PATH` trickery. If any models are in a path either
37
35
  not on your project's load path or in a path that doesn't include the word
38
36
  "models", consistency\_fail won't be able to find or analyze them. I'm open to
39
- making the text "models" configurable if people want that.
37
+ making the text "models" configurable if people want that. Please open an issue
38
+ or pull request if so!
40
39
 
41
40
  ## Usage
42
41
 
@@ -64,9 +63,9 @@ Gemfile, or by some other mechanism.
64
63
  This mega-fail mode is nice to have if you have a large team and want to ensure
65
64
  that new models or validations/associations follow the rules.
66
65
 
67
- If you're using the `Enforcer`, depending on your project, you may need to
66
+ If you're using the `Enforcer`, depending on your project, you may need to
68
67
  delay the initializer until later, so that model files can be loaded only once
69
- gem dependencies have been satisfied. One possible way is to move the code above
68
+ gem dependencies have been satisfied. One possible way is to move the code above
70
69
  to the end of `environment.rb` or to the more specific `config/environment/*` files.
71
70
 
72
71
  ## License
data/bin/consistency_fail CHANGED
@@ -26,11 +26,20 @@ models.preload_all
26
26
 
27
27
  reporter = ConsistencyFail::Reporter.new
28
28
 
29
+ success = true
30
+
29
31
  introspector = ConsistencyFail::Introspectors::ValidatesUniquenessOf.new
30
32
  problems = problems(models.all, introspector)
31
33
  reporter.report_validates_uniqueness_problems(problems)
34
+ success &&= problems.empty?
32
35
 
33
36
  introspector = ConsistencyFail::Introspectors::HasOne.new
34
37
  problems = problems(models.all, introspector)
35
38
  reporter.report_has_one_problems(problems)
39
+ success &&= problems.empty?
36
40
 
41
+ if success
42
+ exit 0
43
+ else
44
+ exit 1
45
+ end
@@ -1,13 +1,15 @@
1
1
  module ConsistencyFail
2
2
  class Index
3
- attr_reader :table_name, :columns
4
- def initialize(table_name, columns)
3
+ attr_reader :model, :table_name, :columns
4
+ def initialize(model, table_name, columns)
5
+ @model = model
5
6
  @table_name = table_name
6
7
  @columns = columns.map(&:to_s)
7
8
  end
8
9
 
9
10
  def ==(other)
10
- self.table_name == other.table_name && self.columns.sort == other.columns.sort
11
+ self.table_name == other.table_name &&
12
+ self.columns.sort == other.columns.sort
11
13
  end
12
14
  end
13
15
  end
@@ -12,7 +12,9 @@ module ConsistencyFail
12
12
  # TODO: handle has_one :through cases (multicolumn index on the join table?)
13
13
  def desired_indexes(model)
14
14
  instances(model).map do |a|
15
- ConsistencyFail::Index.new(a.table_name.to_s, [a.primary_key_name]) rescue nil # TODO: why?
15
+ ConsistencyFail::Index.new(a.class_name.constantize,
16
+ a.table_name.to_s,
17
+ [a.foreign_key])
16
18
  end.compact
17
19
  end
18
20
  private :desired_indexes
@@ -21,8 +23,9 @@ module ConsistencyFail
21
23
  desired = desired_indexes(model)
22
24
 
23
25
  existing_indexes = desired.inject([]) do |acc, d|
24
- # TODO: This assumes the models share a database. Need to make that configurable somehow.
25
- acc += TableData.new.unique_indexes_by_table(model.connection, d.table_name)
26
+ acc += TableData.new.unique_indexes_by_table(d.model,
27
+ d.model.connection,
28
+ d.table_name)
26
29
  end
27
30
 
28
31
  desired.reject do |index|
@@ -6,14 +6,17 @@ module ConsistencyFail
6
6
  def unique_indexes(model)
7
7
  return [] if !model.table_exists?
8
8
 
9
- unique_indexes_by_table(model.connection, model.table_name)
9
+ unique_indexes_by_table(model, model.connection, model.table_name)
10
10
  end
11
11
 
12
- def unique_indexes_by_table(connection, table_name)
12
+ def unique_indexes_by_table(model, connection, table_name)
13
13
  ar_indexes = connection.indexes(table_name).select(&:unique)
14
- ar_indexes.map do |index|
15
- ConsistencyFail::Index.new(table_name, index.columns)
14
+ result = ar_indexes.map do |index|
15
+ ConsistencyFail::Index.new(model,
16
+ table_name,
17
+ index.columns)
16
18
  end
19
+ result
17
20
  end
18
21
  end
19
22
  end
@@ -13,7 +13,9 @@ module ConsistencyFail
13
13
  instances(model).map do |v|
14
14
  v.attributes.map do |attribute|
15
15
  scoped_columns = v.options[:scope] || []
16
- ConsistencyFail::Index.new(model.table_name, [attribute, *scoped_columns])
16
+ ConsistencyFail::Index.new(model,
17
+ model.table_name,
18
+ [attribute, *scoped_columns])
17
19
  end
18
20
  end.flatten
19
21
  end
@@ -1,3 +1,3 @@
1
1
  module ConsistencyFail
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
data/spec/index_spec.rb CHANGED
@@ -4,33 +4,34 @@ require 'consistency_fail/index'
4
4
  describe ConsistencyFail::Index do
5
5
 
6
6
  describe "value objectiness" do
7
- it "holds onto table name and columns" do
8
- index = ConsistencyFail::Index.new("addresses", ["city", "state"])
7
+ it "holds onto model, table name, and columns" do
8
+ model = double("model")
9
+ index = ConsistencyFail::Index.new(model, "addresses", ["city", "state"])
10
+ index.model.should == model
9
11
  index.table_name.should == "addresses"
10
12
  index.columns.should == ["city", "state"]
11
13
  end
12
14
 
13
15
  it "leaves columns in the initial order (since we only care about presence, not performance)" do
14
- index = ConsistencyFail::Index.new("addresses", ["state", "city"])
15
- index.table_name.should == "addresses"
16
+ index = ConsistencyFail::Index.new(double('model'), "addresses", ["state", "city"])
16
17
  index.columns.should == ["state", "city"]
17
18
  end
18
19
  end
19
20
 
20
21
  describe "equality test" do
21
22
  it "passes when everything matches" do
22
- ConsistencyFail::Index.new("addresses", ["city", "state"]).should ==
23
- ConsistencyFail::Index.new("addresses", ["city", "state"])
23
+ ConsistencyFail::Index.new(double('model'), "addresses", ["city", "state"]).should ==
24
+ ConsistencyFail::Index.new(double('model'),"addresses", ["city", "state"])
24
25
  end
25
26
 
26
27
  it "fails when tables are different" do
27
- ConsistencyFail::Index.new("locations", ["city", "state"]).should_not ==
28
- ConsistencyFail::Index.new("addresses", ["city", "state"])
28
+ ConsistencyFail::Index.new(double('model'),"locations", ["city", "state"]).should_not ==
29
+ ConsistencyFail::Index.new(double('model'),"addresses", ["city", "state"])
29
30
  end
30
31
 
31
32
  it "fails when columns are different" do
32
- ConsistencyFail::Index.new("addresses", ["city", "state"]).should_not ==
33
- ConsistencyFail::Index.new("addresses", ["state", "zip"])
33
+ ConsistencyFail::Index.new(double('model'),"addresses", ["city", "state"]).should_not ==
34
+ ConsistencyFail::Index.new(double('model'),"addresses", ["state", "zip"])
34
35
  end
35
36
  end
36
37
  end
@@ -37,26 +37,30 @@ describe ConsistencyFail::Introspectors::HasOne do
37
37
  @association = double("association", :macro => :has_one)
38
38
  @model = fake_ar_model("User", :table_exists? => true,
39
39
  :table_name => "users",
40
+ :class_name => "User",
40
41
  :reflect_on_all_associations => [@association])
42
+ @address_class = double("Address Class")
43
+ @address_string = "Address"
44
+ @address_string.stub(:constantize).and_return(@address_class)
41
45
  end
42
46
 
43
47
  it "finds one" do
44
- @association.stub!(:table_name => :addresses, :primary_key_name => "user_id")
45
- @model.stub_chain(:connection, :indexes).with("addresses").and_return([])
48
+ @association.stub!(:table_name => :addresses, :class_name => @address_string, :foreign_key => "user_id")
49
+ @address_class.stub_chain(:connection, :indexes).with("addresses").and_return([])
46
50
 
47
51
  indexes = subject.missing_indexes(@model)
48
- indexes.should == [ConsistencyFail::Index.new("addresses", ["user_id"])]
52
+ indexes.should == [ConsistencyFail::Index.new(fake_ar_model("Address"), "addresses", ["user_id"])]
49
53
  end
50
54
 
51
55
  it "finds none when they're already in place" do
52
- @association.stub!(:table_name => :addresses, :primary_key_name => "user_id")
53
- index = ConsistencyFail::Index.new("addresses", ["user_id"])
56
+ @association.stub!(:table_name => :addresses, :class_name => @address_string, :foreign_key => "user_id")
57
+ index = ConsistencyFail::Index.new(double('model'), "addresses", ["user_id"])
54
58
 
55
59
  fake_connection = double("connection")
56
- @model.stub_chain(:connection).and_return(fake_connection)
60
+ @address_class.stub_chain(:connection).and_return(fake_connection)
57
61
 
58
62
  ConsistencyFail::Introspectors::TableData.stub_chain(:new, :unique_indexes_by_table).
59
- with(fake_connection, "addresses").
63
+ with(@address_class, fake_connection, "addresses").
60
64
  and_return([index])
61
65
 
62
66
  subject.missing_indexes(@model).should == []
@@ -18,7 +18,7 @@ describe ConsistencyFail::Introspectors::TableData do
18
18
  and_return([fake_index_on(["a"], :unique => true)])
19
19
 
20
20
  indexes = subject.unique_indexes(model)
21
- indexes.should == [ConsistencyFail::Index.new("users", ["a"])]
21
+ indexes.should == [ConsistencyFail::Index.new(double('model'), "users", ["a"])]
22
22
  end
23
23
 
24
24
  it "doesn't get non-unique indexes" do
@@ -43,8 +43,8 @@ describe ConsistencyFail::Introspectors::TableData do
43
43
 
44
44
  indexes = subject.unique_indexes(model)
45
45
  indexes.size.should == 2
46
- indexes.should == [ConsistencyFail::Index.new("users", ["a"]),
47
- ConsistencyFail::Index.new("users", ["b", "c"])]
46
+ indexes.should == [ConsistencyFail::Index.new(double('model'), "users", ["a"]),
47
+ ConsistencyFail::Index.new(double('model'), "users", ["b", "c"])]
48
48
  end
49
49
  end
50
50
 
@@ -44,7 +44,7 @@ describe ConsistencyFail::Introspectors::ValidatesUniquenessOf do
44
44
  @model.stub_chain(:connection, :indexes).with("users").and_return([])
45
45
 
46
46
  indexes = subject.missing_indexes(@model)
47
- indexes.should == [ConsistencyFail::Index.new("users", ["email"])]
47
+ indexes.should == [ConsistencyFail::Index.new(double('model'), "users", ["email"])]
48
48
  end
49
49
 
50
50
  it "finds one where the validation has scoped columns" do
@@ -52,7 +52,7 @@ describe ConsistencyFail::Introspectors::ValidatesUniquenessOf do
52
52
  @model.stub_chain(:connection, :indexes).with("users").and_return([])
53
53
 
54
54
  indexes = subject.missing_indexes(@model)
55
- indexes.should == [ConsistencyFail::Index.new("users", ["city", "email", "state"])]
55
+ indexes.should == [ConsistencyFail::Index.new(double('model'), "users", ["city", "email", "state"])]
56
56
  end
57
57
 
58
58
  it "leaves the columns in the given order" do
@@ -60,7 +60,7 @@ describe ConsistencyFail::Introspectors::ValidatesUniquenessOf do
60
60
  @model.stub_chain(:connection, :indexes).with("users").and_return([])
61
61
 
62
62
  indexes = subject.missing_indexes(@model)
63
- indexes.should == [ConsistencyFail::Index.new("users", ["email", "city", "state"])]
63
+ indexes.should == [ConsistencyFail::Index.new(double('model'), "users", ["email", "city", "state"])]
64
64
  end
65
65
 
66
66
  it "finds two where there are multiple attributes" do
@@ -68,8 +68,8 @@ describe ConsistencyFail::Introspectors::ValidatesUniquenessOf do
68
68
  @model.stub_chain(:connection, :indexes).with("users").and_return([])
69
69
 
70
70
  indexes = subject.missing_indexes(@model)
71
- indexes.should == [ConsistencyFail::Index.new("users", ["email", "city", "state"]),
72
- ConsistencyFail::Index.new("users", ["name", "city", "state"])]
71
+ indexes.should == [ConsistencyFail::Index.new(double('model'), "users", ["email", "city", "state"]),
72
+ ConsistencyFail::Index.new(double('model'), "users", ["name", "city", "state"])]
73
73
  end
74
74
 
75
75
  it "finds none when they're already in place" do
@@ -20,7 +20,7 @@ describe ConsistencyFail::Reporter do
20
20
  end
21
21
 
22
22
  it "shows a missing single-column index on a single model" do
23
- missing_indexes = [ConsistencyFail::Index.new("users", ["email"])]
23
+ missing_indexes = [ConsistencyFail::Index.new(double('model'), "users", ["email"])]
24
24
 
25
25
  subject.report_validates_uniqueness_problems(fake_ar_model("User", :table_name => "users") => missing_indexes)
26
26
 
@@ -28,7 +28,7 @@ describe ConsistencyFail::Reporter do
28
28
  end
29
29
 
30
30
  it "shows a missing multiple-column index on a single model" do
31
- missing_indexes = [ConsistencyFail::Index.new("addresses", ["number", "street", "zip"])]
31
+ missing_indexes = [ConsistencyFail::Index.new(double('model'),"addresses", ["number", "street", "zip"])]
32
32
 
33
33
  subject.report_validates_uniqueness_problems(fake_ar_model("Address", :table_name => "addresses") => missing_indexes)
34
34
 
@@ -39,9 +39,9 @@ describe ConsistencyFail::Reporter do
39
39
  before(:each) do
40
40
  subject.report_validates_uniqueness_problems(
41
41
  fake_ar_model("User", :table_name => "users") =>
42
- [ConsistencyFail::Index.new("users", ["email"])],
42
+ [ConsistencyFail::Index.new(double('model'),"users", ["email"])],
43
43
  fake_ar_model("Citizen", :table_name => "citizens") =>
44
- [ConsistencyFail::Index.new("citizens", ["ssn"])]
44
+ [ConsistencyFail::Index.new(double('model'),"citizens", ["ssn"])]
45
45
  )
46
46
  end
47
47
 
@@ -64,7 +64,7 @@ describe ConsistencyFail::Reporter do
64
64
  end
65
65
 
66
66
  it "shows a missing single-column index on a single model" do
67
- missing_indexes = [ConsistencyFail::Index.new("users", ["email"])]
67
+ missing_indexes = [ConsistencyFail::Index.new(double('model'),"users", ["email"])]
68
68
 
69
69
  subject.report_has_one_problems(fake_ar_model("Friend", :table_name => "users") => missing_indexes)
70
70
 
metadata CHANGED
@@ -1,69 +1,70 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: consistency_fail
3
- version: !ruby/object:Gem::Version
4
- hash: 19
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 2
9
- - 2
10
- version: 0.2.2
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Colin Jones
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-01-04 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2013-01-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: activerecord
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
18
+ requirements:
26
19
  - - ~>
27
- - !ruby/object:Gem::Version
28
- hash: 7
29
- segments:
30
- - 3
31
- - 0
32
- version: "3.0"
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
33
22
  type: :development
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: rspec
37
23
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
39
25
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
47
38
  type: :development
48
- version_requirements: *id002
49
- description: |
50
- With more than one application server, validates_uniqueness_of becomes a lie.
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: ! 'With more than one application server, validates_uniqueness_of becomes
47
+ a lie.
48
+
51
49
  Two app servers -> two requests -> two near-simultaneous uniqueness checks ->
50
+
52
51
  two processes that commit to the database independently, violating this faux
53
- constraint. You'll need a database-level constraint for cases like these.
54
-
52
+
53
+ constraint. You''ll need a database-level constraint for cases like these.
54
+
55
+
55
56
  consistency_fail will find your missing unique indexes, so you can add them and
57
+
56
58
  stop ignoring the C in ACID.
57
59
 
58
- email:
60
+ '
61
+ email:
59
62
  - colin@8thlight.com
60
- executables:
63
+ executables:
61
64
  - consistency_fail
62
65
  extensions: []
63
-
64
66
  extra_rdoc_files: []
65
-
66
- files:
67
+ files:
67
68
  - .gitignore
68
69
  - Gemfile
69
70
  - LICENSE
@@ -83,7 +84,6 @@ files:
83
84
  - lib/consistency_fail/reporters/has_one.rb
84
85
  - lib/consistency_fail/reporters/validates_uniqueness_of.rb
85
86
  - lib/consistency_fail/version.rb
86
- - spec/consistency_fail_spec.rb
87
87
  - spec/index_spec.rb
88
88
  - spec/introspectors/has_one_spec.rb
89
89
  - spec/introspectors/table_data_spec.rb
@@ -93,36 +93,33 @@ files:
93
93
  - spec/spec_helper.rb
94
94
  homepage: http://github.com/trptcolin/consistency_fail
95
95
  licenses: []
96
-
97
96
  post_install_message:
98
97
  rdoc_options: []
99
-
100
- require_paths:
98
+ require_paths:
101
99
  - lib
102
- required_ruby_version: !ruby/object:Gem::Requirement
100
+ required_ruby_version: !ruby/object:Gem::Requirement
103
101
  none: false
104
- requirements:
105
- - - ">="
106
- - !ruby/object:Gem::Version
107
- hash: 3
108
- segments:
109
- - 0
110
- version: "0"
111
- required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
107
  none: false
113
- requirements:
114
- - - ">="
115
- - !ruby/object:Gem::Version
116
- hash: 3
117
- segments:
118
- - 0
119
- version: "0"
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
120
112
  requirements: []
121
-
122
113
  rubyforge_project:
123
- rubygems_version: 1.8.10
114
+ rubygems_version: 1.8.24
124
115
  signing_key:
125
116
  specification_version: 3
126
117
  summary: A tool to detect missing unique indexes
127
- test_files: []
128
-
118
+ test_files:
119
+ - spec/index_spec.rb
120
+ - spec/introspectors/has_one_spec.rb
121
+ - spec/introspectors/table_data_spec.rb
122
+ - spec/introspectors/validates_uniqueness_of_spec.rb
123
+ - spec/models_spec.rb
124
+ - spec/reporter_spec.rb
125
+ - spec/spec_helper.rb
File without changes