activerecord 1.15.6 → 2.0.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.

Files changed (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -1,6 +1,8 @@
1
1
  require 'abstract_unit'
2
+ require 'fixtures/topic'
2
3
 
3
4
  class AttributeMethodsTest < Test::Unit::TestCase
5
+ fixtures :topics
4
6
  def setup
5
7
  @old_suffixes = ActiveRecord::Base.send(:attribute_method_suffixes).dup
6
8
  @target = Class.new(ActiveRecord::Base)
@@ -46,4 +48,99 @@ class AttributeMethodsTest < Test::Unit::TestCase
46
48
  assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3)
47
49
  end
48
50
  end
51
+
52
+ def test_should_unserialize_attributes_for_frozen_records
53
+ myobj = {:value1 => :value2}
54
+ topic = Topic.create("content" => myobj)
55
+ topic.freeze
56
+ assert_equal myobj, topic.content
57
+ end
58
+
59
+ def test_kernel_methods_not_implemented_in_activerecord
60
+ %w(test name display y).each do |method|
61
+ assert_equal false, ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined"
62
+ end
63
+ end
64
+
65
+ def test_primary_key_implemented
66
+ assert_equal true, Class.new(ActiveRecord::Base).instance_method_already_implemented?('id')
67
+ end
68
+
69
+ def test_defined_kernel_methods_implemented_in_model
70
+ %w(test name display y).each do |method|
71
+ klass = Class.new ActiveRecord::Base
72
+ klass.class_eval "def #{method}() 'defined #{method}' end"
73
+ assert_equal true, klass.instance_method_already_implemented?(method), "##{method} is not defined"
74
+ end
75
+ end
76
+
77
+ def test_defined_kernel_methods_implemented_in_model_abstract_subclass
78
+ %w(test name display y).each do |method|
79
+ abstract = Class.new ActiveRecord::Base
80
+ abstract.class_eval "def #{method}() 'defined #{method}' end"
81
+ abstract.abstract_class = true
82
+ klass = Class.new abstract
83
+ assert_equal true, klass.instance_method_already_implemented?(method), "##{method} is not defined"
84
+ end
85
+ end
86
+
87
+ def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model
88
+ %w(save create_or_update).each do |method|
89
+ klass = Class.new ActiveRecord::Base
90
+ klass.class_eval "def #{method}() 'defined #{method}' end"
91
+ assert_raises ActiveRecord::DangerousAttributeError do
92
+ klass.instance_method_already_implemented?(method)
93
+ end
94
+ end
95
+ end
96
+
97
+ def test_only_time_related_columns_are_meant_to_be_cached_by_default
98
+ expected = %w(datetime timestamp time date).sort
99
+ assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort
100
+ end
101
+
102
+ def test_declaring_attributes_as_cached_adds_them_to_the_attributes_cached_by_default
103
+ default_attributes = Topic.cached_attributes
104
+ Topic.cache_attributes :replies_count
105
+ expected = default_attributes + ["replies_count"]
106
+ assert_equal expected.sort, Topic.cached_attributes.sort
107
+ Topic.instance_variable_set "@cached_attributes", nil
108
+ end
109
+
110
+ def test_time_related_columns_are_actually_cached
111
+ column_types = %w(datetime timestamp time date).map(&:to_sym)
112
+ column_names = Topic.columns.select{|c| column_types.include?(c.type) }.map(&:name)
113
+
114
+ assert_equal column_names.sort, Topic.cached_attributes.sort
115
+ assert_equal time_related_columns_on_topic.sort, Topic.cached_attributes.sort
116
+ end
117
+
118
+ def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else
119
+ t = topics(:first)
120
+ cache = t.instance_variable_get "@attributes_cache"
121
+
122
+ assert_not_nil cache
123
+ assert cache.empty?
124
+
125
+ all_columns = Topic.columns.map(&:name)
126
+ cached_columns = time_related_columns_on_topic
127
+ uncached_columns = all_columns - cached_columns
128
+
129
+ all_columns.each do |attr_name|
130
+ attribute_gets_cached = Topic.cache_attribute?(attr_name)
131
+ val = t.send attr_name unless attr_name == "type"
132
+ if attribute_gets_cached
133
+ assert cached_columns.include?(attr_name)
134
+ assert_equal val, cache[attr_name]
135
+ else
136
+ assert uncached_columns.include?(attr_name)
137
+ assert !cache.include?(attr_name)
138
+ end
139
+ end
140
+ end
141
+
142
+ private
143
+ def time_related_columns_on_topic
144
+ Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name)
145
+ end
49
146
  end
