schema_expectations 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7bcaa02aa13161953b36126a982156c8eb192aac
4
+ data.tar.gz: 3378e981d2f3325310f4ce37097f8cb217956562
5
+ SHA512:
6
+ metadata.gz: a0d519d4a0c7a1439af4e0ac32802b98f520f65abb8c61be27344e9f4502e89e0a6716613f35f2a4f9c6e37697640a3bb2f9f41cfb91ccbefb26f978d45f6d48
7
+ data.tar.gz: e397d41a9aca63152a1b9052797f5cd116e8475f22f7a449beca78f5b5322fcd5f2cd0793744b2afd5ce9495f27aa65958f2b3302957ed18a08c61d5bf177f67
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.0
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.5
5
+ - 2.2.0
6
+ - ruby-head
7
+ script: 'bundle exec rake'
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
3
+ watch(%r{^spec/.+_spec\.rb$})
4
+ watch(%r{^spec/support}) { 'spec' }
5
+ watch('spec/spec_helper.rb') { 'spec' }
6
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Emma Borhanian
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ [![Build Status](https://travis-ci.org/emma-borhanian/schema_expectations.svg?branch=master)](https://travis-ci.org/emma-borhanian/schema_expectations)
2
+
3
+ # Work in progress
4
+
5
+ # Schema Expectations
6
+
7
+ Allows you to test whether your database schema matches the validations in your ActiveRecord models.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'bundler/setup'
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task :default => :spec
10
+ rescue LoadError
11
+ # no rspec available
12
+ end
@@ -0,0 +1,7 @@
1
+ require 'schema_expectations/version'
2
+
3
+ begin
4
+ require 'rspec/core'
5
+ rescue LoadError
6
+ end
7
+ require 'schema_expectations/rspec' if defined?(RSpec)
@@ -0,0 +1,6 @@
1
+ require 'rspec/core'
2
+ require 'schema_expectations/rspec_matchers'
3
+
4
+ RSpec.configure do |config|
5
+ config.include SchemaExpectations::RSpecMatchers, type: :model
6
+ end
@@ -0,0 +1 @@
1
+ require 'schema_expectations/rspec_matchers/validate_schema_nullable'
@@ -0,0 +1,109 @@
1
+ require 'rspec/expectations'
2
+
3
+ module SchemaExpectations
4
+ module RSpecMatchers
5
+ def validate_schema_nullable
6
+ ValidateSchemaNullableMatcher.new
7
+ end
8
+
9
+ class ValidateSchemaNullableMatcher
10
+ def matches?(model)
11
+ @model = model
12
+ @not_null_columns = filter_attributes(not_null_columns(model))
13
+ @present_attributes = filter_attributes(present_attributes(model))
14
+ @not_null_columns == @present_attributes
15
+ end
16
+
17
+ def failure_message
18
+ @not_null_columns.sort!
19
+ @present_attributes.sort!
20
+
21
+ errors = []
22
+
23
+ (@present_attributes - @not_null_columns).each do |attribute|
24
+ errors << "#{attribute} has unconditional presence validation but is missing NOT NULL"
25
+ end
26
+
27
+ (@not_null_columns - @present_attributes).each do |attribute|
28
+ if condition = validator_condition(@model, attribute)
29
+ errors << "#{attribute} is NOT NULL but its presence validator was conditional: #{condition.inspect}"
30
+ else
31
+ errors << "#{attribute} is NOT NULL but has no presence validation"
32
+ end
33
+ end
34
+
35
+ errors.join(', ')
36
+ end
37
+
38
+ def only(*args)
39
+ fail 'cannot use only and except' if @except
40
+ @only = Array(args)
41
+ fail 'empty only list' if @only.empty?
42
+ self
43
+ end
44
+
45
+ def except(*args)
46
+ fail 'cannot use only and except' if @only
47
+ @except = Array(args)
48
+ fail 'empty except list' if @except.empty?
49
+ self
50
+ end
51
+
52
+ private
53
+
54
+ def presence_validators(model)
55
+ presence_validators = model.validators.select do |validator|
56
+ validator.kind == :presence
57
+ end
58
+ end
59
+
60
+ def unconditional_presence_validators(model)
61
+ presence_validators(model).select do |validator|
62
+ keep = %i(on if unless).all? do |option_key|
63
+ Array(validator.options[option_key]).empty?
64
+ end
65
+
66
+ keep && !validator.options[:allow_nil] && !validator.options[:allow_blank]
67
+ end
68
+ end
69
+
70
+ def present_attributes(model)
71
+ present_attributes = unconditional_presence_validators(model).
72
+ flat_map(&:attributes).uniq
73
+ present_attributes &= model.columns.map(&:name).map(&:to_sym)
74
+ present_attributes
75
+ end
76
+
77
+ def not_null_columns(model)
78
+ model.columns.select { |column| !column.null }.map(&:name).map(&:to_sym)
79
+ end
80
+
81
+ def filter_attributes(attributes)
82
+ attributes &= @only if @only
83
+ attributes -= @except if @except
84
+ attributes -= [:id]
85
+ attributes
86
+ end
87
+
88
+ def validator_condition(model, attribute)
89
+ validators = presence_validators(model).select do |validator|
90
+ validator.attributes.include? attribute
91
+ end
92
+
93
+ validators.each do |validator|
94
+ condition = [:on, :if, :unless].detect do |option_key|
95
+ !Array(validator.options[option_key]).empty?
96
+ end
97
+
98
+ condition ||= [:allow_nil, :allow_blank].detect do |option_key|
99
+ validator.options[option_key]
100
+ end
101
+
102
+ return { condition => validator.options[condition] } if condition
103
+ end
104
+
105
+ nil
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,3 @@
1
+ module SchemaExpectations
2
+ VERSION = '0.0.0'
3
+ end
@@ -0,0 +1,35 @@
1
+ require File.expand_path('../lib/schema_expectations/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.platform = Gem::Platform::RUBY
5
+ gem.name = 'schema_expectations'
6
+ gem.version = SchemaExpectations::VERSION
7
+ gem.summary = 'Database Schema Expectations'
8
+ gem.description = %q(
9
+ Allows you to test whether your database schema
10
+ matches the validations in your ActiveRecord models.'
11
+ )
12
+ gem.license = 'MIT'
13
+
14
+ gem.authors = ['Emma Borhanian']
15
+ gem.email = 'emma.borhanian+schema_expectations@gmail.com'
16
+ gem.homepage = 'https://github.com/emma-borhanian/schema_expectations'
17
+
18
+ gem.files = `git ls-files`.split($\)
19
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
+ gem.test_files = gem.files.grep(%r{^spec/})
21
+ gem.require_paths = %w(lib)
22
+
23
+ gem.required_ruby_version = '>= 2.0.0'
24
+
25
+ gem.add_dependency 'activerecord', '~> 4.2'
26
+
27
+ gem.add_development_dependency 'pry'
28
+ gem.add_development_dependency 'rake', '~> 10.4'
29
+
30
+ # tests
31
+ gem.add_development_dependency 'codeclimate-test-reporter'
32
+ gem.add_development_dependency 'rspec', '~> 3.2'
33
+ gem.add_development_dependency 'guard-rspec', '~> 4.5'
34
+ gem.add_development_dependency 'sqlite3', '~> 1.3'
35
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe :validate_schema_nullable do
4
+ specify 'asserts that presence validations match NOT NULL', :active_record do
5
+ create_table :records do |t|
6
+ t.string :not_null, null: false
7
+ t.string :not_null_present, null: false
8
+
9
+ t.string :nullable
10
+ t.string :nullable_present
11
+ end
12
+
13
+ class Record < ActiveRecord::Base
14
+ validates :not_null_present, presence: true
15
+ validates :nullable_present, presence: true
16
+ end
17
+
18
+ expect(Record).to validate_schema_nullable.only(:not_null_present, :nullable)
19
+ expect(Record).to validate_schema_nullable.except(:not_null, :nullable_present)
20
+
21
+ expect(Record).to_not validate_schema_nullable
22
+ expect(Record).to_not validate_schema_nullable.only(:not_null)
23
+ expect(Record).to_not validate_schema_nullable.only(:nullable_present)
24
+
25
+ expect do
26
+ expect(Record).to validate_schema_nullable.only(:not_null)
27
+ end.to raise_error 'not_null is NOT NULL but has no presence validation'
28
+
29
+ expect do
30
+ expect(Record).to validate_schema_nullable.only(:nullable_present)
31
+ end.to raise_error 'nullable_present has unconditional presence validation but is missing NOT NULL'
32
+
33
+ class Record < ActiveRecord::Base
34
+ clear_validators!
35
+ validates :not_null_present, presence: true, on: :create
36
+ end
37
+ expect(Record).to_not validate_schema_nullable.only(:not_null_present)
38
+ expect do
39
+ expect(Record).to validate_schema_nullable.only(:not_null_present)
40
+ end.to raise_error 'not_null_present is NOT NULL but its presence validator was conditional: {:on=>:create}'
41
+
42
+ class Record < ActiveRecord::Base
43
+ clear_validators!
44
+ validates :not_null_present, presence: true, if: ->{ false }
45
+ end
46
+ expect(Record).to_not validate_schema_nullable.only(:not_null_present)
47
+ expect do
48
+ expect(Record).to validate_schema_nullable.only(:not_null_present)
49
+ end.to raise_error /\Anot_null_present is NOT NULL but its presence validator was conditional: {:if=>\#<Proc:.*>}\z/
50
+
51
+ class Record < ActiveRecord::Base
52
+ clear_validators!
53
+ validates :not_null_present, presence: true, unless: ->{ true }
54
+ end
55
+ expect(Record).to_not validate_schema_nullable.only(:not_null_present)
56
+ expect do
57
+ expect(Record).to validate_schema_nullable.only(:not_null_present)
58
+ end.to raise_error /\Anot_null_present is NOT NULL but its presence validator was conditional: {:unless=>\#<Proc:.*>}\z/
59
+
60
+ class Record < ActiveRecord::Base
61
+ clear_validators!
62
+ validates :not_null_present, presence: true, allow_nil: true
63
+ end
64
+ expect(Record).to_not validate_schema_nullable.only(:not_null_present)
65
+ expect do
66
+ expect(Record).to validate_schema_nullable.only(:not_null_present)
67
+ end.to raise_error 'not_null_present is NOT NULL but its presence validator was conditional: {:allow_nil=>true}'
68
+
69
+ class Record < ActiveRecord::Base
70
+ clear_validators!
71
+ validates :not_null_present, presence: true, allow_blank: true
72
+ end
73
+ expect(Record).to_not validate_schema_nullable.only(:not_null_present)
74
+ expect do
75
+ expect(Record).to validate_schema_nullable.only(:not_null_present)
76
+ end.to raise_error 'not_null_present is NOT NULL but its presence validator was conditional: {:allow_blank=>true}'
77
+ end
78
+ end
@@ -0,0 +1,17 @@
1
+ require 'codeclimate-test-reporter'
2
+ CodeClimate::TestReporter.start
3
+
4
+ require 'rspec'
5
+ require 'pry'
6
+
7
+ require 'schema_expectations'
8
+
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each do |file|
10
+ require file
11
+ end
12
+
13
+ RSpec.configure do |config|
14
+ config.raise_errors_for_deprecations!
15
+
16
+ config.include SchemaExpectations::RSpecMatchers
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'active_record'
2
+
3
+ module ActiveRecordHelpers
4
+ extend Forwardable
5
+
6
+ CONNECTION_DELEGATES = %i(create_table)
7
+
8
+ def connection
9
+ ActiveRecord::Base.connection
10
+ end
11
+
12
+ delegate CONNECTION_DELEGATES => :connection
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+ config.before(:each, active_record: true) do |example|
17
+ ActiveRecord::Base.establish_connection(
18
+ adapter: 'sqlite3', database: ':memory:')
19
+ end
20
+
21
+ config.after(:each, active_record: true) do
22
+ ActiveRecord::Base.remove_connection
23
+ end
24
+
25
+ config.include ActiveRecordHelpers, active_record: true
26
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schema_expectations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Emma Borhanian
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.4'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: codeclimate-test-reporter
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.3'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.3'
111
+ description: "\n Allows you to test whether your database schema\n matches the
112
+ validations in your ActiveRecord models.'\n "
113
+ email: emma.borhanian+schema_expectations@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".ruby-version"
121
+ - ".travis.yml"
122
+ - Gemfile
123
+ - Guardfile
124
+ - LICENSE
125
+ - README.md
126
+ - Rakefile
127
+ - lib/schema_expectations.rb
128
+ - lib/schema_expectations/rspec.rb
129
+ - lib/schema_expectations/rspec_matchers.rb
130
+ - lib/schema_expectations/rspec_matchers/validate_schema_nullable.rb
131
+ - lib/schema_expectations/version.rb
132
+ - schema_expectations.gemspec
133
+ - spec/lib/schema_expectations/rspec_matchers/validate_schema_nullable_spec.rb
134
+ - spec/spec_helper.rb
135
+ - spec/support/active_record.rb
136
+ homepage: https://github.com/emma-borhanian/schema_expectations
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 2.0.0
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.4.5
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Database Schema Expectations
160
+ test_files:
161
+ - spec/lib/schema_expectations/rspec_matchers/validate_schema_nullable_spec.rb
162
+ - spec/spec_helper.rb
163
+ - spec/support/active_record.rb