table_warnings 0.0.7 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG +14 -0
- data/Gemfile +1 -2
- data/README.rdoc +3 -3
- data/Rakefile +5 -10
- data/lib/table_warnings/arbitrary.rb +17 -0
- data/lib/table_warnings/blank.rb +13 -0
- data/lib/table_warnings/column.rb +70 -0
- data/lib/table_warnings/debug.rb +2 -0
- data/lib/table_warnings/exclusive.rb +31 -0
- data/lib/table_warnings/nonexistent_owner.rb +31 -0
- data/lib/table_warnings/null.rb +13 -0
- data/lib/table_warnings/range.rb +32 -0
- data/lib/table_warnings/registry.rb +38 -0
- data/lib/table_warnings/scout.rb +85 -0
- data/lib/table_warnings/size.rb +55 -0
- data/lib/table_warnings/version.rb +1 -1
- data/lib/table_warnings.rb +134 -24
- data/table_warnings.gemspec +6 -5
- data/test/helper.rb +94 -16
- data/test/test_combinations.rb +85 -0
- data/test/test_warn_if_nonexistent_owner.rb +60 -0
- data/test/test_warn_if_nonexistent_owner_except.rb +67 -0
- data/test/test_warn_if_nulls_except.rb +120 -0
- data/test/test_warn_if_nulls_in.rb +114 -0
- data/test/test_warn_unless_range.rb +18 -0
- metadata +105 -25
- data/lib/table_warnings/config.rb +0 -11
- data/lib/table_warnings/warning/arbitrary.rb +0 -13
- data/lib/table_warnings/warning/blank.rb +0 -25
- data/lib/table_warnings/warning/size.rb +0 -34
- data/lib/table_warnings/warning.rb +0 -30
- data/test/test_table_warnings.rb +0 -64
data/lib/table_warnings.rb
CHANGED
@@ -1,53 +1,163 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
1
3
|
require 'active_support'
|
2
4
|
require 'active_support/version'
|
3
|
-
require 'active_support/core_ext' if
|
5
|
+
require 'active_support/core_ext' if ActiveSupport::VERSION::MAJOR >= 3
|
4
6
|
require 'active_record'
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
require 'table_warnings/registry'
|
9
|
+
require 'table_warnings/exclusive'
|
10
|
+
require 'table_warnings/blank'
|
11
|
+
require 'table_warnings/size'
|
12
|
+
require 'table_warnings/arbitrary'
|
13
|
+
require 'table_warnings/null'
|
14
|
+
require 'table_warnings/column'
|
15
|
+
require 'table_warnings/scout'
|
16
|
+
require 'table_warnings/nonexistent_owner'
|
17
|
+
require 'table_warnings/range'
|
9
18
|
|
10
|
-
|
11
|
-
|
19
|
+
module TableWarnings
|
20
|
+
def TableWarnings.registry
|
21
|
+
@registry || Thread.exclusive do
|
22
|
+
@registry ||= Registry.new
|
23
|
+
end
|
12
24
|
end
|
13
25
|
|
14
|
-
#
|
26
|
+
# used to resolve columns to warnings
|
27
|
+
Disposition = Struct.new(:exclusives, :covers, :matches)
|
28
|
+
|
29
|
+
# Get current warning messages on the table.
|
15
30
|
def table_warnings
|
16
|
-
|
31
|
+
messages = []
|
32
|
+
|
33
|
+
TableWarnings.registry.nonexclusive(self).each do |warning|
|
34
|
+
messages << warning.messages
|
35
|
+
end
|
36
|
+
|
37
|
+
pool = column_names.map do |column_name|
|
38
|
+
TableWarnings::Column.new self, column_name
|
39
|
+
end
|
40
|
+
exclusive = TableWarnings.registry.exclusive(self)
|
41
|
+
|
42
|
+
assignments = {}
|
43
|
+
# pass 1 - exclusives and covers
|
44
|
+
exclusive.each do |warning|
|
45
|
+
disposition = Disposition.new
|
46
|
+
disposition.exclusives = warning.exclusives pool
|
47
|
+
disposition.covers = warning.covers pool
|
48
|
+
assignments[warning] = disposition
|
49
|
+
pool -= disposition.exclusives
|
50
|
+
end
|
51
|
+
if ENV['TABLE_WARNINGS_DEBUG'] == 'true'
|
52
|
+
$stderr.puts "pass 1"
|
53
|
+
assignments.each do |warning, disposition|
|
54
|
+
$stderr.puts " #{warning.scout.matcher} - exclusives=#{disposition.exclusives.map(&:name)} covers=#{disposition.covers.map(&:name)}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
# pass 2 - allow regexp matching, but only if somebody else didn't cover it
|
58
|
+
exclusive.each do |warning|
|
59
|
+
disposition = assignments[warning]
|
60
|
+
disposition.matches = warning.matches(pool).select do |match|
|
61
|
+
assignments.except(warning).none? { |_, other| other.covers.include?(match) }
|
62
|
+
end
|
63
|
+
pool -= disposition.matches
|
64
|
+
end
|
65
|
+
if ENV['TABLE_WARNINGS_DEBUG'] == 'true'
|
66
|
+
$stderr.puts "pass 2"
|
67
|
+
assignments.each do |warning, disposition|
|
68
|
+
$stderr.puts " #{warning.scout.matcher} - exclusives=#{disposition.exclusives.map(&:name)} covers=#{disposition.covers.map(&:name)} matches=#{disposition.matches.map(&:name)}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
if ENV['TABLE_WARNINGS_STRICT'] == 'true'
|
72
|
+
$stderr.puts "uncovered columns"
|
73
|
+
$stderr.puts pool.join("\n")
|
74
|
+
end
|
75
|
+
|
76
|
+
# now you can generate messages
|
77
|
+
assignments.each do |warning, disposition|
|
78
|
+
messages << warning.messages(disposition.exclusives+disposition.matches)
|
79
|
+
end
|
80
|
+
|
81
|
+
messages.flatten.compact
|
82
|
+
end
|
83
|
+
|
84
|
+
def warn_unless_range(*args)
|
85
|
+
options = args.extract_options!
|
86
|
+
args.flatten.each do |matcher|
|
87
|
+
TableWarnings.registry.add_warning self, TableWarnings::Range.new(self, matcher, options)
|
88
|
+
end
|
17
89
|
end
|
18
90
|
|
19
91
|
# Warn if there are blanks in a certain column.
|
20
92
|
#
|
21
93
|
# Blank includes both NULL and "" (empty string)
|
22
|
-
def
|
23
|
-
|
24
|
-
|
94
|
+
def warn_if_blanks(*args)
|
95
|
+
options = args.extract_options!
|
96
|
+
args.flatten.each do |matcher|
|
97
|
+
TableWarnings.registry.add_warning self, TableWarnings::Blank.new(self, matcher, options)
|
98
|
+
end
|
25
99
|
end
|
26
100
|
|
101
|
+
# Warn if there are NULLs in a certain column.
|
102
|
+
def warn_if_nulls(*args)
|
103
|
+
options = args.extract_options!
|
104
|
+
args.flatten.each do |matcher|
|
105
|
+
TableWarnings.registry.add_warning self, TableWarnings::Null.new(self, matcher, options)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
27
109
|
# Warn if there are blanks in ANY column.
|
28
|
-
#
|
29
|
-
# Blank includes both NULL and "" (empty string)
|
30
110
|
def warn_if_any_blanks
|
31
|
-
|
32
|
-
|
111
|
+
TableWarnings.registry.add_warning self, TableWarnings::Blank.new(self, /.*/)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Warn if there are nulls in ANY column.
|
115
|
+
def warn_if_any_nulls
|
116
|
+
TableWarnings.registry.add_warning self, TableWarnings::Null.new(self, /.*/)
|
33
117
|
end
|
34
118
|
|
35
|
-
# Warn if the number of records falls out of an (approximate)
|
119
|
+
# Warn if the number of records falls out of an (approximate) size.
|
36
120
|
#
|
37
121
|
# Approximations: :few, :tens, :dozens, :hundreds, :thousands, :hundreds_of_thousands, :millions
|
38
122
|
# Exact: pass a Range or a Numeric
|
39
|
-
def
|
40
|
-
|
41
|
-
::TableWarnings.config.warnings[self].add warning
|
123
|
+
def warn_unless_size(approximate_size, options = {})
|
124
|
+
TableWarnings.registry.add_warning self, TableWarnings::Size.new(self, approximate_size, options)
|
42
125
|
end
|
43
126
|
|
44
127
|
# An arbitrary warning.
|
45
|
-
def
|
46
|
-
|
47
|
-
|
128
|
+
def warn_if(&blk)
|
129
|
+
TableWarnings.registry.add_warning self, TableWarnings::Arbitrary.new(self, blk)
|
130
|
+
end
|
131
|
+
|
132
|
+
def warn_if_nulls_except(*args)
|
133
|
+
options = args.extract_options!
|
134
|
+
args.flatten.each do |matcher|
|
135
|
+
TableWarnings.registry.add_warning self, TableWarnings::Null.new(self, matcher, options.merge(:negative => true))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def warn_if_blanks_except(*args)
|
140
|
+
options = args.extract_options!
|
141
|
+
args.flatten.each do |matcher|
|
142
|
+
TableWarnings.registry.add_warning self, TableWarnings::Blank.new(self, matcher, options.merge(:negative => true))
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def warn_if_nonexistent_owner(*args)
|
147
|
+
options = args.extract_options!
|
148
|
+
args.flatten.each do |belongs_to_association_name|
|
149
|
+
TableWarnings.registry.add_warning self, TableWarnings::NonexistentOwner.new(self, belongs_to_association_name, options)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def warn_if_nonexistent_owner_except(*args)
|
154
|
+
options = args.extract_options!
|
155
|
+
args.flatten.each do |belongs_to_association_name|
|
156
|
+
TableWarnings.registry.add_warning self, TableWarnings::NonexistentOwner.new(self, belongs_to_association_name, options.merge(:negative => true))
|
157
|
+
end
|
48
158
|
end
|
49
159
|
end
|
50
160
|
|
51
|
-
unless
|
52
|
-
|
161
|
+
unless ActiveRecord::Base.method_defined? :table_warnings
|
162
|
+
ActiveRecord::Base.extend TableWarnings
|
53
163
|
end
|
data/table_warnings.gemspec
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require 'table_warnings/version'
|
2
|
+
require File.expand_path('../lib/table_warnings/version', __FILE__)
|
4
3
|
|
5
4
|
Gem::Specification.new do |s|
|
6
5
|
s.name = 'table_warnings'
|
7
6
|
s.version = TableWarnings::VERSION
|
8
|
-
s.platform = Gem::Platform::RUBY
|
9
7
|
s.authors = ['Seamus Abshere']
|
10
8
|
s.email = ['seamus@abshere.net']
|
11
9
|
s.homepage = 'https://github.com/seamusabshere/table_warnings'
|
@@ -23,7 +21,10 @@ Gem::Specification.new do |s|
|
|
23
21
|
s.add_runtime_dependency 'activesupport'
|
24
22
|
|
25
23
|
s.add_development_dependency 'fastercsv'
|
26
|
-
s.add_development_dependency '
|
27
|
-
s.add_development_dependency 'mini_record-compat'
|
24
|
+
s.add_development_dependency 'active_record_inline_schema'
|
28
25
|
s.add_development_dependency 'sqlite3'
|
26
|
+
s.add_development_dependency 'minitest'
|
27
|
+
s.add_development_dependency 'minitest-reporters'
|
28
|
+
s.add_development_dependency 'yard'
|
29
|
+
# s.add_development_dependency 'debugger'
|
29
30
|
end
|
data/test/helper.rb
CHANGED
@@ -1,25 +1,103 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
|
4
|
-
|
5
|
-
require '
|
6
|
-
|
7
|
-
require '
|
8
|
-
# thanks authlogic!
|
9
|
-
ActiveRecord::Schema.verbose = false
|
10
|
-
begin
|
11
|
-
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
12
|
-
rescue ArgumentError
|
13
|
-
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
if ::Bundler.definition.specs['debugger'].first
|
5
|
+
require 'debugger'
|
6
|
+
elsif ::Bundler.definition.specs['ruby-debug'].first
|
7
|
+
require 'ruby-debug'
|
14
8
|
end
|
15
9
|
|
10
|
+
require 'minitest/spec'
|
11
|
+
require 'minitest/autorun'
|
12
|
+
require 'minitest/reporters'
|
13
|
+
MiniTest::Unit.runner = MiniTest::SuiteRunner.new
|
14
|
+
MiniTest::Unit.runner.reporters << MiniTest::Reporters::SpecReporter.new
|
15
|
+
|
16
|
+
require 'active_record'
|
17
|
+
require 'active_record_inline_schema'
|
18
|
+
|
19
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
20
|
+
|
16
21
|
# require 'logger'
|
17
22
|
# logger = Logger.new $stdout
|
18
23
|
# logger.level = Logger::DEBUG
|
19
24
|
# ActiveRecord::Base.logger = logger
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
26
|
+
class ActiveRecord::Base
|
27
|
+
class << self
|
28
|
+
# ignores protected attrs
|
29
|
+
def force_create!(attrs = {})
|
30
|
+
record = new
|
31
|
+
attrs.each do |k, v|
|
32
|
+
record.send "#{k}=", v
|
33
|
+
end
|
34
|
+
record.save!
|
35
|
+
record
|
36
|
+
end
|
37
|
+
end
|
25
38
|
end
|
39
|
+
|
40
|
+
class MiniTest::Spec
|
41
|
+
# start transaction
|
42
|
+
before do
|
43
|
+
# activerecord-3.2.3/lib/active_record/fixtures.rb
|
44
|
+
@fixture_connections = ActiveRecord::Base.connection_handler.connection_pools.values.map(&:connection)
|
45
|
+
@fixture_connections.each do |connection|
|
46
|
+
connection.increment_open_transactions
|
47
|
+
connection.transaction_joinable = false
|
48
|
+
connection.begin_db_transaction
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# rollback
|
53
|
+
after do
|
54
|
+
@fixture_connections.each do |connection|
|
55
|
+
if connection.open_transactions != 0
|
56
|
+
connection.rollback_db_transaction
|
57
|
+
connection.decrement_open_transactions
|
58
|
+
end
|
59
|
+
end
|
60
|
+
@fixture_connections.clear
|
61
|
+
ActiveRecord::Base.clear_active_connections!
|
62
|
+
end
|
63
|
+
|
64
|
+
def assert_warning(model, expected_warning)
|
65
|
+
hits = model.table_warnings.select { |warning| warning =~ expected_warning }
|
66
|
+
refute hits.none?, "#{model.name} unexpectedly lacked warning #{expected_warning.inspect}"
|
67
|
+
refute hits.many?, "#{model.name} had MULTIPLE warnings like #{expected_warning.inspect}: #{hits.inspect}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def assert_no_warning(model, specific_unexpected_warning = nil)
|
71
|
+
warnings = model.table_warnings
|
72
|
+
if specific_unexpected_warning
|
73
|
+
refute(warnings.any? { |warning| warning =~ specific_unexpected_warning }, "#{model.name} unexpectedly had warning #{specific_unexpected_warning.inspect}")
|
74
|
+
else
|
75
|
+
refute warnings.any?, "#{model.name} unexpectedly had some warnings (#{warnings.inspect})"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def assert_causes_warning(model, expected_warnings)
|
80
|
+
expected_warnings = [expected_warnings].flatten
|
81
|
+
expected_warnings.each do |expected_warning|
|
82
|
+
assert_no_warning model, expected_warning
|
83
|
+
end
|
84
|
+
warnings_before = model.table_warnings
|
85
|
+
yield
|
86
|
+
expected_warnings.each do |expected_warning|
|
87
|
+
assert_warning model, expected_warning
|
88
|
+
end
|
89
|
+
unexpected_warnings = (model.table_warnings - warnings_before).reject do |warning|
|
90
|
+
expected_warnings.any? { |expected_warning| warning =~ expected_warning }
|
91
|
+
end
|
92
|
+
refute unexpected_warnings.any?, "#{model.name} unexpectedly ALSO got warnings #{unexpected_warnings.inspect}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def assert_does_not_cause_warning(model)
|
96
|
+
assert_no_warning model
|
97
|
+
yield
|
98
|
+
assert_no_warning model
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
require 'table_warnings'
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class PetAlpha < ActiveRecord::Base
|
4
|
+
col :birthday, :type => :datetime
|
5
|
+
col :gender
|
6
|
+
col :sire
|
7
|
+
warn_if_nulls :birthday
|
8
|
+
warn_if_nulls_except :gender
|
9
|
+
end
|
10
|
+
PetAlpha.auto_upgrade!
|
11
|
+
|
12
|
+
class PetBeta < ActiveRecord::Base
|
13
|
+
col :birthday, :type => :datetime
|
14
|
+
col :gender
|
15
|
+
col :sire
|
16
|
+
warn_if_nulls :birthday
|
17
|
+
warn_if_nulls_except /ende/
|
18
|
+
end
|
19
|
+
PetBeta.auto_upgrade!
|
20
|
+
|
21
|
+
class PetGamma < ActiveRecord::Base
|
22
|
+
col :birthday, :type => :datetime
|
23
|
+
col :gender
|
24
|
+
col :sire
|
25
|
+
col :certified, :type => :boolean
|
26
|
+
warn_if_nulls /irthd/, :conditions => { :certified => true }
|
27
|
+
warn_if_nulls_except /ende/
|
28
|
+
end
|
29
|
+
PetGamma.auto_upgrade!
|
30
|
+
|
31
|
+
class PetDelta < ActiveRecord::Base
|
32
|
+
col :birthday, :type => :datetime
|
33
|
+
col :gender
|
34
|
+
col :sire
|
35
|
+
col :certified, :type => :boolean
|
36
|
+
warn_if_nulls /irthd/, :conditions => { :certified => true }
|
37
|
+
warn_if_nulls_except /ende/, :conditions => { :certified => true }
|
38
|
+
end
|
39
|
+
PetDelta.auto_upgrade!
|
40
|
+
|
41
|
+
describe TableWarnings do
|
42
|
+
describe "combinations of positive ('in') and negative ('except') rules" do
|
43
|
+
it "combines a positive column and a negative column" do
|
44
|
+
assert_causes_warning PetAlpha, [/null.*birthday/i, /null.*sire/i] do
|
45
|
+
PetAlpha.force_create!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
it "combines a positive column and a negative regexp" do
|
49
|
+
assert_causes_warning PetBeta, [/null.*birthday/i, /null.*sire/i] do
|
50
|
+
PetBeta.force_create!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
it "combines a positive regexp with conditions and a negative regexp" do
|
54
|
+
assert_causes_warning PetGamma, [/null.*sire/i, /null.*certified/i] do
|
55
|
+
PetGamma.force_create!
|
56
|
+
end
|
57
|
+
PetGamma.delete_all # !
|
58
|
+
assert_causes_warning PetGamma, [/null.*sire/i, /null.*birthday/i] do
|
59
|
+
PetGamma.force_create! :certified => true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
it "combines a positive regexp with conditions and a negative regexp with conditions" do
|
63
|
+
assert_does_not_cause_warning PetDelta do
|
64
|
+
PetDelta.force_create!
|
65
|
+
end
|
66
|
+
PetDelta.delete_all # !
|
67
|
+
assert_causes_warning PetDelta, [/null.*sire/i, /null.*birthday/i] do
|
68
|
+
PetDelta.force_create! :certified => true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
=begin
|
75
|
+
warn_if_nulls_except(
|
76
|
+
:alt_fuel_code,
|
77
|
+
:carline_mfr_code,
|
78
|
+
:vi_mfr_code,
|
79
|
+
:carline_code,
|
80
|
+
:carline_class_code,
|
81
|
+
:carline_class_name,
|
82
|
+
)
|
83
|
+
warn_if_nulls /alt_fuel_efficiency/, :conditions => 'alt_fuel_code IS NOT NULL'
|
84
|
+
warn_if_nulls :carline_class, :conditions => 'year < 1998'
|
85
|
+
=end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class Person < ActiveRecord::Base
|
4
|
+
col :llc_name
|
5
|
+
end
|
6
|
+
Person.auto_upgrade!
|
7
|
+
|
8
|
+
class PetRed < ActiveRecord::Base
|
9
|
+
col :handler_id, :type => :integer
|
10
|
+
belongs_to :handler, :class_name => 'Person'
|
11
|
+
warn_if_nonexistent_owner :handler
|
12
|
+
end
|
13
|
+
PetRed.auto_upgrade!
|
14
|
+
|
15
|
+
class PetBlue < ActiveRecord::Base
|
16
|
+
col :trainer_id
|
17
|
+
belongs_to :trainer, :class_name => 'Person', :primary_key => :llc_name
|
18
|
+
warn_if_nonexistent_owner :trainer
|
19
|
+
end
|
20
|
+
PetBlue.auto_upgrade!
|
21
|
+
|
22
|
+
class PetGreen < ActiveRecord::Base
|
23
|
+
col :trainer_id
|
24
|
+
belongs_to :trainer, :class_name => 'Person', :primary_key => :llc_name
|
25
|
+
warn_if_nonexistent_owner :trainer, :allow_null => true
|
26
|
+
end
|
27
|
+
PetGreen.auto_upgrade!
|
28
|
+
|
29
|
+
describe TableWarnings do
|
30
|
+
describe :warn_if_nonexistent_owner do
|
31
|
+
before do
|
32
|
+
Person.force_create! :llc_name => 'My Small Business, LLC'
|
33
|
+
end
|
34
|
+
it "takes a single belongs-to association name" do
|
35
|
+
assert_causes_warning PetRed, /nonexistent.*handler/i do
|
36
|
+
PetRed.force_create!
|
37
|
+
end
|
38
|
+
end
|
39
|
+
it "checks the value of foreign keys not just their presence" do
|
40
|
+
assert_causes_warning PetRed, /nonexistent.*handler/i do
|
41
|
+
PetRed.force_create! :handler_id => 999999
|
42
|
+
end
|
43
|
+
end
|
44
|
+
it "doesn't raise false warnings" do
|
45
|
+
assert_does_not_cause_warning PetRed do
|
46
|
+
PetRed.force_create! :handler_id => Person.first.id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
it "regards nulls as nonexistent even if the association primary key column contains nulls" do
|
50
|
+
assert_causes_warning PetBlue, /nonexistent.*trainer/i do
|
51
|
+
PetBlue.force_create!
|
52
|
+
end
|
53
|
+
end
|
54
|
+
it "allows nulls if explicitly requested" do
|
55
|
+
assert_does_not_cause_warning PetGreen do
|
56
|
+
PetGreen.force_create!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|