consistency_fail 0.2.2 → 0.3.0

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.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