@@ -11,6 +11,8 @@ require 'fixtures/column_name'
11
11
  require 'fixtures/subscriber'
12
12
  require 'fixtures/keyboard'
13
13
  require 'fixtures/post'
14
+ require 'fixtures/minimalistic'
15
+ require 'rexml/document'
14
16
 
15
17
  class Category < ActiveRecord::Base; end
16
18
  class Smarts < ActiveRecord::Base; end
@@ -38,6 +40,11 @@ class LooseDescendant < LoosePerson
38
40
  attr_protected :phone_number
39
41
  end
40
42
 
43
+ class LooseDescendantSecond< LoosePerson
44
+ attr_protected :phone_number
45
+ attr_protected :name
46
+ end
47
+
41
48
  class TightPerson < ActiveRecord::Base
42
49
  self.table_name = 'people'
43
50
  attr_accessible :name, :address
@@ -47,14 +54,24 @@ class TightDescendant < TightPerson
47
54
  attr_accessible :phone_number
48
55
  end
49
56
 
57
+ class ReadonlyTitlePost < Post
58
+ attr_readonly :title
59
+ end
60
+
50
61
  class Booleantest < ActiveRecord::Base; end
51
62
 
52
63
  class Task < ActiveRecord::Base
53
64
  attr_protected :starting
54
65
  end
55
66
 
67
+ class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base
68
+ self.table_name = 'topics'
69
+ attr_accessible :author_name
70
+ attr_protected :content
71
+ end
72
+
56
73
  class BasicsTest < Test::Unit::TestCase
57
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts
74
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics
58
75
 
59
76
  def test_table_exists
60
77
  assert !NonExistentTable.table_exists?
@@ -174,6 +191,15 @@ class BasicsTest < Test::Unit::TestCase
174
191
  assert_nil topic.title
175
192
  end
176
193
 
194
+ def test_save_for_record_with_only_primary_key
195
+ minimalistic = Minimalistic.new
196
+ assert_nothing_raised { minimalistic.save }
197
+ end
198
+
199
+ def test_save_for_record_with_only_primary_key_that_is_provided
200
+ assert_nothing_raised { Minimalistic.create!(:id => 2) }
201
+ end
202
+
177
203
  def test_hashes_not_mangled
178
204
  new_topic = { :title => "New Topic" }
179
205
  new_topic_values = { :title => "AnotherTopic" }
@@ -230,6 +256,11 @@ class BasicsTest < Test::Unit::TestCase
230
256
  topicReloaded.send :write_attribute, 'does_not_exist', 'test'
231
257
  assert_nothing_raised { topicReloaded.save }
232
258
  end
259
+
260
+ def test_update_for_record_with_only_primary_key
261
+ minimalistic = minimalistics(:first)
262
+ assert_nothing_raised { minimalistic.save }
263
+ end
233
264
 
234
265
  def test_write_attribute
235
266
  topic = Topic.new
@@ -289,25 +320,59 @@ class BasicsTest < Test::Unit::TestCase
289
320
  assert topic.approved?, "approved should be true"
290
321
  # puts ""
291
322
  end
292
-
293
- def test_reader_generation
294
- Topic.find(:first).title
295
- Firm.find(:first).name
296
- Client.find(:first).name
297
- if ActiveRecord::Base.generate_read_methods
298
- assert_readers(Topic, %w(type replies_count))
299
- assert_readers(Firm, %w(type))
300
- assert_readers(Client, %w(type ruby_type rating?))
301
- else
302
- [Topic, Firm, Client].each {|klass| assert_equal klass.read_methods, {}}
323
+
324
+ def test_query_attribute_string
325
+ [nil, "", " "].each do |value|
326
+ assert_equal false, Topic.new(:author_name => value).author_name?
327
+ end
328
+
329
+ assert_equal true, Topic.new(:author_name => "Name").author_name?
330
+ end
331
+
332
+ def test_query_attribute_number
333
+ [nil, 0, "0"].each do |value|
334
+ assert_equal false, Developer.new(:salary => value).salary?
303
335
  end
336
+
337
+ assert_equal true, Developer.new(:salary => 1).salary?
338
+ assert_equal true, Developer.new(:salary => "1").salary?
304
339
  end
