activerecord 1.14.4 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +400 -1
- data/README +2 -2
- data/RUNNING_UNIT_TESTS +21 -3
- data/Rakefile +55 -10
- data/lib/active_record.rb +10 -4
- data/lib/active_record/acts/list.rb +15 -4
- data/lib/active_record/acts/nested_set.rb +11 -12
- data/lib/active_record/acts/tree.rb +13 -14
- data/lib/active_record/aggregations.rb +46 -22
- data/lib/active_record/associations.rb +213 -162
- data/lib/active_record/associations/association_collection.rb +45 -15
- data/lib/active_record/associations/association_proxy.rb +32 -13
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
- data/lib/active_record/associations/has_many_association.rb +37 -17
- data/lib/active_record/associations/has_many_through_association.rb +120 -30
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +282 -203
- data/lib/active_record/calculations.rb +95 -54
- data/lib/active_record/callbacks.rb +13 -24
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
- data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
- data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
- data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
- data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
- data/lib/active_record/deprecated_associations.rb +24 -10
- data/lib/active_record/deprecated_finders.rb +4 -1
- data/lib/active_record/fixtures.rb +37 -23
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +8 -5
- data/lib/active_record/observer.rb +73 -34
- data/lib/active_record/reflection.rb +21 -7
- data/lib/active_record/schema_dumper.rb +33 -5
- data/lib/active_record/timestamp.rb +23 -34
- data/lib/active_record/transactions.rb +37 -30
- data/lib/active_record/validations.rb +46 -30
- data/lib/active_record/vendor/mysql.rb +20 -5
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record/wrappings.rb +1 -2
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +5 -1
- data/test/abstract_unit.rb +18 -8
- data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
- data/test/adapter_test.rb +9 -7
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +29 -0
- data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
- data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
- data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
- data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
- data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
- data/test/associations_test.rb +339 -45
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +321 -67
- data/test/calculations_test.rb +48 -10
- data/test/callbacks_test.rb +13 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connections/native_db2/connection.rb +18 -17
- data/test/connections/native_firebird/connection.rb +19 -17
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +18 -15
- data/test/connections/native_openbase/connection.rb +14 -15
- data/test/connections/native_oracle/connection.rb +16 -12
- data/test/connections/native_postgresql/connection.rb +16 -17
- data/test/connections/native_sqlite/connection.rb +3 -6
- data/test/connections/native_sqlite3/connection.rb +3 -6
- data/test/connections/native_sqlserver/connection.rb +16 -17
- data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
- data/test/connections/native_sybase/connection.rb +16 -17
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/defaults_test.rb +52 -10
- data/test/deprecated_associations_test.rb +151 -107
- data/test/deprecated_finder_test.rb +83 -66
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +118 -11
- data/test/fixtures/accounts.yml +6 -1
- data/test/fixtures/author.rb +27 -4
- data/test/fixtures/categorizations.yml +8 -2
- data/test/fixtures/category.rb +1 -2
- data/test/fixtures/comments.yml +0 -6
- data/test/fixtures/companies.yml +6 -1
- data/test/fixtures/company.rb +23 -1
- data/test/fixtures/company_in_module.rb +8 -10
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/customers.yml +9 -0
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +9 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
- data/test/fixtures/db_definitions/firebird.sql +13 -1
- data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
- data/test/fixtures/db_definitions/frontbase.sql +262 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
- data/test/fixtures/db_definitions/mysql.sql +23 -14
- data/test/fixtures/db_definitions/openbase.sql +13 -1
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +29 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +13 -3
- data/test/fixtures/db_definitions/schema.rb +29 -1
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +12 -3
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +35 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
- data/test/fixtures/db_definitions/sybase.sql +13 -4
- data/test/fixtures/developer.rb +12 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/funny_jokes.yml +3 -7
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/mixin.rb +15 -0
- data/test/fixtures/mixins.yml +38 -0
- data/test/fixtures/post.rb +3 -2
- data/test/fixtures/project.rb +3 -1
- data/test/fixtures/topic.rb +6 -1
- data/test/fixtures/topics.yml +4 -4
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +45 -0
- data/test/inheritance_test.rb +67 -6
- data/test/lifecycle_test.rb +40 -19
- data/test/locking_test.rb +170 -26
- data/test/method_scoping_test.rb +2 -2
- data/test/migration_test.rb +387 -110
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +14 -2
- data/test/mixin_test.rb +56 -18
- data/test/modules_test.rb +8 -2
- data/test/multiple_db_test.rb +2 -2
- data/test/pk_test.rb +1 -0
- data/test/reflection_test.rb +8 -2
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +40 -4
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +19 -16
- data/test/transactions_test.rb +86 -72
- data/test/validations_test.rb +126 -56
- data/test/xml_serialization_test.rb +125 -0
- metadata +45 -11
- data/lib/active_record/locking.rb +0 -79
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'abstract_unit'
|
2
|
+
require "#{File.dirname(__FILE__)}/../lib/active_record/schema"
|
3
|
+
|
4
|
+
if ActiveRecord::Base.connection.supports_migrations?
|
5
|
+
class Order < ActiveRecord::Base
|
6
|
+
self.table_name = '[order]'
|
7
|
+
end
|
8
|
+
|
9
|
+
class TableNameTest < Test::Unit::TestCase
|
10
|
+
self.use_transactional_fixtures = false
|
11
|
+
|
12
|
+
# Ensures Model.columns works when using SQLServer escape characters.
|
13
|
+
# Enables legacy schemas using SQL reserved words as table names.
|
14
|
+
# Should work with table names with spaces as well ('table name').
|
15
|
+
def test_escaped_table_name
|
16
|
+
assert_nothing_raised do
|
17
|
+
ActiveRecord::Base.connection.select_all 'SELECT * FROM [order]'
|
18
|
+
end
|
19
|
+
assert_equal '[order]', Order.table_name
|
20
|
+
assert_equal 5, Order.columns.length
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'abstract_unit'
|
2
2
|
require 'fixtures/topic'
|
3
|
+
require 'fixtures/reply'
|
3
4
|
|
4
|
-
|
5
|
-
|
5
|
+
unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
|
6
|
+
class ThreadedConnectionsTest < Test::Unit::TestCase
|
7
|
+
self.use_transactional_fixtures = false
|
6
8
|
|
7
|
-
|
9
|
+
fixtures :topics
|
8
10
|
|
9
11
|
def setup
|
10
12
|
@connection = ActiveRecord::Base.remove_connection
|
@@ -25,21 +27,22 @@ class ThreadedConnectionsTest < Test::Unit::TestCase
|
|
25
27
|
ActiveRecord::Base.allow_concurrency = use_threaded_connections
|
26
28
|
ActiveRecord::Base.establish_connection(@connection)
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
5.times do
|
31
|
+
Thread.new do
|
32
|
+
Topic.find :first
|
33
|
+
@connections << ActiveRecord::Base.active_connections.values.first
|
34
|
+
end.join
|
35
|
+
end
|
33
36
|
end
|
34
|
-
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
def test_threaded_connections
|
39
|
+
gather_connections(true)
|
40
|
+
assert_equal @connections.uniq.length, 5
|
41
|
+
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
def test_unthreaded_connections
|
44
|
+
gather_connections(false)
|
45
|
+
assert_equal @connections.uniq.length, 1
|
46
|
+
end
|
44
47
|
end
|
45
48
|
end
|
data/test/transactions_test.rb
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
require 'abstract_unit'
|
2
2
|
require 'fixtures/topic'
|
3
|
+
require 'fixtures/reply'
|
3
4
|
require 'fixtures/developer'
|
4
5
|
|
5
6
|
class TransactionTest < Test::Unit::TestCase
|
6
7
|
self.use_transactional_fixtures = false
|
7
|
-
|
8
8
|
fixtures :topics, :developers
|
9
9
|
|
10
10
|
def setup
|
11
|
-
# sqlite does not seem to return these in the right order, so we sort them
|
12
|
-
# explicitly for sqlite's sake. sqlite3 does fine.
|
13
11
|
@first, @second = Topic.find(1, 2).sort_by { |t| t.id }
|
14
12
|
end
|
15
13
|
|
@@ -92,12 +90,14 @@ class TransactionTest < Test::Unit::TestCase
|
|
92
90
|
assert !@first.approved?, "First should be unapproved initially"
|
93
91
|
|
94
92
|
begin
|
95
|
-
|
96
|
-
@first
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
93
|
+
assert_deprecated /Object transactions/ do
|
94
|
+
Topic.transaction(@first, @second) do
|
95
|
+
@first.approved = true
|
96
|
+
@second.approved = false
|
97
|
+
@first.save
|
98
|
+
@second.save
|
99
|
+
raise "Bad things!"
|
100
|
+
end
|
101
101
|
end
|
102
102
|
rescue
|
103
103
|
# caught it
|
@@ -136,81 +136,95 @@ class TransactionTest < Test::Unit::TestCase
|
|
136
136
|
assert !Topic.find(2).approved?, "Second should have been unapproved"
|
137
137
|
end
|
138
138
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
139
|
+
private
|
140
|
+
def add_exception_raising_after_save_callback_to_topic
|
141
|
+
Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
|
142
|
+
end
|
143
|
+
|
144
|
+
def remove_exception_raising_after_save_callback_to_topic
|
145
|
+
Topic.class_eval { remove_method :after_save }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if current_adapter?(:PostgreSQLAdapter)
|
150
|
+
class ConcurrentTransactionTest < TransactionTest
|
151
|
+
def setup
|
152
|
+
@allow_concurrency = ActiveRecord::Base.allow_concurrency
|
153
|
+
ActiveRecord::Base.allow_concurrency = true
|
154
|
+
super
|
155
|
+
end
|
156
|
+
|
157
|
+
def teardown
|
158
|
+
super
|
159
|
+
ActiveRecord::Base.allow_concurrency = @allow_concurrency
|
160
|
+
end
|
161
|
+
|
162
|
+
# This will cause transactions to overlap and fail unless they are performed on
|
163
|
+
# separate database connections.
|
164
|
+
def test_transaction_per_thread
|
165
|
+
assert_nothing_raised do
|
166
|
+
threads = (1..3).map do
|
167
|
+
Thread.new do
|
168
|
+
Topic.transaction do
|
169
|
+
topic = Topic.find(1)
|
170
|
+
topic.approved = !topic.approved?
|
171
|
+
topic.save!
|
172
|
+
topic.approved = !topic.approved?
|
173
|
+
topic.save!
|
174
|
+
end
|
151
175
|
end
|
152
176
|
end
|
153
|
-
end
|
154
177
|
|
155
|
-
|
178
|
+
threads.each { |t| t.join }
|
179
|
+
end
|
156
180
|
end
|
157
|
-
end
|
158
181
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
182
|
+
# Test for dirty reads among simultaneous transactions.
|
183
|
+
def test_transaction_isolation__read_committed
|
184
|
+
# Should be invariant.
|
185
|
+
original_salary = Developer.find(1).salary
|
186
|
+
temporary_salary = 200000
|
187
|
+
|
188
|
+
assert_nothing_raised do
|
189
|
+
threads = (1..3).map do
|
190
|
+
Thread.new do
|
191
|
+
Developer.transaction do
|
192
|
+
# Expect original salary.
|
193
|
+
dev = Developer.find(1)
|
194
|
+
assert_equal original_salary, dev.salary
|
195
|
+
|
196
|
+
dev.salary = temporary_salary
|
197
|
+
dev.save!
|
198
|
+
|
199
|
+
# Expect temporary salary.
|
200
|
+
dev = Developer.find(1)
|
201
|
+
assert_equal temporary_salary, dev.salary
|
202
|
+
|
203
|
+
dev.salary = original_salary
|
204
|
+
dev.save!
|
205
|
+
|
206
|
+
# Expect original salary.
|
207
|
+
dev = Developer.find(1)
|
208
|
+
assert_equal original_salary, dev.salary
|
209
|
+
end
|
186
210
|
end
|
187
211
|
end
|
188
|
-
end
|
189
212
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
213
|
+
# Keep our eyes peeled.
|
214
|
+
threads << Thread.new do
|
215
|
+
10.times do
|
216
|
+
sleep 0.05
|
217
|
+
Developer.transaction do
|
218
|
+
# Always expect original salary.
|
219
|
+
assert_equal original_salary, Developer.find(1).salary
|
220
|
+
end
|
197
221
|
end
|
198
222
|
end
|
223
|
+
|
224
|
+
threads.each { |t| t.join }
|
199
225
|
end
|
200
226
|
|
201
|
-
|
227
|
+
assert_equal original_salary, Developer.find(1).salary
|
202
228
|
end
|
203
|
-
|
204
|
-
assert_equal original_salary, Developer.find(1).salary
|
205
229
|
end
|
206
|
-
|
207
|
-
|
208
|
-
private
|
209
|
-
def add_exception_raising_after_save_callback_to_topic
|
210
|
-
Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
|
211
|
-
end
|
212
|
-
|
213
|
-
def remove_exception_raising_after_save_callback_to_topic
|
214
|
-
Topic.class_eval { remove_method :after_save }
|
215
|
-
end
|
216
230
|
end
|
data/test/validations_test.rb
CHANGED
@@ -26,16 +26,16 @@ class ValidationsTest < Test::Unit::TestCase
|
|
26
26
|
def test_single_field_validation
|
27
27
|
r = Reply.new
|
28
28
|
r.title = "There's no content!"
|
29
|
-
assert !r.
|
29
|
+
assert !r.valid?, "A reply without content shouldn't be saveable"
|
30
30
|
|
31
31
|
r.content = "Messa content!"
|
32
|
-
assert r.
|
32
|
+
assert r.valid?, "A reply with content should be saveable"
|
33
33
|
end
|
34
34
|
|
35
35
|
def test_single_attr_validation_and_error_msg
|
36
36
|
r = Reply.new
|
37
37
|
r.title = "There's no content!"
|
38
|
-
r.
|
38
|
+
assert !r.valid?
|
39
39
|
assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
|
40
40
|
assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
|
41
41
|
assert_equal 1, r.errors.count
|
@@ -43,7 +43,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
43
43
|
|
44
44
|
def test_double_attr_validation_and_error_msg
|
45
45
|
r = Reply.new
|
46
|
-
assert !r.
|
46
|
+
assert !r.valid?
|
47
47
|
|
48
48
|
assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid"
|
49
49
|
assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error"
|
@@ -57,7 +57,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
57
57
|
def test_error_on_create
|
58
58
|
r = Reply.new
|
59
59
|
r.title = "Wrong Create"
|
60
|
-
assert !r.
|
60
|
+
assert !r.valid?
|
61
61
|
assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
|
62
62
|
assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error"
|
63
63
|
end
|
@@ -88,6 +88,12 @@ class ValidationsTest < Test::Unit::TestCase
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
def test_scoped_create_without_attributes
|
92
|
+
Reply.with_scope(:create => {}) do
|
93
|
+
assert_raises(ActiveRecord::RecordInvalid) { Reply.create! }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
91
97
|
def test_single_error_per_attr_iteration
|
92
98
|
r = Reply.new
|
93
99
|
r.save
|
@@ -135,6 +141,12 @@ class ValidationsTest < Test::Unit::TestCase
|
|
135
141
|
assert reply.save(false)
|
136
142
|
end
|
137
143
|
|
144
|
+
def test_create_without_validation_bang
|
145
|
+
count = Reply.count
|
146
|
+
assert_nothing_raised { Reply.new.save_without_validation! }
|
147
|
+
assert count+1, Reply.count
|
148
|
+
end
|
149
|
+
|
138
150
|
def test_validates_each
|
139
151
|
perform = true
|
140
152
|
hits = 0
|
@@ -153,24 +165,21 @@ class ValidationsTest < Test::Unit::TestCase
|
|
153
165
|
perform = false
|
154
166
|
end
|
155
167
|
|
156
|
-
def
|
157
|
-
|
158
|
-
assert !developer.save
|
159
|
-
assert_equal "is too short (minimum is 3 characters)", developer.errors.on("name")
|
168
|
+
def test_no_title_confirmation
|
169
|
+
Topic.validates_confirmation_of(:title)
|
160
170
|
|
161
|
-
|
162
|
-
assert
|
163
|
-
assert_equal "is too long (maximum is 20 characters)", developer.errors.on("name")
|
171
|
+
t = Topic.new(:author_name => "Plutarch")
|
172
|
+
assert t.valid?
|
164
173
|
|
165
|
-
|
166
|
-
assert
|
167
|
-
end
|
174
|
+
t.title_confirmation = "Parallel Lives"
|
175
|
+
assert !t.valid?
|
168
176
|
|
169
|
-
|
170
|
-
|
177
|
+
t.title_confirmation = nil
|
178
|
+
t.title = "Parallel Lives"
|
179
|
+
assert t.valid?
|
171
180
|
|
172
|
-
t =
|
173
|
-
assert t.
|
181
|
+
t.title_confirmation = "Parallel Lives"
|
182
|
+
assert t.valid?
|
174
183
|
end
|
175
184
|
|
176
185
|
def test_title_confirmation
|
@@ -304,6 +313,37 @@ class ValidationsTest < Test::Unit::TestCase
|
|
304
313
|
assert !r3.save, "Saving r3 the third time."
|
305
314
|
end
|
306
315
|
|
316
|
+
def test_validate_case_insensitive_uniqueness
|
317
|
+
Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true)
|
318
|
+
|
319
|
+
t = Topic.new("title" => "I'm unique!", :parent_id => 2)
|
320
|
+
assert t.save, "Should save t as unique"
|
321
|
+
|
322
|
+
t.content = "Remaining unique"
|
323
|
+
assert t.save, "Should still save t as unique"
|
324
|
+
|
325
|
+
t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1)
|
326
|
+
assert !t2.valid?, "Shouldn't be valid"
|
327
|
+
assert !t2.save, "Shouldn't save t2 as unique"
|
328
|
+
assert t2.errors.on(:title)
|
329
|
+
assert t2.errors.on(:parent_id)
|
330
|
+
assert_equal "has already been taken", t2.errors.on(:title)
|
331
|
+
|
332
|
+
t2.title = "I'm truly UNIQUE!"
|
333
|
+
assert !t2.valid?, "Shouldn't be valid"
|
334
|
+
assert !t2.save, "Shouldn't save t2 as unique"
|
335
|
+
assert_nil t2.errors.on(:title)
|
336
|
+
assert t2.errors.on(:parent_id)
|
337
|
+
|
338
|
+
t2.parent_id = 3
|
339
|
+
assert t2.save, "Should now save t2 as unique"
|
340
|
+
|
341
|
+
t2.parent_id = nil
|
342
|
+
t2.title = nil
|
343
|
+
assert t2.valid?, "should validate with nil"
|
344
|
+
assert t2.save, "should save with nil"
|
345
|
+
end
|
346
|
+
|
307
347
|
def test_validate_format
|
308
348
|
Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
|
309
349
|
|
@@ -573,6 +613,18 @@ class ValidationsTest < Test::Unit::TestCase
|
|
573
613
|
|
574
614
|
assert_equal 'tu est trops petit hombre 10', t.errors['title']
|
575
615
|
end
|
616
|
+
|
617
|
+
def test_add_on_boundary_breaking_is_deprecated
|
618
|
+
t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
|
619
|
+
class << t
|
620
|
+
def validate
|
621
|
+
errors.add_on_boundary_breaking('title', 1..6)
|
622
|
+
end
|
623
|
+
end
|
624
|
+
assert_deprecated 'add_on_boundary_breaking' do
|
625
|
+
assert !t.valid?
|
626
|
+
end
|
627
|
+
end
|
576
628
|
|
577
629
|
def test_validates_size_of_association
|
578
630
|
assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
|
@@ -672,7 +724,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
672
724
|
|
673
725
|
t = Topic.create("title" => "一二三四五", "content" => "whatever")
|
674
726
|
assert t.valid?
|
675
|
-
|
727
|
+
|
676
728
|
t.title = "一二34五六"
|
677
729
|
assert !t.valid?
|
678
730
|
assert t.errors.on(:title)
|
@@ -714,16 +766,16 @@ class ValidationsTest < Test::Unit::TestCase
|
|
714
766
|
assert !t.save
|
715
767
|
assert t.errors.on(:title)
|
716
768
|
assert_equal "長すぎます: 10", t.errors[:title]
|
717
|
-
|
769
|
+
|
718
770
|
t.title = "一二三四五六七八九"
|
719
771
|
assert t.save
|
720
|
-
|
772
|
+
|
721
773
|
t.title = "一二3"
|
722
774
|
assert t.save
|
723
|
-
|
775
|
+
|
724
776
|
t.content = "一二三四五六七八九十"
|
725
777
|
assert t.save
|
726
|
-
|
778
|
+
|
727
779
|
t.content = t.title = "一二三四五六"
|
728
780
|
assert t.save
|
729
781
|
end
|
@@ -736,17 +788,17 @@ class ValidationsTest < Test::Unit::TestCase
|
|
736
788
|
t = Topic.create("title" => "一二三4", "content" => "whatever")
|
737
789
|
assert !t.save
|
738
790
|
assert t.errors.on(:title)
|
739
|
-
|
791
|
+
|
740
792
|
t.title = "1二三4"
|
741
793
|
assert !t.save
|
742
794
|
assert t.errors.on(:title)
|
743
795
|
assert_equal "短すぎます: 5", t.errors[:title]
|
744
|
-
|
796
|
+
|
745
797
|
t.title = "valid"
|
746
798
|
t.content = "一二三四五六七八九十A"
|
747
799
|
assert !t.save
|
748
800
|
assert t.errors.on(:content)
|
749
|
-
|
801
|
+
|
750
802
|
t.content = "一二345"
|
751
803
|
assert t.save
|
752
804
|
end
|
@@ -814,7 +866,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
814
866
|
end
|
815
867
|
|
816
868
|
def test_throw_away_typing
|
817
|
-
d = Developer.
|
869
|
+
d = Developer.new("name" => "David", "salary" => "100,000")
|
818
870
|
assert !d.valid?
|
819
871
|
assert_equal 100, d.salary
|
820
872
|
assert_equal "100,000", d.salary_before_type_cast
|
@@ -829,20 +881,20 @@ class ValidationsTest < Test::Unit::TestCase
|
|
829
881
|
end
|
830
882
|
|
831
883
|
def test_validates_confirmation_of_with_custom_error_using_quotes
|
832
|
-
Developer.validates_confirmation_of :name, :message=> "
|
884
|
+
Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
|
833
885
|
d = Developer.new
|
834
886
|
d.name = "John"
|
835
887
|
d.name_confirmation = "Johnny"
|
836
888
|
assert !d.valid?
|
837
|
-
assert_equal
|
889
|
+
assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
|
838
890
|
end
|
839
891
|
|
840
892
|
def test_validates_format_of_with_custom_error_using_quotes
|
841
|
-
Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "
|
893
|
+
Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
|
842
894
|
d = Developer.new
|
843
|
-
d.name = "John 32"
|
895
|
+
d.name = d.name_confirmation = "John 32"
|
844
896
|
assert !d.valid?
|
845
|
-
assert_equal
|
897
|
+
assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
|
846
898
|
end
|
847
899
|
|
848
900
|
def test_validates_inclusion_of_with_custom_error_using_quotes
|
@@ -850,7 +902,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
850
902
|
d = Developer.new
|
851
903
|
d.salary = "90,000"
|
852
904
|
assert !d.valid?
|
853
|
-
assert_equal
|
905
|
+
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).first
|
854
906
|
end
|
855
907
|
|
856
908
|
def test_validates_length_of_with_custom_too_long_using_quotes
|
@@ -858,7 +910,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
858
910
|
d = Developer.new
|
859
911
|
d.name = "Jeffrey"
|
860
912
|
assert !d.valid?
|
861
|
-
assert_equal
|
913
|
+
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).first
|
862
914
|
end
|
863
915
|
|
864
916
|
def test_validates_length_of_with_custom_too_short_using_quotes
|
@@ -866,7 +918,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
866
918
|
d = Developer.new
|
867
919
|
d.name = "Joe"
|
868
920
|
assert !d.valid?
|
869
|
-
assert_equal
|
921
|
+
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).first
|
870
922
|
end
|
871
923
|
|
872
924
|
def test_validates_length_of_with_custom_message_using_quotes
|
@@ -874,7 +926,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
874
926
|
d = Developer.new
|
875
927
|
d.name = "Joe"
|
876
928
|
assert !d.valid?
|
877
|
-
assert_equal
|
929
|
+
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).first
|
878
930
|
end
|
879
931
|
|
880
932
|
def test_validates_presence_of_with_custom_message_using_quotes
|
@@ -882,7 +934,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
882
934
|
d = Developer.new
|
883
935
|
d.name = "Joe"
|
884
936
|
assert !d.valid?
|
885
|
-
assert_equal
|
937
|
+
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
|
886
938
|
end
|
887
939
|
|
888
940
|
def test_validates_uniqueness_of_with_custom_message_using_quotes
|
@@ -890,7 +942,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
890
942
|
d = Developer.new
|
891
943
|
d.name = "David"
|
892
944
|
assert !d.valid?
|
893
|
-
assert_equal
|
945
|
+
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).first
|
894
946
|
end
|
895
947
|
|
896
948
|
def test_validates_associated_with_custom_message_using_quotes
|
@@ -899,7 +951,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
899
951
|
r = Reply.create("title" => "A reply", "content" => "with content!")
|
900
952
|
r.topic = Topic.create("title" => "uhohuhoh")
|
901
953
|
assert !r.valid?
|
902
|
-
assert_equal
|
954
|
+
assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic).first
|
903
955
|
end
|
904
956
|
|
905
957
|
def test_conditional_validation_using_method_true
|
@@ -916,7 +968,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
916
968
|
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true_but_its_not )
|
917
969
|
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
|
918
970
|
assert t.valid?
|
919
|
-
assert !t.errors.on(:title)
|
971
|
+
assert !t.errors.on(:title)
|
920
972
|
end
|
921
973
|
|
922
974
|
def test_conditional_validation_using_string_true
|
@@ -964,16 +1016,28 @@ class ValidationsTest < Test::Unit::TestCase
|
|
964
1016
|
r.topic = Topic.find :first
|
965
1017
|
assert r.valid?
|
966
1018
|
end
|
1019
|
+
|
1020
|
+
def test_errors_to_xml
|
1021
|
+
r = Reply.new :title => "Wrong Create"
|
1022
|
+
assert !r.valid?
|
1023
|
+
xml = r.errors.to_xml(:skip_instruct => true)
|
1024
|
+
assert_equal "<errors>", xml.first(8)
|
1025
|
+
assert xml.include?("<error>Title is Wrong Create</error>")
|
1026
|
+
assert xml.include?("<error>Content Empty</error>")
|
1027
|
+
end
|
967
1028
|
end
|
968
1029
|
|
969
1030
|
|
970
|
-
class ValidatesNumericalityTest
|
971
|
-
NIL = [nil
|
972
|
-
|
1031
|
+
class ValidatesNumericalityTest < Test::Unit::TestCase
|
1032
|
+
NIL = [nil]
|
1033
|
+
BLANK = ["", " ", " \t \r \n"]
|
1034
|
+
BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits
|
1035
|
+
FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5)
|
973
1036
|
INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
|
974
1037
|
FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
|
975
1038
|
INTEGERS = [0, 10, -10] + INTEGER_STRINGS
|
976
|
-
|
1039
|
+
BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
|
1040
|
+
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
|
977
1041
|
|
978
1042
|
def setup
|
979
1043
|
Topic.write_inheritable_attribute(:validate, nil)
|
@@ -984,44 +1048,50 @@ class ValidatesNumericalityTest
|
|
984
1048
|
def test_default_validates_numericality_of
|
985
1049
|
Topic.validates_numericality_of :approved
|
986
1050
|
|
987
|
-
invalid!(NIL + JUNK)
|
988
|
-
valid!(FLOATS + INTEGERS)
|
1051
|
+
invalid!(NIL + BLANK + JUNK)
|
1052
|
+
valid!(FLOATS + INTEGERS + BIGDECIMAL)
|
989
1053
|
end
|
990
1054
|
|
991
1055
|
def test_validates_numericality_of_with_nil_allowed
|
992
1056
|
Topic.validates_numericality_of :approved, :allow_nil => true
|
993
1057
|
|
994
|
-
invalid!(JUNK)
|
995
|
-
valid!(NIL + FLOATS + INTEGERS)
|
1058
|
+
invalid!(BLANK + JUNK)
|
1059
|
+
valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL)
|
996
1060
|
end
|
997
1061
|
|
998
1062
|
def test_validates_numericality_of_with_integer_only
|
999
1063
|
Topic.validates_numericality_of :approved, :only_integer => true
|
1000
1064
|
|
1001
|
-
invalid!(NIL + JUNK + FLOATS)
|
1065
|
+
invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL)
|
1002
1066
|
valid!(INTEGERS)
|
1003
1067
|
end
|
1004
1068
|
|
1005
1069
|
def test_validates_numericality_of_with_integer_only_and_nil_allowed
|
1006
1070
|
Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
|
1007
1071
|
|
1008
|
-
invalid!(JUNK + FLOATS)
|
1072
|
+
invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL)
|
1009
1073
|
valid!(NIL + INTEGERS)
|
1010
1074
|
end
|
1011
1075
|
|
1012
1076
|
private
|
1013
1077
|
def invalid!(values)
|
1014
|
-
values
|
1015
|
-
topic
|
1016
|
-
assert !topic.valid?, "#{value} not rejected as a number"
|
1078
|
+
with_each_topic_approved_value(values) do |topic, value|
|
1079
|
+
assert !topic.valid?, "#{value.inspect} not rejected as a number"
|
1017
1080
|
assert topic.errors.on(:approved)
|
1018
1081
|
end
|
1019
1082
|
end
|
1020
1083
|
|
1021
1084
|
def valid!(values)
|
1085
|
+
with_each_topic_approved_value(values) do |topic, value|
|
1086
|
+
assert topic.valid?, "#{value.inspect} not accepted as a number"
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
def with_each_topic_approved_value(values)
|
1091
|
+
topic = Topic.new("title" => "numeric test", "content" => "whatever")
|
1022
1092
|
values.each do |value|
|
1023
|
-
topic
|
1024
|
-
|
1093
|
+
topic.approved = value
|
1094
|
+
yield topic, value
|
1025
1095
|
end
|
1026
1096
|
end
|
1027
1097
|
end
|