sexy_pg_constraints 0.1.2 → 0.2.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/Manifest CHANGED
@@ -11,4 +11,8 @@ lib/sexy_pg_constraints.rb
11
11
  sexy_pg_constraints.gemspec
12
12
  test/postgresql_adapter.rb
13
13
  test/sexy_pg_constraints_test.rb
14
+ test/support/assert_prohibits_allows.rb
15
+ test/support/database.yml
16
+ test/support/database.yml.example
17
+ test/support/models.rb
14
18
  test/test_helper.rb
@@ -106,6 +106,8 @@ Below is the list of constraints available and tested so far.
106
106
  * even
107
107
  * odd
108
108
  * format
109
+ * lowercase
110
+ * xor
109
111
 
110
112
  == Extensibility
111
113
 
@@ -113,8 +115,12 @@ All constraints are located in the lib/constraints.rb. Extending this module wit
113
115
 
114
116
  == TODO
115
117
 
118
+ * Add support for Rails schema.rb
116
119
  * Create better API for adding constraints
117
120
 
121
+ == Contributors
122
+ * Empact[http://github.com/Empact] (Big thanks for lots of work. Better flexibility, more tests, organizing code, bug fixes.)
123
+
118
124
  == License
119
125
 
120
126
  Copyright (c) 2008 Maxim Chernyak
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('sexy_pg_constraints', '0.1.2') do |p|
5
+ Echoe.new('sexy_pg_constraints', '0.2.0') do |p|
6
6
  p.description = "Use migrations and simple syntax to manage constraints in PostgreSQL DB."
7
7
  p.summary = "If you're on PostgreSQL and see the importance of data-layer constraints - this gem/plugin is for you. It integrates constraints into PostgreSQL adapter so you can add/remove them in your migrations. You get two simple methods for adding/removing constraints, as well as a pack of pre-made constraints."
8
8
  p.url = "http://github.com/maxim/sexy_pg_constraints"
@@ -24,7 +24,7 @@ module SexyPgConstraints
24
24
  def add_constraints(table, column, constraints)
25
25
  constraints.each_pair do |type, options|
26
26
  sql = "alter table #{table} add constraint #{make_title(table, column, type)} " +
27
- SexyPgConstraints::Constraints.send(type, column, options) + ';'
27
+ SexyPgConstraints::Constraints.send(type, table, column, options) + ';'
28
28
 
29
29
  execute sql
30
30
  end
@@ -1,38 +1,38 @@
1
1
  module SexyPgConstraints
2
2
  module Constraints
3
3
  module_function
4
-
4
+
5
5
  ##
6
6
  # Only allow listed values.
7
7
  #
8
- # Example:
8
+ # Example:
9
9
  # constrain :books, :variation, :whitelist => %w(hardcover softcover)
10
10
  #
11
- def whitelist(column, options)
12
- "check (#{column} in (#{ options.collect{|v| "'#{v}'"}.join(',') }))"
11
+ def whitelist(table, column, options)
12
+ "check (#{table}.#{column} in (#{ options.collect{|v| "'#{v}'"}.join(',') }))"
13
13
  end
14
-
15
- ##
14
+
15
+ ##
16
16
  # Prohibit listed values.
17
17
  #
18
- # Example:
18
+ # Example:
19
19
  # constrain :books, :isbn, :blacklist => %w(invalid_isbn1 invalid_isbn2)
20
20
  #
21
- def blacklist(column, options)
22
- "check (#{column} not in (#{ options.collect{|v| "'#{v}'"}.join(',') }))"
21
+ def blacklist(table, column, options)
22
+ "check (#{table}.#{column} not in (#{ options.collect{|v| "'#{v}'"}.join(',') }))"
23
23
  end
24
-
25
- ##
24
+
25
+ ##
26
26
  # The value must have at least 1 non-space character.
27
27
  #
28
28
  # Example:
29
29
  # constrain :books, :title, :not_blank => true
30
30
  #
31
- def not_blank(column, options)
32
- "check ( length(trim(both from #{column})) > 0 )"
31
+ def not_blank(table, column, options)
32
+ "check ( length(trim(both from #{table}.#{column})) > 0 )"
33
33
  end
34
34
 
35
- ##
35
+ ##
36
36
  # The numeric value must be within given range.
37
37
  #
38
38
  # Example:
@@ -40,115 +40,140 @@ module SexyPgConstraints
40
40
  # constrain :books, :year, :within => 1980...2009
41
41
  # (the two lines above do the same thing)
42
42
  #
43
- def within(column, options)
44
- "check (#{column} >= #{options.begin} and #{column} #{options.exclude_end? ? ' < ' : ' <= '} #{options.end})"
43
+ def within(table, column, options)
44
+ column_ref = column.to_s.include?('.') ? column : "#{table}.#{column}"
45
+ "check (#{column_ref} >= #{options.begin} and #{column_ref} #{options.exclude_end? ? ' < ' : ' <= '} #{options.end})"
45
46
  end
46
47
 
47
- ##
48
+ ##
48
49
  # Check the length of strings/text to be within the range.
49
50
  #
50
51
  # Example:
51
52
  # constrain :books, :author, :length_within => 4..50
52
53
  #
53
- def length_within(column, options)
54
- within("length(#{column})", options)
54
+ def length_within(table, column, options)
55
+ within(table, "length(#{table}.#{column})", options)
55
56
  end
56
-
57
- ##
57
+
58
+ ##
58
59
  # Allow only valid email format.
59
60
  #
60
61
  # Example:
61
62
  # constrain :books, :author, :email => true
62
63
  #
63
- def email(column, options)
64
- "check (((#{column})::text ~ E'^([-a-z0-9]+)@([-a-z0-9]+[.]+[a-z]{2,4})$'::text))"
64
+ def email(table, column, options)
65
+ "check (((#{table}.#{column})::text ~ E'^([-a-z0-9]+)@([-a-z0-9]+[.]+[a-z]{2,4})$'::text))"
65
66
  end
66
67
 
67
- ##
68
+ ##
68
69
  # Allow only alphanumeric values.
69
70
  #
70
71
  # Example:
71
72
  # constrain :books, :author, :alphanumeric => true
72
73
  #
73
- def alphanumeric(column, options)
74
- "check (((#{column})::text ~* '^[a-z0-9]+$'::text))"
74
+ def alphanumeric(table, column, options)
75
+ "check (((#{table}.#{column})::text ~* '^[a-z0-9]+$'::text))"
76
+ end
77
+
78
+ ##
79
+ # Allow only lower case values.
80
+ #
81
+ # Example:
82
+ # constrain :books, :author, :lowercase => true
83
+ #
84
+ def lowercase(table, column, options)
85
+ "check (#{table}.#{column} = lower(#{table}.#{column}))"
75
86
  end
76
-
77
- ##
87
+
88
+ ##
78
89
  # Allow only positive values.
79
90
  #
80
91
  # Example:
81
92
  # constrain :books, :quantity, :positive => true
82
93
  #
83
- def positive(column, options)
84
- "check (#{column} >= 0)"
94
+ def positive(table, column, options)
95
+ "check (#{table}.#{column} >= 0)"
85
96
  end
86
-
87
- ##
97
+
98
+ ##
88
99
  # Allow only odd values.
89
100
  #
90
101
  # Example:
91
102
  # constrain :books, :quantity, :odd => true
92
103
  #
93
- def odd(column, options)
94
- "check (mod(#{column}, 2) != 0)"
104
+ def odd(table, column, options)
105
+ "check (mod(#{table}.#{column}, 2) != 0)"
95
106
  end
96
107
 
97
- ##
108
+ ##
98
109
  # Allow only even values.
99
110
  #
100
111
  # Example:
101
112
  # constrain :books, :quantity, :even => true
102
113
  #
103
- def even(column, options)
104
- "check (mod(#{column}, 2) = 0)"
114
+ def even(table, column, options)
115
+ "check (mod(#{table}.#{column}, 2) = 0)"
105
116
  end
106
117
 
107
- ##
118
+ ##
108
119
  # Make sure every entry in the column is unique.
109
120
  #
110
121
  # Example:
111
122
  # constrain :books, :isbn, :unique => true
112
123
  #
113
- def unique(column, options)
114
- column = column.join(', ') if column.respond_to?(:join)
124
+ def unique(table, column, options)
125
+ column = Array(column).map {|c| %{"#{c}"} }.join(', ')
115
126
  "unique (#{column})"
116
127
  end
117
-
118
- ##
128
+
129
+ ##
130
+ # Allow only one of the values in the given columns to be true.
131
+ # Only reasonable with more than one column.
132
+ # See Enterprise Rails, Chapter 10 for details.
133
+ #
134
+ # Example:
135
+ # constrain :books, [], :xor => true
136
+ #
137
+ def xor(table, column, options)
138
+ addition = Array(column).map {|c| %{("#{c}" is not null)::integer} }.join(' + ')
139
+
140
+ "check (#{addition} = 1)"
141
+ end
142
+
143
+ ##
119
144
  # Allow only text/strings of the exact length specified, no more, no less.
120
145
  #
121
146
  # Example:
122
147
  # constrain :books, :hash, :exact_length => 32
123
148
  #
124
- def exact_length(column, options)
125
- "check ( length(trim(both from #{column})) = #{options} )"
149
+ def exact_length(table, column, options)
150
+ "check ( length(trim(both from #{table}.#{column})) = #{options} )"
126
151
  end
127
-
128
- ##
152
+
153
+ ##
129
154
  # Allow only values that match the regular expression.
130
155
  #
131
156
  # Example:
132
157
  # constrain :orders, :visa, :format => /^([4]{1})([0-9]{12,15})$/
133
158
  #
134
- def format(column, options)
135
- "check (((#{column})::text #{options.casefold? ? '~*' : '~'} E'#{options.source}'::text ))"
159
+ def format(table, column, options)
160
+ "check (((#{table}.#{column})::text #{options.casefold? ? '~*' : '~'} E'#{options.source}'::text ))"
136
161
  end
137
-
138
- ##
162
+
163
+ ##
139
164
  # Add foreign key constraint.
140
165
  #
141
166
  # Example:
142
167
  # constrain :books, :author_id, :reference => {:authors => :id, :on_delete => :cascade}
143
168
  #
144
- def reference(column, options)
169
+ def reference(table, column, options)
145
170
  on_delete = options.delete(:on_delete)
146
171
  fk_table = options.keys.first
147
172
  fk_column = options[fk_table]
148
-
173
+
149
174
  on_delete = "on delete #{on_delete}" if on_delete
150
-
151
- "foreign key (#{column}) references #{fk_table} (#{fk_column}) #{on_delete}"
175
+
176
+ %{foreign key ("#{column}") references #{fk_table} (#{fk_column}) #{on_delete}}
152
177
  end
153
178
  end
154
- end
179
+ end
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{sexy_pg_constraints}
5
- s.version = "0.1.2"
5
+ s.version = "0.2.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Maxim Chernyak"]
9
- s.date = %q{2009-10-04}
9
+ s.date = %q{2010-02-01}
10
10
  s.description = %q{Use migrations and simple syntax to manage constraints in PostgreSQL DB.}
11
11
  s.email = %q{max@bitsonnet.com}
12
12
  s.extra_rdoc_files = ["CHANGELOG.rdoc", "README.rdoc", "lib/constrainer.rb", "lib/constraints.rb", "lib/deconstrainer.rb", "lib/helpers.rb", "lib/sexy_pg_constraints.rb"]
13
- s.files = ["CHANGELOG.rdoc", "Manifest", "README.rdoc", "Rakefile", "init.rb", "lib/constrainer.rb", "lib/constraints.rb", "lib/deconstrainer.rb", "lib/helpers.rb", "lib/sexy_pg_constraints.rb", "sexy_pg_constraints.gemspec", "test/postgresql_adapter.rb", "test/sexy_pg_constraints_test.rb", "test/test_helper.rb"]
13
+ s.files = ["CHANGELOG.rdoc", "Manifest", "README.rdoc", "Rakefile", "init.rb", "lib/constrainer.rb", "lib/constraints.rb", "lib/deconstrainer.rb", "lib/helpers.rb", "lib/sexy_pg_constraints.rb", "sexy_pg_constraints.gemspec", "test/postgresql_adapter.rb", "test/sexy_pg_constraints_test.rb", "test/support/assert_prohibits_allows.rb", "test/support/database.yml", "test/support/database.yml.example", "test/support/models.rb", "test/test_helper.rb"]
14
14
  s.homepage = %q{http://github.com/maxim/sexy_pg_constraints}
15
15
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Sexy_pg_constraints", "--main", "README.rdoc"]
16
16
  s.require_paths = ["lib"]
@@ -1,572 +1,899 @@
1
1
  require File.dirname(__FILE__) + '/test_helper.rb'
2
2
 
3
- # Database spc_test should be created manually.
4
- ActiveRecord::Base.establish_connection(:adapter => "postgresql", :database => "spc_test")
5
-
6
- # Setting up sample migrations
7
- class CreateBooks < ActiveRecord::Migration
8
- def self.up
9
- create_table :books do |t|
10
- t.string :title
11
- t.string :author
12
- t.integer :author_id
13
- t.integer :quantity
14
- t.string :isbn
15
- end
16
- end
17
-
18
- def self.down
19
- drop_table :books
20
- end
21
- end
22
-
23
- class CreateAuthors < ActiveRecord::Migration
24
- def self.up
25
- create_table :authors do |t|
26
- t.string :name
27
- t.string :bio
28
- end
29
- end
30
-
31
- def self.down
32
- drop_table :authors
33
- end
34
- end
35
-
36
- class Book < ActiveRecord::Base; end
37
- class Author < ActiveRecord::Base; end
38
-
39
3
  class SexyPgConstraintsTest < Test::Unit::TestCase
40
4
  def setup
41
5
  CreateBooks.up
42
6
  CreateAuthors.up
43
7
  end
44
-
8
+
45
9
  def teardown
46
10
  CreateBooks.down
47
11
  CreateAuthors.down
48
12
  end
49
-
13
+
50
14
  def test_should_create_book
51
15
  Book.create
52
16
  assert_equal 1, Book.count
53
17
  end
54
-
18
+
55
19
  def test_whitelist
56
20
  ActiveRecord::Migration.constrain :books, :author, :whitelist => %w(whitelisted1 whitelisted2 whitelisted3)
57
-
58
- assert_prohibits :author, :whitelist do |book|
21
+
22
+ assert_prohibits Book, :author, :whitelist do |book|
59
23
  book.author = 'not_whitelisted'
60
24
  end
61
-
62
- assert_allows do |book|
25
+
26
+ assert_allows Book do |book|
63
27
  book.author = 'whitelisted2'
64
28
  end
65
-
29
+
66
30
  ActiveRecord::Migration.deconstrain :books, :author, :whitelist
67
-
68
- assert_allows do |book|
31
+
32
+ assert_allows Book do |book|
69
33
  book.author = 'not_whitelisted'
70
34
  end
71
35
  end
72
-
36
+
37
+ def test_whitelist_on_a_column_whose_name_is_a_sql_keyword
38
+ ActiveRecord::Migration.constrain :books, :as, :whitelist => %w(whitelisted1 whitelisted2 whitelisted3)
39
+
40
+ assert_prohibits Book, :as, :whitelist do |book|
41
+ book.as = 'not_whitelisted'
42
+ end
43
+
44
+ assert_allows Book do |book|
45
+ book.as = 'whitelisted2'
46
+ end
47
+
48
+ ActiveRecord::Migration.deconstrain :books, :as, :whitelist
49
+
50
+ assert_allows Book do |book|
51
+ book.as = 'not_whitelisted'
52
+ end
53
+ end
54
+
73
55
  def test_blacklist
74
56
  ActiveRecord::Migration.constrain :books, :author, :blacklist => %w(blacklisted1 blacklisted2 blacklisted3)
75
-
76
- assert_prohibits :author, :blacklist do |book|
57
+
58
+ assert_prohibits Book, :author, :blacklist do |book|
77
59
  book.author = 'blacklisted2'
78
60
  end
79
-
80
- assert_allows do |book|
61
+
62
+ assert_allows Book do |book|
81
63
  book.author = 'not_blacklisted'
82
64
  end
83
-
65
+
84
66
  ActiveRecord::Migration.deconstrain :books, :author, :blacklist
85
-
86
- assert_allows do |book|
67
+
68
+ assert_allows Book do |book|
87
69
  book.author = 'blacklisted2'
88
70
  end
89
71
  end
90
-
72
+
73
+ def test_blacklist_on_a_column_whose_name_is_a_sql_keyword
74
+ ActiveRecord::Migration.constrain :books, :as, :blacklist => %w(blacklisted1 blacklisted2 blacklisted3)
75
+
76
+ assert_prohibits Book, :as, :blacklist do |book|
77
+ book.as = 'blacklisted2'
78
+ end
79
+
80
+ assert_allows Book do |book|
81
+ book.as = 'not_blacklisted'
82
+ end
83
+
84
+ ActiveRecord::Migration.deconstrain :books, :as, :blacklist
85
+
86
+ assert_allows Book do |book|
87
+ book.as = 'blacklisted2'
88
+ end
89
+ end
90
+
91
91
  def test_not_blank
92
92
  ActiveRecord::Migration.constrain :books, :author, :not_blank => true
93
-
94
- assert_prohibits :author, :not_blank do |book|
93
+
94
+ assert_prohibits Book, :author, :not_blank do |book|
95
95
  book.author = ' '
96
96
  end
97
-
98
- assert_allows do |book|
97
+
98
+ assert_allows Book do |book|
99
99
  book.author = 'foo'
100
100
  end
101
-
101
+
102
102
  ActiveRecord::Migration.deconstrain :books, :author, :not_blank
103
-
104
- assert_allows do |book|
103
+
104
+ assert_allows Book do |book|
105
105
  book.author = ' '
106
106
  end
107
107
  end
108
-
108
+
109
+ def test_not_blank_on_a_column_whose_name_is_a_sql_keyword
110
+ ActiveRecord::Migration.constrain :books, :as, :not_blank => true
111
+
112
+ assert_prohibits Book, :as, :not_blank do |book|
113
+ book.as = ' '
114
+ end
115
+
116
+ assert_allows Book do |book|
117
+ book.as = 'foo'
118
+ end
119
+
120
+ ActiveRecord::Migration.deconstrain :books, :as, :not_blank
121
+
122
+ assert_allows Book do |book|
123
+ book.as = ' '
124
+ end
125
+ end
126
+
109
127
  def test_within_inclusive
110
128
  ActiveRecord::Migration.constrain :books, :quantity, :within => 5..11
111
-
112
- assert_prohibits :quantity, :within do |book|
129
+
130
+ assert_prohibits Book, :quantity, :within do |book|
113
131
  book.quantity = 12
114
132
  end
115
-
116
- assert_prohibits :quantity, :within do |book|
133
+
134
+ assert_prohibits Book, :quantity, :within do |book|
117
135
  book.quantity = 4
118
136
  end
119
-
120
- assert_allows do |book|
137
+
138
+ assert_allows Book do |book|
121
139
  book.quantity = 7
122
140
  end
123
-
141
+
124
142
  ActiveRecord::Migration.deconstrain :books, :quantity, :within
125
-
126
- assert_allows do |book|
143
+
144
+ assert_allows Book do |book|
127
145
  book.quantity = 12
128
146
  end
129
147
  end
130
-
148
+
149
+ def test_within_inclusive_on_a_column_whose_name_is_a_sql_keyword
150
+ ActiveRecord::Migration.constrain :books, :from, :within => 5..11
151
+
152
+ assert_prohibits Book, :from, :within do |book|
153
+ book.from = 12
154
+ end
155
+
156
+ assert_prohibits Book, :from, :within do |book|
157
+ book.from = 4
158
+ end
159
+
160
+ assert_allows Book do |book|
161
+ book.from = 7
162
+ end
163
+
164
+ ActiveRecord::Migration.deconstrain :books, :from, :within
165
+
166
+ assert_allows Book do |book|
167
+ book.from = 12
168
+ end
169
+ end
170
+
131
171
  def test_within_non_inclusive
132
172
  ActiveRecord::Migration.constrain :books, :quantity, :within => 5...11
133
-
134
- assert_prohibits :quantity, :within do |book|
173
+
174
+ assert_prohibits Book, :quantity, :within do |book|
135
175
  book.quantity = 11
136
176
  end
137
-
138
- assert_prohibits :quantity, :within do |book|
177
+
178
+ assert_prohibits Book, :quantity, :within do |book|
139
179
  book.quantity = 4
140
180
  end
141
-
142
- assert_allows do |book|
181
+
182
+ assert_allows Book do |book|
143
183
  book.quantity = 10
144
184
  end
145
-
185
+
146
186
  ActiveRecord::Migration.deconstrain :books, :quantity, :within
147
-
148
- assert_allows do |book|
187
+
188
+ assert_allows Book do |book|
149
189
  book.quantity = 11
150
190
  end
151
191
  end
152
-
192
+
193
+ def test_within_non_inclusive_on_a_column_whose_name_is_a_sql_keyword
194
+ ActiveRecord::Migration.constrain :books, :from, :within => 5...11
195
+
196
+ assert_prohibits Book, :from, :within do |book|
197
+ book.from = 11
198
+ end
199
+
200
+ assert_prohibits Book, :from, :within do |book|
201
+ book.from = 4
202
+ end
203
+
204
+ assert_allows Book do |book|
205
+ book.from = 10
206
+ end
207
+
208
+ ActiveRecord::Migration.deconstrain :books, :from, :within
209
+
210
+ assert_allows Book do |book|
211
+ book.from = 11
212
+ end
213
+ end
214
+
153
215
  def test_length_within_inclusive
154
216
  ActiveRecord::Migration.constrain :books, :title, :length_within => 5..11
155
-
156
- assert_prohibits :title, :length_within do |book|
217
+
218
+ assert_prohibits Book, :title, :length_within do |book|
157
219
  book.title = 'abcdefghijkl'
158
220
  end
159
-
160
- assert_prohibits :title, :length_within do |book|
221
+
222
+ assert_prohibits Book, :title, :length_within do |book|
161
223
  book.title = 'abcd'
162
224
  end
163
-
164
- assert_allows do |book|
225
+
226
+ assert_allows Book do |book|
165
227
  book.title = 'abcdefg'
166
228
  end
167
-
229
+
168
230
  ActiveRecord::Migration.deconstrain :books, :title, :length_within
169
-
170
- assert_allows do |book|
231
+
232
+ assert_allows Book do |book|
171
233
  book.title = 'abcdefghijkl'
172
234
  end
173
235
  end
174
-
236
+
237
+ def test_length_within_inclusive_on_a_column_whose_name_is_a_sql_keyword
238
+ ActiveRecord::Migration.constrain :books, :as, :length_within => 5..11
239
+
240
+ assert_prohibits Book, :as, :length_within do |book|
241
+ book.as = 'abcdefghijkl'
242
+ end
243
+
244
+ assert_prohibits Book, :as, :length_within do |book|
245
+ book.as = 'abcd'
246
+ end
247
+
248
+ assert_allows Book do |book|
249
+ book.as = 'abcdefg'
250
+ end
251
+
252
+ ActiveRecord::Migration.deconstrain :books, :as, :length_within
253
+
254
+ assert_allows Book do |book|
255
+ book.as = 'abcdefghijkl'
256
+ end
257
+ end
258
+
175
259
  def test_length_within_non_inclusive
176
260
  ActiveRecord::Migration.constrain :books, :title, :length_within => 5...11
177
-
178
- assert_prohibits :title, :length_within do |book|
261
+
262
+ assert_prohibits Book, :title, :length_within do |book|
179
263
  book.title = 'abcdefghijk'
180
264
  end
181
-
182
- assert_prohibits :title, :length_within do |book|
265
+
266
+ assert_prohibits Book, :title, :length_within do |book|
183
267
  book.title = 'abcd'
184
268
  end
185
-
186
- assert_allows do |book|
269
+
270
+ assert_allows Book do |book|
187
271
  book.title = 'abcdefg'
188
272
  end
189
-
273
+
190
274
  ActiveRecord::Migration.deconstrain :books, :title, :length_within
191
-
192
- assert_allows do |book|
275
+
276
+ assert_allows Book do |book|
193
277
  book.title = 'abcdefghijk'
194
278
  end
195
279
  end
196
-
280
+
281
+ def test_length_within_non_inclusive_on_a_column_whose_name_is_a_sql_keyword
282
+ ActiveRecord::Migration.constrain :books, :as, :length_within => 5...11
283
+
284
+ assert_prohibits Book, :as, :length_within do |book|
285
+ book.as = 'abcdefghijk'
286
+ end
287
+
288
+ assert_prohibits Book, :as, :length_within do |book|
289
+ book.as = 'abcd'
290
+ end
291
+
292
+ assert_allows Book do |book|
293
+ book.as = 'abcdefg'
294
+ end
295
+
296
+ ActiveRecord::Migration.deconstrain :books, :as, :length_within
297
+
298
+ assert_allows Book do |book|
299
+ book.as = 'abcdefghijk'
300
+ end
301
+ end
302
+
197
303
  def test_email
198
304
  ActiveRecord::Migration.constrain :books, :author, :email => true
199
-
200
- assert_prohibits :author, :email do |book|
305
+
306
+ assert_prohibits Book, :author, :email do |book|
201
307
  book.author = 'blah@example'
202
308
  end
203
-
204
- assert_allows do |book|
309
+
310
+ assert_allows Book do |book|
205
311
  book.author = 'blah@example.com'
206
312
  end
207
-
313
+
208
314
  ActiveRecord::Migration.deconstrain :books, :author, :email
209
-
210
- assert_allows do |book|
315
+
316
+ assert_allows Book do |book|
211
317
  book.author = 'blah@example'
212
318
  end
213
- end
214
-
319
+ end
320
+
321
+ def test_email_on_a_column_whose_name_is_a_sql_keyword
322
+ ActiveRecord::Migration.constrain :books, :as, :email => true
323
+
324
+ assert_prohibits Book, :as, :email do |book|
325
+ book.as = 'blah@example'
326
+ end
327
+
328
+ assert_allows Book do |book|
329
+ book.as = 'blah@example.com'
330
+ end
331
+
332
+ ActiveRecord::Migration.deconstrain :books, :as, :email
333
+
334
+ assert_allows Book do |book|
335
+ book.as = 'blah@example'
336
+ end
337
+ end
338
+
215
339
  def test_alphanumeric
216
340
  ActiveRecord::Migration.constrain :books, :title, :alphanumeric => true
217
-
218
- assert_prohibits :title, :alphanumeric do |book|
341
+
342
+ assert_prohibits Book, :title, :alphanumeric do |book|
219
343
  book.title = 'asdf@asdf'
220
344
  end
221
-
222
- assert_allows do |book|
345
+
346
+ assert_allows Book do |book|
223
347
  book.title = 'asdf'
224
348
  end
225
-
349
+
226
350
  ActiveRecord::Migration.deconstrain :books, :title, :alphanumeric
227
-
228
- assert_allows do |book|
351
+
352
+ assert_allows Book do |book|
229
353
  book.title = 'asdf@asdf'
230
354
  end
231
355
  end
232
-
356
+
357
+ def test_alphanumeric_on_a_column_whose_name_is_a_sql_keyword
358
+ ActiveRecord::Migration.constrain :books, :as, :alphanumeric => true
359
+
360
+ assert_prohibits Book, :as, :alphanumeric do |book|
361
+ book.as = 'asdf@asdf'
362
+ end
363
+
364
+ assert_allows Book do |book|
365
+ book.as = 'asdf'
366
+ end
367
+
368
+ ActiveRecord::Migration.deconstrain :books, :as, :alphanumeric
369
+
370
+ assert_allows Book do |book|
371
+ book.as = 'asdf@asdf'
372
+ end
373
+ end
374
+
233
375
  def test_positive
234
376
  ActiveRecord::Migration.constrain :books, :quantity, :positive => true
235
-
236
- assert_prohibits :quantity, :positive do |book|
377
+
378
+ assert_prohibits Book, :quantity, :positive do |book|
237
379
  book.quantity = -1
238
380
  end
239
-
240
- assert_allows do |book|
381
+
382
+ assert_allows Book do |book|
241
383
  book.quantity = 0
242
384
  end
243
-
244
- assert_allows do |book|
385
+
386
+ assert_allows Book do |book|
245
387
  book.quantity = 1
246
388
  end
247
-
389
+
248
390
  ActiveRecord::Migration.deconstrain :books, :quantity, :positive
249
-
250
- assert_allows do |book|
391
+
392
+ assert_allows Book do |book|
251
393
  book.quantity = -1
252
394
  end
253
395
  end
254
-
396
+
397
+ def test_positive_on_a_column_whose_name_is_a_sql_keyword
398
+ ActiveRecord::Migration.constrain :books, :from, :positive => true
399
+
400
+ assert_prohibits Book, :from, :positive do |book|
401
+ book.from = -1
402
+ end
403
+
404
+ assert_allows Book do |book|
405
+ book.from = 0
406
+ end
407
+
408
+ assert_allows Book do |book|
409
+ book.from = 1
410
+ end
411
+
412
+ ActiveRecord::Migration.deconstrain :books, :from, :positive
413
+
414
+ assert_allows Book do |book|
415
+ book.from = -1
416
+ end
417
+ end
418
+
255
419
  def test_odd
256
420
  ActiveRecord::Migration.constrain :books, :quantity, :odd => true
257
-
258
- assert_prohibits :quantity, :odd do |book|
421
+
422
+ assert_prohibits Book, :quantity, :odd do |book|
259
423
  book.quantity = 2
260
424
  end
261
-
262
- assert_allows do |book|
425
+
426
+ assert_allows Book do |book|
263
427
  book.quantity = 1
264
428
  end
265
-
429
+
266
430
  ActiveRecord::Migration.deconstrain :books, :quantity, :odd
267
-
268
- assert_allows do |book|
431
+
432
+ assert_allows Book do |book|
269
433
  book.quantity = 2
270
434
  end
271
435
  end
272
-
436
+
437
+ def test_odd_on_a_column_whose_name_is_a_sql_keyword
438
+ ActiveRecord::Migration.constrain :books, :from, :odd => true
439
+
440
+ assert_prohibits Book, :from, :odd do |book|
441
+ book.from = 2
442
+ end
443
+
444
+ assert_allows Book do |book|
445
+ book.from = 1
446
+ end
447
+
448
+ ActiveRecord::Migration.deconstrain :books, :from, :odd
449
+
450
+ assert_allows Book do |book|
451
+ book.from = 2
452
+ end
453
+ end
454
+
273
455
  def test_even
274
456
  ActiveRecord::Migration.constrain :books, :quantity, :even => true
275
-
276
- assert_prohibits :quantity, :even do |book|
457
+
458
+ assert_prohibits Book, :quantity, :even do |book|
277
459
  book.quantity = 1
278
460
  end
279
-
280
- assert_allows do |book|
461
+
462
+ assert_allows Book do |book|
281
463
  book.quantity = 2
282
464
  end
283
-
465
+
284
466
  ActiveRecord::Migration.deconstrain :books, :quantity, :even
285
-
286
- assert_allows do |book|
467
+
468
+ assert_allows Book do |book|
287
469
  book.quantity = 1
288
470
  end
289
471
  end
290
-
472
+
473
+ def test_even_on_a_column_whose_name_is_a_sql_keyword
474
+ ActiveRecord::Migration.constrain :books, :from, :even => true
475
+
476
+ assert_prohibits Book, :from, :even do |book|
477
+ book.from = 1
478
+ end
479
+
480
+ assert_allows Book do |book|
481
+ book.from = 2
482
+ end
483
+
484
+ ActiveRecord::Migration.deconstrain :books, :from, :even
485
+
486
+ assert_allows Book do |book|
487
+ book.from = 1
488
+ end
489
+ end
490
+
291
491
  def test_unique
292
492
  ActiveRecord::Migration.constrain :books, :isbn, :unique => true
293
-
294
- assert_allows do |book|
493
+
494
+ assert_allows Book do |book|
295
495
  book.isbn = 'foo'
296
496
  end
297
-
298
- assert_prohibits :isbn, :unique, 'unique' do |book|
497
+
498
+ assert_prohibits Book, :isbn, :unique, 'unique' do |book|
299
499
  book.isbn = 'foo'
300
500
  end
301
-
501
+
302
502
  ActiveRecord::Migration.deconstrain :books, :isbn, :unique
303
-
304
- assert_allows do |book|
503
+
504
+ assert_allows Book do |book|
305
505
  book.isbn = 'foo'
306
506
  end
307
507
  end
308
-
508
+
509
+ def test_unique_on_a_column_whose_name_is_a_sql_keyword
510
+ ActiveRecord::Migration.constrain :books, :as, :unique => true
511
+
512
+ assert_allows Book do |book|
513
+ book.as = 'foo'
514
+ end
515
+
516
+ assert_prohibits Book, :as, :unique, 'unique' do |book|
517
+ book.as = 'foo'
518
+ end
519
+
520
+ ActiveRecord::Migration.deconstrain :books, :as, :unique
521
+
522
+ assert_allows Book do |book|
523
+ book.as = 'foo'
524
+ end
525
+ end
526
+
309
527
  def test_exact_length
310
528
  ActiveRecord::Migration.constrain :books, :isbn, :exact_length => 5
311
-
312
- assert_prohibits :isbn, :exact_length do |book|
529
+
530
+ assert_prohibits Book, :isbn, :exact_length do |book|
313
531
  book.isbn = '123456'
314
532
  end
315
-
316
- assert_prohibits :isbn, :exact_length do |book|
533
+
534
+ assert_prohibits Book, :isbn, :exact_length do |book|
317
535
  book.isbn = '1234'
318
536
  end
319
-
320
- assert_allows do |book|
537
+
538
+ assert_allows Book do |book|
321
539
  book.isbn = '12345'
322
540
  end
323
-
541
+
324
542
  ActiveRecord::Migration.deconstrain :books, :isbn, :exact_length
325
-
326
- assert_allows do |book|
543
+
544
+ assert_allows Book do |book|
327
545
  book.isbn = '123456'
328
546
  end
329
547
  end
330
-
548
+
549
+ def test_exact_length_on_a_column_whose_name_is_a_sql_keyword
550
+ ActiveRecord::Migration.constrain :books, :as, :exact_length => 5
551
+
552
+ assert_prohibits Book, :as, :exact_length do |book|
553
+ book.as = '123456'
554
+ end
555
+
556
+ assert_prohibits Book, :as, :exact_length do |book|
557
+ book.as = '1234'
558
+ end
559
+
560
+ assert_allows Book do |book|
561
+ book.as = '12345'
562
+ end
563
+
564
+ ActiveRecord::Migration.deconstrain :books, :as, :exact_length
565
+
566
+ assert_allows Book do |book|
567
+ book.as = '123456'
568
+ end
569
+ end
570
+
331
571
  def test_format_case_insensitive
332
572
  ActiveRecord::Migration.constrain :books, :title, :format => /^[a-z]+$/i
333
-
334
- assert_prohibits :title, :format do |book|
573
+
574
+ assert_prohibits Book, :title, :format do |book|
335
575
  book.title = 'abc3'
336
576
  end
337
-
338
- assert_prohibits :title, :format do |book|
577
+
578
+ assert_prohibits Book, :title, :format do |book|
339
579
  book.title = ''
340
580
  end
341
-
342
- assert_allows do |book|
581
+
582
+ assert_allows Book do |book|
343
583
  book.title = 'abc'
344
584
  end
345
-
346
- assert_allows do |book|
585
+
586
+ assert_allows Book do |book|
347
587
  book.title = 'ABc'
348
588
  end
349
-
589
+
350
590
  ActiveRecord::Migration.deconstrain :books, :title, :format
351
-
352
- assert_allows do |book|
591
+
592
+ assert_allows Book do |book|
353
593
  book.title = 'abc3'
354
594
  end
355
595
  end
356
-
596
+
597
+ def test_format_case_insensitive_on_a_column_whose_name_is_a_sql_keyword
598
+ ActiveRecord::Migration.constrain :books, :as, :format => /^[a-z]+$/i
599
+
600
+ assert_prohibits Book, :as, :format do |book|
601
+ book.as = 'abc3'
602
+ end
603
+
604
+ assert_prohibits Book, :as, :format do |book|
605
+ book.as = ''
606
+ end
607
+
608
+ assert_allows Book do |book|
609
+ book.as = 'abc'
610
+ end
611
+
612
+ assert_allows Book do |book|
613
+ book.as = 'ABc'
614
+ end
615
+
616
+ ActiveRecord::Migration.deconstrain :books, :as, :format
617
+
618
+ assert_allows Book do |book|
619
+ book.as = 'abc3'
620
+ end
621
+ end
622
+
357
623
  def test_format_case_sensitive
358
624
  ActiveRecord::Migration.constrain :books, :title, :format => /^[a-z]+$/
359
-
360
- assert_prohibits :title, :format do |book|
625
+
626
+ assert_prohibits Book, :title, :format do |book|
361
627
  book.title = 'aBc'
362
628
  end
363
-
364
- assert_allows do |book|
629
+
630
+ assert_allows Book do |book|
365
631
  book.title = 'abc'
366
632
  end
367
-
633
+
368
634
  ActiveRecord::Migration.deconstrain :books, :title, :format
369
-
370
- assert_allows do |book|
635
+
636
+ assert_allows Book do |book|
371
637
  book.title = 'aBc'
372
638
  end
373
639
  end
374
-
640
+
641
+ def test_format_case_sensitive_on_a_column_whose_name_is_a_sql_keyword
642
+ ActiveRecord::Migration.constrain :books, :as, :format => /^[a-z]+$/
643
+
644
+ assert_prohibits Book, :as, :format do |book|
645
+ book.as = 'aBc'
646
+ end
647
+
648
+ assert_allows Book do |book|
649
+ book.as = 'abc'
650
+ end
651
+
652
+ ActiveRecord::Migration.deconstrain :books, :as, :format
653
+
654
+ assert_allows Book do |book|
655
+ book.as = 'aBc'
656
+ end
657
+ end
658
+
375
659
  def test_reference
376
660
  ActiveRecord::Migration.constrain :books, :author_id, :reference => {:authors => :id}
377
-
378
- assert_prohibits :author_id, :reference, 'foreign key' do |book|
661
+
662
+ assert_prohibits Book, :author_id, :reference, 'foreign key' do |book|
379
663
  book.author_id = 1
380
664
  end
381
-
665
+
382
666
  author = Author.new
383
667
  author.name = "Mark Twain"
384
668
  author.bio = "American writer"
385
669
  assert author.save
386
-
670
+
387
671
  assert_equal 1, author.id
388
-
389
- assert_allows do |book|
672
+
673
+ assert_allows Book do |book|
390
674
  book.author_id = 1
391
675
  end
392
-
676
+
393
677
  ActiveRecord::Migration.deconstrain :books, :author_id, :reference
394
-
395
- assert_allows do |book|
678
+
679
+ assert_allows Book do |book|
396
680
  book.author_id = 2
397
681
  end
398
682
  end
399
-
683
+
684
+ def test_reference_on_a_column_whose_name_is_a_sql_keyword
685
+ ActiveRecord::Migration.constrain :books, :from, :reference => {:authors => :id}
686
+
687
+ assert_prohibits Book, :from, :reference, 'foreign key' do |book|
688
+ book.from = 1
689
+ end
690
+
691
+ author = Author.new
692
+ author.name = "Mark Twain"
693
+ author.bio = "American writer"
694
+ assert author.save
695
+
696
+ assert_equal 1, author.id
697
+
698
+ assert_allows Book do |book|
699
+ book.from = 1
700
+ end
701
+
702
+ ActiveRecord::Migration.deconstrain :books, :from, :reference
703
+
704
+ assert_allows Book do |book|
705
+ book.from = 2
706
+ end
707
+ end
708
+
400
709
  def test_reference_with_on_delete
401
710
  ActiveRecord::Migration.constrain :books, :author_id, :reference => {:authors => :id, :on_delete => :cascade}
402
-
711
+
403
712
  author = Author.new
404
713
  author.name = "Mark Twain"
405
714
  author.bio = "American writer"
406
715
  assert author.save
407
-
716
+
408
717
  assert_equal 1, Author.count
409
-
410
- assert_allows do |book|
718
+
719
+ assert_allows Book do |book|
411
720
  book.title = "The Adventures of Tom Sawyer"
412
721
  book.author_id = 1
413
722
  end
414
-
415
- assert_allows do |book|
723
+
724
+ assert_allows Book do |book|
416
725
  book.title = "The Adventures of Huckleberry Finn"
417
726
  book.author_id = 1
418
727
  end
419
-
728
+
420
729
  author.destroy
421
-
730
+
422
731
  assert_equal 0, Author.count
423
732
  assert_equal 0, Book.count
424
733
  end
425
-
734
+
426
735
  def test_block_syntax
427
736
  ActiveRecord::Migration.constrain :books do |t|
428
737
  t.title :not_blank => true
429
738
  t.isbn :exact_length => 15
430
739
  t.author :alphanumeric => true
431
740
  end
432
-
433
- assert_prohibits :title, :not_blank do |book|
741
+
742
+ assert_prohibits Book, :title, :not_blank do |book|
434
743
  book.title = ' '
435
744
  end
436
-
437
- assert_prohibits :isbn, :exact_length do |book|
745
+
746
+ assert_prohibits Book, :isbn, :exact_length do |book|
438
747
  book.isbn = 'asdf'
439
748
  end
440
-
441
- assert_prohibits :author, :alphanumeric do |book|
749
+
750
+ assert_prohibits Book, :author, :alphanumeric do |book|
442
751
  book.author = 'foo#bar'
443
752
  end
444
-
753
+
445
754
  ActiveRecord::Migration.deconstrain :books do |t|
446
755
  t.title :not_blank
447
756
  t.isbn :exact_length
448
757
  t.author :alphanumeric
449
758
  end
450
-
451
- assert_allows do |book|
759
+
760
+ assert_allows Book do |book|
452
761
  book.title = ' '
453
762
  book.isbn = 'asdf'
454
763
  book.author = 'foo#bar'
455
764
  end
456
765
  end
457
-
766
+
458
767
  def test_multiple_constraints_per_line
459
768
  ActiveRecord::Migration.constrain :books do |t|
460
769
  t.title :not_blank => true, :alphanumeric => true, :blacklist => %w(foo bar)
461
770
  end
462
-
463
- assert_prohibits :title, :not_blank do |book|
771
+
772
+ assert_prohibits Book, :title, [:not_blank, :alphanumeric] do |book|
464
773
  book.title = ' '
465
774
  end
466
-
467
- assert_prohibits :title, :alphanumeric do |book|
775
+
776
+ assert_prohibits Book, :title, :alphanumeric do |book|
468
777
  book.title = 'asdf@asdf'
469
778
  end
470
-
471
- assert_prohibits :title, :blacklist do |book|
779
+
780
+ assert_prohibits Book, :title, :blacklist do |book|
472
781
  book.title = 'foo'
473
782
  end
474
-
783
+
475
784
  ActiveRecord::Migration.deconstrain :books do |t|
476
785
  t.title :not_blank, :alphanumeric, :blacklist
477
786
  end
478
-
479
- assert_allows do |book|
787
+
788
+ assert_allows Book do |book|
480
789
  book.title = ' '
481
790
  end
482
-
483
- assert_allows do |book|
791
+
792
+ assert_allows Book do |book|
484
793
  book.title = 'asdf@asdf'
485
794
  end
486
-
487
- assert_allows do |book|
795
+
796
+ assert_allows Book do |book|
488
797
  book.title = 'foo'
489
798
  end
490
799
  end
491
-
800
+
492
801
  def test_multicolumn_constraint
493
802
  ActiveRecord::Migration.constrain :books, [:title, :isbn], :unique => true
494
-
495
- assert_allows do |book|
803
+
804
+ assert_allows Book do |book|
496
805
  book.title = 'foo'
497
806
  book.isbn = 'bar'
498
807
  end
499
-
500
- assert_allows do |book|
808
+
809
+ assert_allows Book do |book|
501
810
  book.title = 'foo'
502
811
  book.isbn = 'foo'
503
812
  end
504
-
505
- assert_prohibits [:title, :isbn], :unique, 'unique' do |book|
813
+
814
+ assert_prohibits Book, [:title, :isbn], :unique, 'unique' do |book|
506
815
  book.title = 'foo'
507
816
  book.isbn = 'bar'
508
817
  end
509
-
818
+
510
819
  ActiveRecord::Migration.deconstrain :books, [:title, :isbn], :unique
511
-
512
- assert_allows do |book|
820
+
821
+ assert_allows Book do |book|
513
822
  book.title = 'foo'
514
823
  book.isbn = 'bar'
515
824
  end
516
825
  end
517
-
826
+
518
827
  def test_multicolumn_constraint_block_syntax
519
828
  ActiveRecord::Migration.constrain :books do |t|
520
829
  t[:title, :isbn].all :unique => true
521
830
  end
522
-
523
- assert_allows do |book|
831
+
832
+ assert_allows Book do |book|
524
833
  book.title = 'foo'
525
834
  book.isbn = 'bar'
526
835
  end
527
-
528
- assert_allows do |book|
836
+
837
+ assert_allows Book do |book|
529
838
  book.title = 'foo'
530
839
  book.isbn = 'foo'
531
840
  end
532
-
533
- assert_prohibits [:title, :isbn], :unique, 'unique' do |book|
841
+
842
+ assert_prohibits Book, [:title, :isbn], :unique, 'unique' do |book|
534
843
  book.title = 'foo'
535
844
  book.isbn = 'bar'
536
845
  end
537
-
846
+
538
847
  ActiveRecord::Migration.deconstrain :books do |t|
539
848
  t[:title, :isbn].all :unique
540
849
  end
541
-
542
- assert_allows do |book|
850
+
851
+ assert_allows Book do |book|
543
852
  book.title = 'foo'
544
853
  book.isbn = 'bar'
545
854
  end
546
855
  end
547
-
548
- private
549
- def assert_prohibits(column, constraint, constraint_type = 'check')
550
- column = column.join('_') if column.respond_to?(:join)
551
-
552
- book = Book.new
553
- yield(book)
554
- assert book.valid?
555
- error = assert_raise ActiveRecord::StatementInvalid do
556
- book.save
557
- end
558
- assert_match /PGError/, error.message
559
- assert_match /violates #{constraint_type} constraint "books_#{column}_#{constraint}"/, error.message
560
- end
561
-
562
- def assert_allows
563
- first_count = Book.count
564
- book = Book.new
565
- yield(book)
566
- assert book.valid?
567
- assert_nothing_raised do
568
- book.save
569
- end
570
- assert_equal first_count + 1, Book.count
856
+
857
+ def test_lowercase
858
+ ActiveRecord::Migration.constrain :books, :author, :lowercase => true
859
+
860
+ assert_prohibits Book, :author, :lowercase do |book|
861
+ book.author = 'UPPER'
862
+ end
863
+
864
+ assert_allows Book do |book|
865
+ book.author = 'lower with 1337'
866
+ end
867
+
868
+ ActiveRecord::Migration.deconstrain :books, :author, :lowercase
869
+
870
+ assert_allows Book do |book|
871
+ book.author = 'UPPER'
872
+ end
873
+
874
+ end
875
+
876
+ def test_xor
877
+ ActiveRecord::Migration.constrain :books, [:xor_col_1, :xor_col_2], :xor => true
878
+
879
+ assert_prohibits Book, [:xor_col_1, :xor_col_2], :xor do |book|
880
+ book.xor_col_1 = 123
881
+ book.xor_col_2 = 321
882
+ end
883
+
884
+ assert_allows Book do |book|
885
+ book.xor_col_1 = 123
886
+ end
887
+
888
+ assert_allows Book do |book|
889
+ book.xor_col_2 = 123
890
+ end
891
+
892
+ ActiveRecord::Migration.deconstrain :books, [:xor_col_1, :xor_col_2], :xor
893
+
894
+ assert_allows Book do |book|
895
+ book.xor_col_1 = 123
896
+ book.xor_col_2 = 123
897
+ end
571
898
  end
572
899
  end