340
+
341
+ def test_query_attribute_boolean
342
+ [nil, "", false, "false", "f", 0].each do |value|
343
+ assert_equal false, Topic.new(:approved => value).approved?
344
+ end
345
+
346
+ [true, "true", "1", 1].each do |value|
347
+ assert_equal true, Topic.new(:approved => value).approved?
348
+ end
349
+ end
350
+
351
+ def test_query_attribute_with_custom_fields
352
+ object = Company.find_by_sql(<<-SQL).first
353
+ SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value
354
+ FROM companies c1, companies c2
355
+ WHERE c1.firm_id = c2.id
356
+ AND c1.id = 2
357
+ SQL
358
+
359
+ assert_equal "Firm", object.string_value
360
+ assert object.string_value?
361
+
362
+ object.string_value = " "
363
+ assert !object.string_value?
364
+
365
+ assert_equal 1, object.int_value.to_i
366
+ assert object.int_value?
367
+
368
+ object.int_value = "0"
369
+ assert !object.int_value?
370
+ end
371
+
305
372
 
306
373
  def test_reader_for_invalid_column_names
307
- # column names which aren't legal ruby ids
308
- topic = Topic.find(:first)
309
- topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil)
310
- assert !Topic.read_methods.include?("mumub-jumbo")
374
+ Topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil)
375
+ assert !Topic.generated_methods.include?("mumub-jumbo")
311
376
  end
312
377
 
313
378
  def test_non_attribute_access_and_assignment
@@ -321,8 +386,9 @@ class BasicsTest < Test::Unit::TestCase
321
386
  # SQL Server doesn't have a separate column type just for dates, so all are returned as time
322
387
  return true if current_adapter?(:SQLServerAdapter)
323
388
 
324
- if current_adapter?(:SybaseAdapter)
389
+ if current_adapter?(:SybaseAdapter, :OracleAdapter)
325
390
  # Sybase ctlib does not (yet?) support the date type; use datetime instead.
391
+ # Oracle treats all dates/times as Time.
326
392
  assert_kind_of(
327
393
  Time, Topic.find(1).last_read,
328
394
  "The last_read attribute should be of the Time class"
@@ -353,6 +419,13 @@ class BasicsTest < Test::Unit::TestCase
353
419
  assert_equal 9900, Topic.find(2).written_on.usec
354
420
  end
355
421
  end
422
+
423
+ def test_custom_mutator
424
+ topic = Topic.find(1)
425
+ # This mutator is protected in the class definition
426
+ topic.send(:approved=, true)
427
+ assert topic.instance_variable_get("@custom_approved")
428
+ end
356
429
 
357
430
  def test_destroy
358
431
  topic = Topic.find(1)
@@ -494,17 +567,33 @@ class BasicsTest < Test::Unit::TestCase
494
567
  Topic.decrement_counter("replies_count", 2)
495
568
  assert_equal -2, Topic.find(2).replies_count
496
569
  end
497
-
498
- def test_update_all
499
- # The ADO library doesn't support the number of affected rows
500
- return true if current_adapter?(:SQLServerAdapter)
501
570
 
571
+ def test_update_all
502
572
  assert_equal 2, Topic.update_all("content = 'bulk updated!'")
503
573
  assert_equal "bulk updated!", Topic.find(1).content
504
574
  assert_equal "bulk updated!", Topic.find(2).content
575
+
505
576
  assert_equal 2, Topic.update_all(['content = ?', 'bulk updated again!'])
506
577
  assert_equal "bulk updated again!", Topic.find(1).content
507
578
  assert_equal "bulk updated again!", Topic.find(2).content
579
+
580
+ assert_equal 2, Topic.update_all(['content = ?', nil])
581
+ assert_nil Topic.find(1).content
582
+ end
583
+
584
+ def test_update_all_with_hash
585
+ assert_not_nil Topic.find(1).last_read
586
+ assert_equal 2, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil)
587
+ assert_equal "bulk updated with hash!", Topic.find(1).content
588
+ assert_equal "bulk updated with hash!", Topic.find(2).content
589
+ assert_nil Topic.find(1).last_read
590
+ assert_nil Topic.find(2).last_read
591
+ end
592
+
593
+ if current_adapter?(:MysqlAdapter)
594
+ def test_update_all_with_order_and_limit
595
+ assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC')
596
+ end
508
597
  end
509
598
 
510
599
  def test_update_many
@@ -517,9 +606,6 @@ class BasicsTest < Test::Unit::TestCase
517
606
  end
518
607
 
519
608
  def test_delete_all
520
- # The ADO library doesn't support the number of affected rows
521
- return true if current_adapter?(:SQLServerAdapter)
522
-
523
609
  assert_equal 2, Topic.delete_all
524
610
  end
525
611
 
@@ -703,6 +789,12 @@ class BasicsTest < Test::Unit::TestCase
703
789
  assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
704
790
  end
705
791
 
792
+ def test_mass_assignment_should_raise_exception_if_accessible_and_protected_attribute_writers_are_both_used
793
+ topic = TopicWithProtectedContentAndAccessibleAuthorName.new
794
+ assert_raises(RuntimeError) { topic.attributes = { "author_name" => "me" } }
795
+ assert_raises(RuntimeError) { topic.attributes = { "content" => "stuff" } }
796
+ end
797
+
706
798
  def test_mass_assignment_protection
707
799
  firm = Firm.new
708
800
  firm.attributes = { "name" => "Next Angle", "rating" => 5 }
@@ -711,7 +803,7 @@ class BasicsTest < Test::Unit::TestCase
711
803
 
712
804
  def test_mass_assignment_protection_against_class_attribute_writers
713
805
  [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :colorize_logging,
714
- :default_timezone, :allow_concurrency, :generate_read_methods, :schema_format, :verification_timeout, :lock_optimistically, :record_timestamps].each do |method|
806
+ :default_timezone, :allow_concurrency, :schema_format, :verification_timeout, :lock_optimistically, :record_timestamps].each do |method|
715
807
  assert Task.respond_to?(method)
716
808
  assert Task.respond_to?("#{method}=")
717
809
  assert Task.new.respond_to?(method)
@@ -727,7 +819,7 @@ class BasicsTest < Test::Unit::TestCase
727
819
  assert_nil keyboard.id
728
820
  end
729
821
 
730
- def test_customized_primary_key_remains_protected_when_refered_to_as_id
822
+ def test_customized_primary_key_remains_protected_when_referred_to_as_id
731
823
  subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try')
732
824
  assert_nil subscriber.id
733
825
 
@@ -756,16 +848,32 @@ class BasicsTest < Test::Unit::TestCase
756
848
 
757
849
  def test_mass_assignment_protection_inheritance
758
850
  assert_nil LoosePerson.accessible_attributes
759
- assert_equal [ :credit_rating, :administrator ], LoosePerson.protected_attributes
851
+ assert_equal Set.new([ 'credit_rating', 'administrator' ]), LoosePerson.protected_attributes
760
852
 
761
853
  assert_nil LooseDescendant.accessible_attributes
762
- assert_equal [ :credit_rating, :administrator, :phone_number ], LooseDescendant.protected_attributes
854
+ assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number' ]), LooseDescendant.protected_attributes
855
+
856
+ assert_nil LooseDescendantSecond.accessible_attributes
857
+ assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name' ]), LooseDescendantSecond.protected_attributes, 'Running attr_protected twice in one class should merge the protections'
763
858
 
764
859
  assert_nil TightPerson.protected_attributes
765
- assert_equal [ :name, :address ], TightPerson.accessible_attributes
860
+ assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes
766
861
 
767
862
  assert_nil TightDescendant.protected_attributes
768
- assert_equal [ :name, :address, :phone_number ], TightDescendant.accessible_attributes
863
+ assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes
864
+ end
865
+
866
+ def test_readonly_attributes
867
+ assert_equal Set.new([ 'title' ]), ReadonlyTitlePost.readonly_attributes
868
+
869
+ post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable")
870
+ post.reload
871
+ assert_equal "cannot change this", post.title
872
+
873
+ post.update_attributes(:title => "try to change", :body => "changed")
874
+ post.reload
875
+ assert_equal "cannot change this", post.title
876
+ assert_equal "changed", post.body
769
877
  end
770
878
 
771
879
  def test_multiparameter_attributes_on_date
@@ -885,6 +993,10 @@ class BasicsTest < Test::Unit::TestCase
885
993
  cloned_topic.title["a"] = "c"
886
994
  assert_equal "b", topic.title["a"]
887
995
 
996
+ #test if attributes set as part of after_initialize are cloned correctly
997
+ assert_equal topic.author_email_address, cloned_topic.author_email_address
998
+
999
+ # test if saved clone object differs from original
888
1000
  cloned_topic.save
889
1001
  assert !cloned_topic.new_record?
890
1002
  assert cloned_topic.id != topic.id
@@ -1149,12 +1261,12 @@ class BasicsTest < Test::Unit::TestCase
1149
1261
  end
1150
1262
 
1151
1263
  def test_increment_attribute
1152
- assert_equal 1, topics(:first).replies_count
1153
- topics(:first).increment! :replies_count
1154
- assert_equal 2, topics(:first, :reload).replies_count
1155
-
1156
- topics(:first).increment(:replies_count).increment!(:replies_count)
1157
- assert_equal 4, topics(:first, :reload).replies_count
1264
+ assert_equal 50, accounts(:signals37).credit_limit
1265
+ accounts(:signals37).increment! :credit_limit
1266
+ assert_equal 51, accounts(:signals37, :reload).credit_limit
1267
+
1268
+ accounts(:signals37).increment(:credit_limit).increment!(:credit_limit)
1269
+ assert_equal 53, accounts(:signals37, :reload).credit_limit
1158
1270
  end
1159
1271
 
1160
1272
  def test_increment_nil_attribute
@@ -1164,14 +1276,13 @@ class BasicsTest < Test::Unit::TestCase
1164
1276
  end
1165
1277
 
1166
1278
  def test_decrement_attribute
1167
- topics(:first).increment(:replies_count).increment!(:replies_count)
1168
- assert_equal 3, topics(:first).replies_count
1169
-
1170
- topics(:first).decrement!(:replies_count)
1171
- assert_equal 2, topics(:first, :reload).replies_count
1279
+ assert_equal 50, accounts(:signals37).credit_limit
1172
1280
 
1173
- topics(:first).decrement(:replies_count).decrement!(:replies_count)
1174
- assert_equal 0, topics(:first, :reload).replies_count
1281
+ accounts(:signals37).decrement!(:credit_limit)
1282
+ assert_equal 49, accounts(:signals37, :reload).credit_limit
1283
+
1284
+ accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit)
1285
+ assert_equal 47, accounts(:signals37, :reload).credit_limit
1175
1286
  end
1176
1287
 
1177
1288
  def test_toggle_attribute
@@ -1250,11 +1361,8 @@ class BasicsTest < Test::Unit::TestCase
1250
1361
 
1251
1362
  def test_count_with_join
1252
1363
  res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
1253
- res2 = nil
1254
- assert_deprecated 'count' do
1255
- res2 = Post.count("posts.#{QUOTED_TYPE} = 'Post'",
1256
- "LEFT JOIN comments ON posts.id=comments.post_id")
1257
- end
1364
+
1365
+ res2 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
1258
1366
  assert_equal res, res2
1259
1367
 
1260
1368
  res3 = nil
@@ -1274,15 +1382,17 @@ class BasicsTest < Test::Unit::TestCase
1274
1382
 
1275
1383
  assert_equal res4, res5
1276
1384
 
1277
- res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
1278
- res7 = nil
1279
- assert_nothing_raised do
1280
- res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
1281
- :joins => "p, comments co",
1282
- :select => "p.id",
1283
- :distinct => true)
1385
+ unless current_adapter?(:SQLite2Adapter, :DeprecatedSQLiteAdapter)
1386
+ res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
1387
+ res7 = nil
1388
+ assert_nothing_raised do
1389
+ res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
1390
+ :joins => "p, comments co",
1391
+ :select => "p.id",
1392
+ :distinct => true)
1393
+ end
1394
+ assert_equal res6, res7
1284
1395
  end
1285
- assert_equal res6, res7
1286
1396
  end
1287
1397
 
1288
1398
  def test_clear_association_cache_stored
@@ -1299,12 +1409,12 @@ class BasicsTest < Test::Unit::TestCase
1299
1409
  client_new = Client.new
1300
1410
  client_new.name = "The Joneses"
1301
1411
  clients = [ client_stored, client_new ]
1302
-
1412
+
1303
1413
  firm.clients << clients
1414
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
1304
1415
 
1305
1416
  firm.clear_association_cache
1306
-
1307
- assert_equal firm.clients.collect{ |x| x.name }.sort, clients.collect{ |x| x.name }.sort
1417
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
1308
1418
  end
1309
1419
 
1310
1420
  def test_interpolate_sql
@@ -1442,28 +1552,48 @@ class BasicsTest < Test::Unit::TestCase
1442
1552
  end
1443
1553
 
1444
1554
  def test_to_xml
1445
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true)
1555
+ xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
1446
1556
  bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
1447
1557
  written_on_in_current_timezone = topics(:first).written_on.xmlschema
1448
1558
  last_read_in_current_timezone = topics(:first).last_read.xmlschema
1449
- assert_equal "<topic>", xml.first(7)
1450
- assert xml.include?(%(<title>The First Topic</title>))
1451
- assert xml.include?(%(<author-name>David</author-name>))
1452
- assert xml.include?(%(<id type="integer">1</id>))
1453
- assert xml.include?(%(<replies-count type="integer">1</replies-count>))
1454
- assert xml.include?(%(<written-on type="datetime">#{written_on_in_current_timezone}</written-on>))
1455
- assert xml.include?(%(<content>Have a nice day</content>))
1456
- assert xml.include?(%(<author-email-address>david@loudthinking.com</author-email-address>))
1457
- assert xml.match(%(<parent-id type="integer"></parent-id>))
1559
+
1560
+ assert_equal "topic", xml.root.name
1561
+ assert_equal "The First Topic" , xml.elements["//title"].text
1562
+ assert_equal "David" , xml.elements["//author-name"].text
1563
+
1564
+ assert_equal "1", xml.elements["//id"].text
1565
+ assert_equal "integer" , xml.elements["//id"].attributes['type']
1566
+
1567
+ assert_equal "1", xml.elements["//replies-count"].text
1568
+ assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
1569
+
1570
+ assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
1571
+ assert_equal "datetime" , xml.elements["//written-on"].attributes['type']
1572
+
1573
+ assert_equal "--- Have a nice day\n" , xml.elements["//content"].text
1574
+ assert_equal "yaml" , xml.elements["//content"].attributes['type']
1575
+
1576
+ assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
1577
+
1578
+ assert_equal nil, xml.elements["//parent-id"].text
1579
+ assert_equal "integer", xml.elements["//parent-id"].attributes['type']
1580
+ assert_equal "true", xml.elements["//parent-id"].attributes['nil']
1581
+
1458
1582
  if current_adapter?(:SybaseAdapter, :SQLServerAdapter, :OracleAdapter)
1459
- assert xml.include?(%(<last-read type="datetime">#{last_read_in_current_timezone}</last-read>))
1583
+ assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
1584
+ assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
1460
1585
  else
1461
- assert xml.include?(%(<last-read type="date">2004-04-15</last-read>))
1586
+ assert_equal "2004-04-15", xml.elements["//last-read"].text
1587
+ assert_equal "date" , xml.elements["//last-read"].attributes['type']
1462
1588
  end
1589
+
1463
1590
  # Oracle and DB2 don't have true boolean or time-only fields
1464
1591
  unless current_adapter?(:OracleAdapter, :DB2Adapter)
1465
- assert xml.include?(%(<approved type="boolean">false</approved>)), "Approved should be a boolean"
1466
- assert xml.include?(%(<bonus-time type="datetime">#{bonus_time_in_current_timezone}</bonus-time>))
1592
+ assert_equal "false", xml.elements["//approved"].text
1593
+ assert_equal "boolean" , xml.elements["//approved"].attributes['type']
1594
+
1595
+ assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
1596
+ assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type']
1467
1597
  end
1468
1598
  end
1469
1599
 
@@ -1481,13 +1611,13 @@ class BasicsTest < Test::Unit::TestCase
1481
1611
  def test_to_xml_including_has_many_association
1482
1612
  xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
1483
1613
  assert_equal "<topic>", xml.first(7)
1484
- assert xml.include?(%(<replies><reply>))
1614
+ assert xml.include?(%(<replies type="array"><reply>))
1485
1615
  assert xml.include?(%(<title>The Second Topic's of the day</title>))
1486
1616
  end
1487
1617
 
1488
1618
  def test_array_to_xml_including_has_many_association
1489
1619
  xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
1490
- assert xml.include?(%(<replies><reply>))
1620
+ assert xml.include?(%(<replies type="array"><reply>))
1491
1621
  end
1492
1622
 
1493
1623
  def test_array_to_xml_including_methods
@@ -1521,7 +1651,7 @@ class BasicsTest < Test::Unit::TestCase
1521
1651
  xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
1522
1652
  assert_equal "<firm>", xml.first(6)
1523
1653
  assert xml.include?(%(<account>))
1524
- assert xml.include?(%(<clients><client>))
1654
+ assert xml.include?(%(<clients type="array"><client>))
1525
1655
  end
1526
1656
 
1527
1657
  def test_to_xml_including_multiple_associations_with_options
@@ -1532,7 +1662,7 @@ class BasicsTest < Test::Unit::TestCase
1532
1662
 
1533
1663
  assert_equal "<firm>", xml.first(6)
1534
1664
  assert xml.include?(%(<client><name>Summit</name></client>))
1535
- assert xml.include?(%(<clients><client>))
1665
+ assert xml.include?(%(<clients type="array"><client>))
1536
1666
  end
1537
1667
 
1538
1668
  def test_to_xml_including_methods
@@ -1541,6 +1671,15 @@ class BasicsTest < Test::Unit::TestCase
1541
1671
  assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>))
1542
1672
  end
1543
1673
 
1674
+ def test_to_xml_with_block
1675
+ value = "Rockin' the block"
1676
+ xml = Company.new.to_xml(:skip_instruct => true) do |xml|
1677
+ xml.tag! "arbitrary-element", value
1678
+ end
1679
+ assert_equal "<company>", xml.first(9)
1680
+ assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>))
1681
+ end
1682
+
1544
1683
  def test_except_attributes
1545
1684
  assert_equal(
1546
1685
  %w( author_name type id approved replies_count bonus_time written_on content author_email_address parent_id last_read),
@@ -1566,34 +1705,41 @@ class BasicsTest < Test::Unit::TestCase
1566
1705
  def test_to_param_should_return_string
1567
1706
  assert_kind_of String, Client.find(:first).to_param
1568
1707
  end
1708
+
1709
+ def test_inspect_class
1710
+ assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect
1711
+ assert_equal 'LoosePerson(abstract)', LoosePerson.inspect
1712
+ assert_match(/^Topic\(id: integer, title: string/, Topic.inspect)
1713
+ end
1714
+
1715
+ def test_inspect_instance
1716
+ topic = topics(:first)
1717
+ assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, type: nil>), topic.inspect
1718
+ end
1719
+
1720
+ def test_inspect_new_instance
1721
+ assert_match /Topic id: nil/, Topic.new.inspect
1722
+ end
1569
1723
 
1570
- # FIXME: this test ought to run, but it needs to run sandboxed so that it
1571
- # doesn't b0rk the current test environment by undefing everything.
1572
- #
1573
- #def test_dev_mode_memory_leak
1574
- # counts = []
1575
- # 2.times do
1576
- # require_dependency 'fixtures/company'
1577
- # Firm.find(:first)
1578
- # Dependencies.clear
1579
- # ActiveRecord::Base.reset_subclasses
1580
- # Dependencies.remove_subclasses_for(ActiveRecord::Base)
1581
- #
1582
- # GC.start
1583
- #
1584
- # count = 0
1585
- # ObjectSpace.each_object(Proc) { count += 1 }
1586
- # counts << count
1587
- # end
1588
- # assert counts.last <= counts.first,
1589
- # "expected last count (#{counts.last}) to be <= first count (#{counts.first})"
1590
- #end
1724
+ def test_inspect_limited_select_instance
1725
+ assert_equal %(#<Topic id: 1>), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect
1726
+ assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect
1727
+ end
1591
1728
 
1592
- private
1593
- def assert_readers(model, exceptions)
1594
- expected_readers = Set.new(model.column_names - ['id'])
1595
- expected_readers += expected_readers.map { |col| "#{col}?" }
1596
- expected_readers -= exceptions
1597
- assert_equal expected_readers, model.read_methods
1598
- end
1729
+ def test_inspect_class_without_table
1730
+ assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
1731
+ end
1732
+
1733
+ def test_attribute_for_inspect
1734
+ t = topics(:first)
1735
+ t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
1736
+
1737
+ assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
1738
+ assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title)
1739
+ end
1740
+
1741
+ def test_becomes
1742
+ assert_kind_of Reply, topics(:first).becomes(Reply)
1743
+ assert_equal "The First Topic", topics(:first).becomes(Reply).title
1744
+ end
1599
1745
  end