activerecord 1.0.0 → 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 (311) hide show
  1. data/CHANGELOG +4928 -3
  2. data/README +45 -46
  3. data/RUNNING_UNIT_TESTS +8 -11
  4. data/Rakefile +247 -0
  5. data/install.rb +8 -38
  6. data/lib/active_record/aggregations.rb +64 -49
  7. data/lib/active_record/associations/association_collection.rb +217 -47
  8. data/lib/active_record/associations/association_proxy.rb +159 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
  12. data/lib/active_record/associations/has_many_association.rb +145 -75
  13. data/lib/active_record/associations/has_many_through_association.rb +283 -0
  14. data/lib/active_record/associations/has_one_association.rb +96 -0
  15. data/lib/active_record/associations.rb +1537 -304
  16. data/lib/active_record/attribute_methods.rb +328 -0
  17. data/lib/active_record/base.rb +2001 -588
  18. data/lib/active_record/calculations.rb +269 -0
  19. data/lib/active_record/callbacks.rb +169 -165
  20. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
  22. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  23. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
  31. data/lib/active_record/fixtures.rb +946 -100
  32. data/lib/active_record/locking/optimistic.rb +144 -0
  33. data/lib/active_record/locking/pessimistic.rb +77 -0
  34. data/lib/active_record/migration.rb +417 -0
  35. data/lib/active_record/observer.rb +142 -32
  36. data/lib/active_record/query_cache.rb +23 -0
  37. data/lib/active_record/reflection.rb +163 -70
  38. data/lib/active_record/schema.rb +58 -0
  39. data/lib/active_record/schema_dumper.rb +171 -0
  40. data/lib/active_record/serialization.rb +98 -0
  41. data/lib/active_record/serializers/json_serializer.rb +71 -0
  42. data/lib/active_record/serializers/xml_serializer.rb +315 -0
  43. data/lib/active_record/timestamp.rb +41 -0
  44. data/lib/active_record/transactions.rb +87 -57
  45. data/lib/active_record/validations.rb +909 -122
  46. data/lib/active_record/vendor/db2.rb +362 -0
  47. data/lib/active_record/vendor/mysql.rb +126 -29
  48. data/lib/active_record/version.rb +9 -0
  49. data/lib/active_record.rb +35 -7
  50. data/lib/activerecord.rb +1 -0
  51. data/test/aaa_create_tables_test.rb +72 -0
  52. data/test/abstract_unit.rb +73 -5
  53. data/test/active_schema_test_mysql.rb +43 -0
  54. data/test/adapter_test.rb +105 -0
  55. data/test/adapter_test_sqlserver.rb +95 -0
  56. data/test/aggregations_test.rb +110 -16
  57. data/test/all.sh +2 -2
  58. data/test/ar_schema_test.rb +33 -0
  59. data/test/association_inheritance_reload.rb +14 -0
  60. data/test/associations/ar_joins_test.rb +0 -0
  61. data/test/associations/callbacks_test.rb +147 -0
  62. data/test/associations/cascaded_eager_loading_test.rb +110 -0
  63. data/test/associations/eager_singularization_test.rb +145 -0
  64. data/test/associations/eager_test.rb +442 -0
  65. data/test/associations/extension_test.rb +47 -0
  66. data/test/associations/inner_join_association_test.rb +88 -0
  67. data/test/associations/join_model_test.rb +553 -0
  68. data/test/associations_test.rb +1930 -267
  69. data/test/attribute_methods_test.rb +146 -0
  70. data/test/base_test.rb +1316 -84
  71. data/test/binary_test.rb +32 -0
  72. data/test/calculations_test.rb +251 -0
  73. data/test/callbacks_test.rb +400 -0
  74. data/test/class_inheritable_attributes_test.rb +3 -4
  75. data/test/column_alias_test.rb +17 -0
  76. data/test/connection_test_firebird.rb +8 -0
  77. data/test/connection_test_mysql.rb +30 -0
  78. data/test/connections/native_db2/connection.rb +25 -0
  79. data/test/connections/native_firebird/connection.rb +26 -0
  80. data/test/connections/native_frontbase/connection.rb +27 -0
  81. data/test/connections/native_mysql/connection.rb +21 -18
  82. data/test/connections/native_openbase/connection.rb +21 -0
  83. data/test/connections/native_oracle/connection.rb +27 -0
  84. data/test/connections/native_postgresql/connection.rb +17 -18
  85. data/test/connections/native_sqlite/connection.rb +17 -16
  86. data/test/connections/native_sqlite3/connection.rb +25 -0
  87. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  88. data/test/connections/native_sybase/connection.rb +23 -0
  89. data/test/copy_table_test_sqlite.rb +69 -0
  90. data/test/datatype_test_postgresql.rb +203 -0
  91. data/test/date_time_test.rb +37 -0
  92. data/test/default_test_firebird.rb +16 -0
  93. data/test/defaults_test.rb +67 -0
  94. data/test/deprecated_finder_test.rb +30 -0
  95. data/test/finder_test.rb +607 -32
  96. data/test/fixtures/accounts.yml +28 -0
  97. data/test/fixtures/all/developers.yml +0 -0
  98. data/test/fixtures/all/people.csv +0 -0
  99. data/test/fixtures/all/tasks.yml +0 -0
  100. data/test/fixtures/author.rb +107 -0
  101. data/test/fixtures/author_favorites.yml +4 -0
  102. data/test/fixtures/authors.yml +7 -0
  103. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  104. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  105. data/test/fixtures/bad_fixtures/blank_line +3 -0
  106. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  107. data/test/fixtures/bad_fixtures/missing_value +1 -0
  108. data/test/fixtures/binaries.yml +132 -0
  109. data/test/fixtures/binary.rb +2 -0
  110. data/test/fixtures/book.rb +4 -0
  111. data/test/fixtures/books.yml +7 -0
  112. data/test/fixtures/categories/special_categories.yml +9 -0
  113. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  114. data/test/fixtures/categories.yml +14 -0
  115. data/test/fixtures/categories_ordered.yml +7 -0
  116. data/test/fixtures/categories_posts.yml +23 -0
  117. data/test/fixtures/categorization.rb +5 -0
  118. data/test/fixtures/categorizations.yml +17 -0
  119. data/test/fixtures/category.rb +26 -0
  120. data/test/fixtures/citation.rb +6 -0
  121. data/test/fixtures/comment.rb +23 -0
  122. data/test/fixtures/comments.yml +59 -0
  123. data/test/fixtures/companies.yml +55 -0
  124. data/test/fixtures/company.rb +81 -4
  125. data/test/fixtures/company_in_module.rb +32 -6
  126. data/test/fixtures/computer.rb +4 -0
  127. data/test/fixtures/computers.yml +4 -0
  128. data/test/fixtures/contact.rb +16 -0
  129. data/test/fixtures/courses.yml +7 -0
  130. data/test/fixtures/customer.rb +28 -3
  131. data/test/fixtures/customers.yml +17 -0
  132. data/test/fixtures/db_definitions/db2.drop.sql +33 -0
  133. data/test/fixtures/db_definitions/db2.sql +235 -0
  134. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  135. data/test/fixtures/db_definitions/db22.sql +5 -0
  136. data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
  137. data/test/fixtures/db_definitions/firebird.sql +310 -0
  138. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  139. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  140. data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
  141. data/test/fixtures/db_definitions/frontbase.sql +273 -0
  142. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  143. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  144. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  145. data/test/fixtures/db_definitions/openbase.sql +318 -0
  146. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  148. data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
  149. data/test/fixtures/db_definitions/oracle.sql +330 -0
  150. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  151. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  152. data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
  153. data/test/fixtures/db_definitions/postgresql.sql +217 -38
  154. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/postgresql2.sql +2 -2
  156. data/test/fixtures/db_definitions/schema.rb +354 -0
  157. data/test/fixtures/db_definitions/schema2.rb +11 -0
  158. data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
  159. data/test/fixtures/db_definitions/sqlite.sql +139 -5
  160. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  161. data/test/fixtures/db_definitions/sqlite2.sql +1 -0
  162. data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
  163. data/test/fixtures/db_definitions/sybase.sql +222 -0
  164. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  165. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  166. data/test/fixtures/developer.rb +70 -6
  167. data/test/fixtures/developers.yml +21 -0
  168. data/test/fixtures/developers_projects/david_action_controller +2 -1
  169. data/test/fixtures/developers_projects/david_active_record +2 -1
  170. data/test/fixtures/developers_projects.yml +17 -0
  171. data/test/fixtures/edge.rb +5 -0
  172. data/test/fixtures/edges.yml +6 -0
  173. data/test/fixtures/entrants.yml +14 -0
  174. data/test/fixtures/example.log +1 -0
  175. data/test/fixtures/fk_test_has_fk.yml +3 -0
  176. data/test/fixtures/fk_test_has_pk.yml +2 -0
  177. data/test/fixtures/flowers.jpg +0 -0
  178. data/test/fixtures/funny_jokes.yml +10 -0
  179. data/test/fixtures/item.rb +7 -0
  180. data/test/fixtures/items.yml +4 -0
  181. data/test/fixtures/joke.rb +3 -0
  182. data/test/fixtures/keyboard.rb +3 -0
  183. data/test/fixtures/legacy_thing.rb +3 -0
  184. data/test/fixtures/legacy_things.yml +3 -0
  185. data/test/fixtures/matey.rb +4 -0
  186. data/test/fixtures/mateys.yml +4 -0
  187. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  188. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  189. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  190. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  191. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  192. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  193. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  194. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  195. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  196. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  197. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  198. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  199. data/test/fixtures/minimalistic.rb +2 -0
  200. data/test/fixtures/minimalistics.yml +2 -0
  201. data/test/fixtures/mixed_case_monkey.rb +3 -0
  202. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  203. data/test/fixtures/mixins.yml +29 -0
  204. data/test/fixtures/movies.yml +7 -0
  205. data/test/fixtures/naked/csv/accounts.csv +1 -0
  206. data/test/fixtures/naked/yml/accounts.yml +1 -0
  207. data/test/fixtures/naked/yml/companies.yml +1 -0
  208. data/test/fixtures/naked/yml/courses.yml +1 -0
  209. data/test/fixtures/order.rb +4 -0
  210. data/test/fixtures/parrot.rb +13 -0
  211. data/test/fixtures/parrots.yml +27 -0
  212. data/test/fixtures/parrots_pirates.yml +7 -0
  213. data/test/fixtures/people.yml +3 -0
  214. data/test/fixtures/person.rb +4 -0
  215. data/test/fixtures/pirate.rb +5 -0
  216. data/test/fixtures/pirates.yml +9 -0
  217. data/test/fixtures/post.rb +59 -0
  218. data/test/fixtures/posts.yml +48 -0
  219. data/test/fixtures/project.rb +27 -2
  220. data/test/fixtures/projects.yml +7 -0
  221. data/test/fixtures/reader.rb +4 -0
  222. data/test/fixtures/readers.yml +4 -0
  223. data/test/fixtures/reply.rb +18 -2
  224. data/test/fixtures/reserved_words/distinct.yml +5 -0
  225. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  226. data/test/fixtures/reserved_words/group.yml +14 -0
  227. data/test/fixtures/reserved_words/select.yml +8 -0
  228. data/test/fixtures/reserved_words/values.yml +7 -0
  229. data/test/fixtures/ship.rb +3 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/subject.rb +4 -0
  232. data/test/fixtures/subscriber.rb +4 -3
  233. data/test/fixtures/tag.rb +7 -0
  234. data/test/fixtures/tagging.rb +10 -0
  235. data/test/fixtures/taggings.yml +25 -0
  236. data/test/fixtures/tags.yml +7 -0
  237. data/test/fixtures/task.rb +3 -0
  238. data/test/fixtures/tasks.yml +7 -0
  239. data/test/fixtures/topic.rb +20 -3
  240. data/test/fixtures/topics.yml +22 -0
  241. data/test/fixtures/treasure.rb +4 -0
  242. data/test/fixtures/treasures.yml +10 -0
  243. data/test/fixtures/vertex.rb +9 -0
  244. data/test/fixtures/vertices.yml +4 -0
  245. data/test/fixtures_test.rb +574 -8
  246. data/test/inheritance_test.rb +113 -27
  247. data/test/json_serialization_test.rb +180 -0
  248. data/test/lifecycle_test.rb +56 -29
  249. data/test/locking_test.rb +273 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +933 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_test.rb +95 -0
  254. data/test/modules_test.rb +23 -10
  255. data/test/multiple_db_test.rb +17 -3
  256. data/test/pk_test.rb +59 -15
  257. data/test/query_cache_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +124 -27
  260. data/test/reserved_word_test_mysql.rb +177 -0
  261. data/test/schema_authorization_test_postgresql.rb +75 -0
  262. data/test/schema_dumper_test.rb +131 -0
  263. data/test/schema_test_postgresql.rb +64 -0
  264. data/test/serialization_test.rb +47 -0
  265. data/test/synonym_test_oracle.rb +17 -0
  266. data/test/table_name_test_sqlserver.rb +23 -0
  267. data/test/threaded_connections_test.rb +48 -0
  268. data/test/transactions_test.rb +227 -29
  269. data/test/unconnected_test.rb +14 -6
  270. data/test/validations_test.rb +1293 -32
  271. data/test/xml_serialization_test.rb +202 -0
  272. metadata +347 -143
  273. data/dev-utils/eval_debugger.rb +0 -9
  274. data/examples/associations.rb +0 -87
  275. data/examples/shared_setup.rb +0 -15
  276. data/examples/validation.rb +0 -88
  277. data/lib/active_record/deprecated_associations.rb +0 -70
  278. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  279. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  280. data/lib/active_record/support/clean_logger.rb +0 -10
  281. data/lib/active_record/support/inflector.rb +0 -70
  282. data/lib/active_record/vendor/simple.rb +0 -702
  283. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  284. data/lib/active_record/wrappings.rb +0 -59
  285. data/rakefile +0 -122
  286. data/test/deprecated_associations_test.rb +0 -336
  287. data/test/fixtures/accounts/signals37 +0 -3
  288. data/test/fixtures/accounts/unknown +0 -2
  289. data/test/fixtures/companies/first_client +0 -6
  290. data/test/fixtures/companies/first_firm +0 -4
  291. data/test/fixtures/companies/second_client +0 -6
  292. data/test/fixtures/courses/java +0 -2
  293. data/test/fixtures/courses/ruby +0 -2
  294. data/test/fixtures/customers/david +0 -6
  295. data/test/fixtures/db_definitions/mysql.sql +0 -96
  296. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  297. data/test/fixtures/developers/david +0 -2
  298. data/test/fixtures/developers/jamis +0 -2
  299. data/test/fixtures/entrants/first +0 -3
  300. data/test/fixtures/entrants/second +0 -3
  301. data/test/fixtures/entrants/third +0 -3
  302. data/test/fixtures/fixture_database.sqlite +0 -0
  303. data/test/fixtures/fixture_database_2.sqlite +0 -0
  304. data/test/fixtures/movies/first +0 -2
  305. data/test/fixtures/movies/second +0 -2
  306. data/test/fixtures/projects/action_controller +0 -2
  307. data/test/fixtures/projects/active_record +0 -2
  308. data/test/fixtures/topics/first +0 -9
  309. data/test/fixtures/topics/second +0 -8
  310. data/test/inflector_test.rb +0 -104
  311. data/test/thread_safety_test.rb +0 -33
data/test/base_test.rb CHANGED
@@ -2,46 +2,94 @@ require 'abstract_unit'
2
2
  require 'fixtures/topic'
3
3
  require 'fixtures/reply'
4
4
  require 'fixtures/company'
5
+ require 'fixtures/customer'
6
+ require 'fixtures/developer'
7
+ require 'fixtures/project'
5
8
  require 'fixtures/default'
6
9
  require 'fixtures/auto_id'
7
10
  require 'fixtures/column_name'
11
+ require 'fixtures/subscriber'
12
+ require 'fixtures/keyboard'
13
+ require 'fixtures/post'
14
+ require 'fixtures/minimalistic'
15
+ require 'rexml/document'
8
16
 
9
17
  class Category < ActiveRecord::Base; end
10
18
  class Smarts < ActiveRecord::Base; end
11
- class CreditCard < ActiveRecord::Base; end
19
+ class CreditCard < ActiveRecord::Base
20
+ class PinNumber < ActiveRecord::Base
21
+ class CvvCode < ActiveRecord::Base; end
22
+ class SubCvvCode < CvvCode; end
23
+ end
24
+ class SubPinNumber < PinNumber; end
25
+ class Brand < Category; end
26
+ end
12
27
  class MasterCreditCard < ActiveRecord::Base; end
28
+ class Post < ActiveRecord::Base; end
29
+ class Computer < ActiveRecord::Base; end
30
+ class NonExistentTable < ActiveRecord::Base; end
31
+ class TestOracleDefault < ActiveRecord::Base; end
13
32
 
14
33
  class LoosePerson < ActiveRecord::Base
34
+ self.table_name = 'people'
35
+ self.abstract_class = true
15
36
  attr_protected :credit_rating, :administrator
16
37
  end
17
38
 
39
+ class LooseDescendant < LoosePerson
40
+ attr_protected :phone_number
41
+ end
42
+
43
+ class LooseDescendantSecond< LoosePerson
44
+ attr_protected :phone_number
45
+ attr_protected :name
46
+ end
47
+
18
48
  class TightPerson < ActiveRecord::Base
49
+ self.table_name = 'people'
19
50
  attr_accessible :name, :address
20
51
  end
21
52
 
22
- class TightDescendent < TightPerson
53
+ class TightDescendant < TightPerson
23
54
  attr_accessible :phone_number
24
55
  end
25
56
 
57
+ class ReadonlyTitlePost < Post
58
+ attr_readonly :title
59
+ end
60
+
26
61
  class Booleantest < ActiveRecord::Base; end
27
62
 
63
+ class Task < ActiveRecord::Base
64
+ attr_protected :starting
65
+ end
66
+
67
+ class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base
68
+ self.table_name = 'topics'
69
+ attr_accessible :author_name
70
+ attr_protected :content
71
+ end
72
+
28
73
  class BasicsTest < Test::Unit::TestCase
29
- def setup
30
- @topic_fixtures, @companies = create_fixtures "topics", "companies"
31
- end
74
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics
32
75
 
76
+ def test_table_exists
77
+ assert !NonExistentTable.table_exists?
78
+ assert Topic.table_exists?
79
+ end
80
+
33
81
  def test_set_attributes
34
82
  topic = Topic.find(1)
35
83
  topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
36
84
  topic.save
37
85
  assert_equal("Budget", topic.title)
38
86
  assert_equal("Jason", topic.author_name)
39
- assert_equal(@topic_fixtures["first"]["author_email_address"], Topic.find(1).author_email_address)
87
+ assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
40
88
  end
41
-
89
+
42
90
  def test_integers_as_nil
43
- Topic.update(1, "approved" => "")
44
- assert_nil Topic.find(1).approved
91
+ test = AutoId.create('value' => '')
92
+ assert_nil AutoId.find(test.id).value
45
93
  end
46
94
 
47
95
  def test_set_attributes_with_block
@@ -59,9 +107,13 @@ class BasicsTest < Test::Unit::TestCase
59
107
  assert topic.respond_to?("title")
60
108
  assert topic.respond_to?("title?")
61
109
  assert topic.respond_to?("title=")
110
+ assert topic.respond_to?(:title)
111
+ assert topic.respond_to?(:title?)
112
+ assert topic.respond_to?(:title=)
62
113
  assert topic.respond_to?("author_name")
63
114
  assert topic.respond_to?("attribute_names")
64
115
  assert !topic.respond_to?("nothingness")
116
+ assert !topic.respond_to?(:nothingness)
65
117
  end
66
118
 
67
119
  def test_array_content
@@ -98,16 +150,80 @@ class BasicsTest < Test::Unit::TestCase
98
150
  topic.content << "five"
99
151
  assert_equal(%w( one two three four five ), topic.content)
100
152
  end
101
-
153
+
154
+ def test_case_sensitive_attributes_hash
155
+ # DB2 is not case-sensitive
156
+ return true if current_adapter?(:DB2Adapter)
157
+
158
+ assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
159
+ end
160
+
102
161
  def test_create
103
162
  topic = Topic.new
104
163
  topic.title = "New Topic"
105
164
  topic.save
106
- id = topic.id
107
- topicReloaded = Topic.find(id)
108
- assert_equal("New Topic", topicReloaded.title)
165
+ topic_reloaded = Topic.find(topic.id)
166
+ assert_equal("New Topic", topic_reloaded.title)
109
167
  end
110
168
 
169
+ def test_save!
170
+ topic = Topic.new(:title => "New Topic")
171
+ assert topic.save!
172
+
173
+ reply = Reply.new
174
+ assert_raise(ActiveRecord::RecordInvalid) { reply.save! }
175
+ end
176
+
177
+ def test_save_null_string_attributes
178
+ topic = Topic.find(1)
179
+ topic.attributes = { "title" => "null", "author_name" => "null" }
180
+ topic.save!
181
+ topic.reload
182
+ assert_equal("null", topic.title)
183
+ assert_equal("null", topic.author_name)
184
+ end
185
+
186
+ def test_save_nil_string_attributes
187
+ topic = Topic.find(1)
188
+ topic.title = nil
189
+ topic.save!
190
+ topic.reload
191
+ assert_nil topic.title
192
+ end
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
+
203
+ def test_hashes_not_mangled
204
+ new_topic = { :title => "New Topic" }
205
+ new_topic_values = { :title => "AnotherTopic" }
206
+
207
+ topic = Topic.new(new_topic)
208
+ assert_equal new_topic[:title], topic.title
209
+
210
+ topic.attributes= new_topic_values
211
+ assert_equal new_topic_values[:title], topic.title
212
+ end
213
+
214
+ def test_create_many
215
+ topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
216
+ assert_equal 2, topics.size
217
+ assert_equal "first", topics.first.title
218
+ end
219
+
220
+ def test_create_columns_not_equal_attributes
221
+ topic = Topic.new
222
+ topic.title = 'Another New Topic'
223
+ topic.send :write_attribute, 'does_not_exist', 'test'
224
+ assert_nothing_raised { topic.save }
225
+ end
226
+
111
227
  def test_create_through_factory
112
228
  topic = Topic.create("title" => "New Topic")
113
229
  topicReloaded = Topic.find(topic.id)
@@ -117,47 +233,209 @@ class BasicsTest < Test::Unit::TestCase
117
233
  def test_update
118
234
  topic = Topic.new
119
235
  topic.title = "Another New Topic"
120
- topic.written_on = "2003-12-12 23:23"
236
+ topic.written_on = "2003-12-12 23:23:00"
121
237
  topic.save
122
- id = topic.id
123
- assert_equal(id, topic.id)
124
-
125
- topicReloaded = Topic.find(id)
238
+ topicReloaded = Topic.find(topic.id)
126
239
  assert_equal("Another New Topic", topicReloaded.title)
127
240
 
128
241
  topicReloaded.title = "Updated topic"
129
242
  topicReloaded.save
130
243
 
131
- topicReloadedAgain = Topic.find(id)
244
+ topicReloadedAgain = Topic.find(topic.id)
132
245
 
133
246
  assert_equal("Updated topic", topicReloadedAgain.title)
134
247
  end
135
248
 
136
- def test_preserving_objects
249
+ def test_update_columns_not_equal_attributes
250
+ topic = Topic.new
251
+ topic.title = "Still another topic"
252
+ topic.save
253
+
254
+ topicReloaded = Topic.find(topic.id)
255
+ topicReloaded.title = "A New Topic"
256
+ topicReloaded.send :write_attribute, 'does_not_exist', 'test'
257
+ assert_nothing_raised { topicReloaded.save }
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
264
+
265
+ def test_write_attribute
266
+ topic = Topic.new
267
+ topic.send(:write_attribute, :title, "Still another topic")
268
+ assert_equal "Still another topic", topic.title
269
+
270
+ topic.send(:write_attribute, "title", "Still another topic: part 2")
271
+ assert_equal "Still another topic: part 2", topic.title
272
+ end
273
+
274
+ def test_read_attribute
275
+ topic = Topic.new
276
+ topic.title = "Don't change the topic"
277
+ assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
278
+ assert_equal "Don't change the topic", topic["title"]
279
+
280
+ assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
281
+ assert_equal "Don't change the topic", topic[:title]
282
+ end
283
+
284
+ def test_read_attribute_when_false
285
+ topic = topics(:first)
286
+ topic.approved = false
287
+ assert !topic.approved?, "approved should be false"
288
+ topic.approved = "false"
289
+ assert !topic.approved?, "approved should be false"
290
+ end
291
+
292
+ def test_read_attribute_when_true
293
+ topic = topics(:first)
294
+ topic.approved = true
295
+ assert topic.approved?, "approved should be true"
296
+ topic.approved = "true"
297
+ assert topic.approved?, "approved should be true"
298
+ end
299
+
300
+ def test_read_write_boolean_attribute
301
+ topic = Topic.new
302
+ # puts ""
303
+ # puts "New Topic"
304
+ # puts topic.inspect
305
+ topic.approved = "false"
306
+ # puts "Expecting false"
307
+ # puts topic.inspect
308
+ assert !topic.approved?, "approved should be false"
309
+ topic.approved = "false"
310
+ # puts "Expecting false"
311
+ # puts topic.inspect
312
+ assert !topic.approved?, "approved should be false"
313
+ topic.approved = "true"
314
+ # puts "Expecting true"
315
+ # puts topic.inspect
316
+ assert topic.approved?, "approved should be true"
317
+ topic.approved = "true"
318
+ # puts "Expecting true"
319
+ # puts topic.inspect
320
+ assert topic.approved?, "approved should be true"
321
+ # puts ""
322
+ end
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?
335
+ end
336
+
337
+ assert_equal true, Developer.new(:salary => 1).salary?
338
+ assert_equal true, Developer.new(:salary => "1").salary?
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
+
372
+
373
+ def test_reader_for_invalid_column_names
374
+ Topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil)
375
+ assert !Topic.generated_methods.include?("mumub-jumbo")
376
+ end
377
+
378
+ def test_non_attribute_access_and_assignment
379
+ topic = Topic.new
380
+ assert !topic.respond_to?("mumbo")
381
+ assert_raises(NoMethodError) { topic.mumbo }
382
+ assert_raises(NoMethodError) { topic.mumbo = 5 }
383
+ end
384
+
385
+ def test_preserving_date_objects
386
+ # SQL Server doesn't have a separate column type just for dates, so all are returned as time
387
+ return true if current_adapter?(:SQLServerAdapter)
388
+
389
+ if current_adapter?(:SybaseAdapter, :OracleAdapter)
390
+ # Sybase ctlib does not (yet?) support the date type; use datetime instead.
391
+ # Oracle treats all dates/times as Time.
392
+ assert_kind_of(
393
+ Time, Topic.find(1).last_read,
394
+ "The last_read attribute should be of the Time class"
395
+ )
396
+ else
397
+ assert_kind_of(
398
+ Date, Topic.find(1).last_read,
399
+ "The last_read attribute should be of the Date class"
400
+ )
401
+ end
402
+ end
403
+
404
+ def test_preserving_time_objects
137
405
  assert_kind_of(
138
- Time, Topic.find(1).written_on,
139
- "The written_on attribute should be of the Time class"
406
+ Time, Topic.find(1).bonus_time,
407
+ "The bonus_time attribute should be of the Time class"
140
408
  )
141
409
 
142
410
  assert_kind_of(
143
- Date, Topic.find(1).last_read,
144
- "The last_read attribute should be of the Date class"
411
+ Time, Topic.find(1).written_on,
412
+ "The written_on attribute should be of the Time class"
145
413
  )
414
+
415
+ # For adapters which support microsecond resolution.
416
+ if current_adapter?(:PostgreSQLAdapter)
417
+ assert_equal 11, Topic.find(1).written_on.sec
418
+ assert_equal 223300, Topic.find(1).written_on.usec
419
+ assert_equal 9900, Topic.find(2).written_on.usec
420
+ end
146
421
  end
147
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
429
+
148
430
  def test_destroy
149
- topic = Topic.new
150
- topic.title = "Yet Another New Topic"
151
- topic.written_on = "2003-12-12 23:23"
152
- topic.save
153
- id = topic.id
154
- topic.destroy
155
-
156
- assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(id) }
431
+ topic = Topic.find(1)
432
+ assert_equal topic, topic.destroy, 'topic.destroy did not return self'
433
+ assert topic.frozen?, 'topic not frozen after destroy'
434
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
157
435
  end
158
-
436
+
159
437
  def test_record_not_found_exception
160
- assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(id) }
438
+ assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
161
439
  end
162
440
 
163
441
  def test_initialize_with_attributes
@@ -168,62 +446,107 @@ class BasicsTest < Test::Unit::TestCase
168
446
  assert_equal("initialized from attributes", topic.title)
169
447
  end
170
448
 
449
+ def test_initialize_with_invalid_attribute
450
+ begin
451
+ topic = Topic.new({ "title" => "test",
452
+ "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
453
+ rescue ActiveRecord::MultiparameterAssignmentErrors => ex
454
+ assert_equal(1, ex.errors.size)
455
+ assert_equal("last_read", ex.errors[0].attribute)
456
+ end
457
+ end
458
+
171
459
  def test_load
172
- topics = Topic.find_all nil, "id"
460
+ topics = Topic.find(:all, :order => 'id')
173
461
  assert_equal(2, topics.size)
174
- assert_equal(@topic_fixtures["first"]["title"], topics.first.title)
462
+ assert_equal(topics(:first).title, topics.first.title)
175
463
  end
176
464
 
177
465
  def test_load_with_condition
178
- topics = Topic.find_all "author_name = 'Mary'"
466
+ topics = Topic.find(:all, :conditions => "author_name = 'Mary'")
179
467
 
180
468
  assert_equal(1, topics.size)
181
- assert_equal(@topic_fixtures["second"]["title"], topics.first.title)
469
+ assert_equal(topics(:second).title, topics.first.title)
182
470
  end
183
471
 
184
472
  def test_table_name_guesses
473
+ classes = [Category, Smarts, CreditCard, CreditCard::PinNumber, CreditCard::PinNumber::CvvCode, CreditCard::SubPinNumber, CreditCard::Brand, MasterCreditCard]
474
+
185
475
  assert_equal "topics", Topic.table_name
186
-
476
+
187
477
  assert_equal "categories", Category.table_name
188
478
  assert_equal "smarts", Smarts.table_name
189
479
  assert_equal "credit_cards", CreditCard.table_name
480
+ assert_equal "credit_card_pin_numbers", CreditCard::PinNumber.table_name
481
+ assert_equal "credit_card_pin_number_cvv_codes", CreditCard::PinNumber::CvvCode.table_name
482
+ assert_equal "credit_card_pin_numbers", CreditCard::SubPinNumber.table_name
483
+ assert_equal "categories", CreditCard::Brand.table_name
190
484
  assert_equal "master_credit_cards", MasterCreditCard.table_name
191
485
 
192
486
  ActiveRecord::Base.pluralize_table_names = false
487
+ classes.each(&:reset_table_name)
488
+
193
489
  assert_equal "category", Category.table_name
194
490
  assert_equal "smarts", Smarts.table_name
195
491
  assert_equal "credit_card", CreditCard.table_name
492
+ assert_equal "credit_card_pin_number", CreditCard::PinNumber.table_name
493
+ assert_equal "credit_card_pin_number_cvv_code", CreditCard::PinNumber::CvvCode.table_name
494
+ assert_equal "credit_card_pin_number", CreditCard::SubPinNumber.table_name
495
+ assert_equal "category", CreditCard::Brand.table_name
196
496
  assert_equal "master_credit_card", MasterCreditCard.table_name
497
+
197
498
  ActiveRecord::Base.pluralize_table_names = true
499
+ classes.each(&:reset_table_name)
198
500
 
199
501
  ActiveRecord::Base.table_name_prefix = "test_"
502
+ Category.reset_table_name
200
503
  assert_equal "test_categories", Category.table_name
201
504
  ActiveRecord::Base.table_name_suffix = "_test"
505
+ Category.reset_table_name
202
506
  assert_equal "test_categories_test", Category.table_name
203
507
  ActiveRecord::Base.table_name_prefix = ""
508
+ Category.reset_table_name
204
509
  assert_equal "categories_test", Category.table_name
205
510
  ActiveRecord::Base.table_name_suffix = ""
511
+ Category.reset_table_name
206
512
  assert_equal "categories", Category.table_name
207
513
 
208
514
  ActiveRecord::Base.pluralize_table_names = false
209
515
  ActiveRecord::Base.table_name_prefix = "test_"
516
+ Category.reset_table_name
210
517
  assert_equal "test_category", Category.table_name
211
518
  ActiveRecord::Base.table_name_suffix = "_test"
519
+ Category.reset_table_name
212
520
  assert_equal "test_category_test", Category.table_name
213
521
  ActiveRecord::Base.table_name_prefix = ""
522
+ Category.reset_table_name
214
523
  assert_equal "category_test", Category.table_name
215
524
  ActiveRecord::Base.table_name_suffix = ""
525
+ Category.reset_table_name
216
526
  assert_equal "category", Category.table_name
527
+
217
528
  ActiveRecord::Base.pluralize_table_names = true
529
+ classes.each(&:reset_table_name)
218
530
  end
219
531
 
220
532
  def test_destroy_all
221
- assert_equal(2, Topic.find_all.size)
533
+ assert_equal 2, Topic.count
222
534
 
223
535
  Topic.destroy_all "author_name = 'Mary'"
224
- assert_equal(1, Topic.find_all.size)
536
+ assert_equal 1, Topic.count
225
537
  end
226
-
538
+
539
+ def test_destroy_many
540
+ assert_equal 3, Client.count
541
+ Client.destroy([2, 3])
542
+ assert_equal 1, Client.count
543
+ end
544
+
545
+ def test_delete_many
546
+ Topic.delete([1, 2])
547
+ assert_equal 0, Topic.count
548
+ end
549
+
227
550
  def test_boolean_attributes
228
551
  assert ! Topic.find(1).approved?
229
552
  assert Topic.find(2).approved?
@@ -231,28 +554,63 @@ class BasicsTest < Test::Unit::TestCase
231
554
 
232
555
  def test_increment_counter
233
556
  Topic.increment_counter("replies_count", 1)
234
- assert_equal 1, Topic.find(1).replies_count
557
+ assert_equal 2, Topic.find(1).replies_count
235
558
 
236
559
  Topic.increment_counter("replies_count", 1)
237
- assert_equal 2, Topic.find(1).replies_count
560
+ assert_equal 3, Topic.find(1).replies_count
238
561
  end
239
562
 
240
563
  def test_decrement_counter
241
564
  Topic.decrement_counter("replies_count", 2)
242
- assert_equal 1, Topic.find(2).replies_count
565
+ assert_equal -1, Topic.find(2).replies_count
243
566
 
244
567
  Topic.decrement_counter("replies_count", 2)
245
- assert_equal 0, Topic.find(1).replies_count
568
+ assert_equal -2, Topic.find(2).replies_count
246
569
  end
247
-
570
+
248
571
  def test_update_all
249
- Topic.update_all "content = 'bulk updated!'"
572
+ assert_equal 2, Topic.update_all("content = 'bulk updated!'")
250
573
  assert_equal "bulk updated!", Topic.find(1).content
251
574
  assert_equal "bulk updated!", Topic.find(2).content
575
+
576
+ assert_equal 2, Topic.update_all(['content = ?', 'bulk updated again!'])
577
+ assert_equal "bulk updated again!", Topic.find(1).content
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
252
582
  end
253
-
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
597
+ end
598
+
599
+ def test_update_many
600
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
601
+ updated = Topic.update(topic_data.keys, topic_data.values)
602
+
603
+ assert_equal 2, updated.size
604
+ assert_equal "1 updated", Topic.find(1).content
605
+ assert_equal "2 updated", Topic.find(2).content
606
+ end
607
+
608
+ def test_delete_all
609
+ assert_equal 2, Topic.delete_all
610
+ end
611
+
254
612
  def test_update_by_condition
255
- Topic.update_all "content = 'bulk updated!'", "approved = 1"
613
+ Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
256
614
  assert_equal "Have a nice day", Topic.find(1).content
257
615
  assert_equal "bulk updated!", Topic.find(2).content
258
616
  end
@@ -269,7 +627,7 @@ class BasicsTest < Test::Unit::TestCase
269
627
  def test_attribute_keys_on_new_instance
270
628
  t = Topic.new
271
629
  assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
272
- assert_raises(NoMethodError) { t.title2 }
630
+ assert_raise(NoMethodError) { t.title2 }
273
631
  end
274
632
 
275
633
  def test_class_name
@@ -298,17 +656,52 @@ class BasicsTest < Test::Unit::TestCase
298
656
 
299
657
  def test_default_values
300
658
  topic = Topic.new
301
- assert_equal 1, topic.approved
659
+ assert topic.approved?
302
660
  assert_nil topic.written_on
661
+ assert_nil topic.bonus_time
303
662
  assert_nil topic.last_read
304
663
 
305
664
  topic.save
306
665
 
307
666
  topic = Topic.find(topic.id)
308
- assert_equal 1, topic.approved
667
+ assert topic.approved?
309
668
  assert_nil topic.last_read
669
+
670
+ # Oracle has some funky default handling, so it requires a bit of
671
+ # extra testing. See ticket #2788.
672
+ if current_adapter?(:OracleAdapter)
673
+ test = TestOracleDefault.new
674
+ assert_equal "X", test.test_char
675
+ assert_equal "hello", test.test_string
676
+ assert_equal 3, test.test_int
677
+ end
310
678
  end
311
-
679
+
680
+ # Oracle, SQLServer, and Sybase do not have a TIME datatype.
681
+ unless current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
682
+ def test_utc_as_time_zone
683
+ Topic.default_timezone = :utc
684
+ attributes = { "bonus_time" => "5:42:00AM" }
685
+ topic = Topic.find(1)
686
+ topic.attributes = attributes
687
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
688
+ Topic.default_timezone = :local
689
+ end
690
+
691
+ def test_utc_as_time_zone_and_new
692
+ Topic.default_timezone = :utc
693
+ attributes = { "bonus_time(1i)"=>"2000",
694
+ "bonus_time(2i)"=>"1",
695
+ "bonus_time(3i)"=>"1",
696
+ "bonus_time(4i)"=>"10",
697
+ "bonus_time(5i)"=>"35",
698
+ "bonus_time(6i)"=>"50" }
699
+ topic = Topic.new(attributes)
700
+ assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time
701
+ Topic.default_timezone = :local
702
+ end
703
+ end
704
+
312
705
  def test_default_values_on_empty_strings
313
706
  topic = Topic.new
314
707
  topic.approved = nil
@@ -318,11 +711,25 @@ class BasicsTest < Test::Unit::TestCase
318
711
 
319
712
  topic = Topic.find(topic.id)
320
713
  assert_nil topic.last_read
321
- assert_nil topic.approved
714
+
715
+ # Sybase adapter does not allow nulls in boolean columns
716
+ if current_adapter?(:SybaseAdapter)
717
+ assert topic.approved == false
718
+ else
719
+ assert_nil topic.approved
720
+ end
322
721
  end
323
-
722
+
324
723
  def test_equality
325
- assert_equal Topic.find(1), Topic.find(2).parent
724
+ assert_equal Topic.find(1), Topic.find(2).topic
725
+ end
726
+
727
+ def test_equality_of_new_records
728
+ assert_not_equal Topic.new, Topic.new
729
+ end
730
+
731
+ def test_hashing
732
+ assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
326
733
  end
327
734
 
328
735
  def test_destroy_new_record
@@ -331,10 +738,61 @@ class BasicsTest < Test::Unit::TestCase
331
738
  assert client.frozen?
332
739
  end
333
740
 
741
+ def test_destroy_record_with_associations
742
+ client = Client.find(3)
743
+ client.destroy
744
+ assert client.frozen?
745
+ assert_kind_of Firm, client.firm
746
+ assert_raises(TypeError) { client.name = "something else" }
747
+ end
748
+
334
749
  def test_update_attribute
335
750
  assert !Topic.find(1).approved?
336
751
  Topic.find(1).update_attribute("approved", true)
337
752
  assert Topic.find(1).approved?
753
+
754
+ Topic.find(1).update_attribute(:approved, false)
755
+ assert !Topic.find(1).approved?
756
+ end
757
+
758
+ def test_update_attributes
759
+ topic = Topic.find(1)
760
+ assert !topic.approved?
761
+ assert_equal "The First Topic", topic.title
762
+
763
+ topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
764
+ topic.reload
765
+ assert topic.approved?
766
+ assert_equal "The First Topic Updated", topic.title
767
+
768
+ topic.update_attributes(:approved => false, :title => "The First Topic")
769
+ topic.reload
770
+ assert !topic.approved?
771
+ assert_equal "The First Topic", topic.title
772
+ end
773
+
774
+ def test_update_attributes!
775
+ reply = Reply.find(2)
776
+ assert_equal "The Second Topic's of the day", reply.title
777
+ assert_equal "Have a nice day", reply.content
778
+
779
+ reply.update_attributes!("title" => "The Second Topic's of the day updated", "content" => "Have a nice evening")
780
+ reply.reload
781
+ assert_equal "The Second Topic's of the day updated", reply.title
782
+ assert_equal "Have a nice evening", reply.content
783
+
784
+ reply.update_attributes!(:title => "The Second Topic's of the day", :content => "Have a nice day")
785
+ reply.reload
786
+ assert_equal "The Second Topic's of the day", reply.title
787
+ assert_equal "Have a nice day", reply.content
788
+
789
+ assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
790
+ end
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" } }
338
796
  end
339
797
 
340
798
  def test_mass_assignment_protection
@@ -343,35 +801,97 @@ class BasicsTest < Test::Unit::TestCase
343
801
  assert_equal 1, firm.rating
344
802
  end
345
803
 
804
+ def test_mass_assignment_protection_against_class_attribute_writers
805
+ [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :colorize_logging,
806
+ :default_timezone, :allow_concurrency, :schema_format, :verification_timeout, :lock_optimistically, :record_timestamps].each do |method|
807
+ assert Task.respond_to?(method)
808
+ assert Task.respond_to?("#{method}=")
809
+ assert Task.new.respond_to?(method)
810
+ assert !Task.new.respond_to?("#{method}=")
811
+ end
812
+ end
813
+
814
+ def test_customized_primary_key_remains_protected
815
+ subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
816
+ assert_nil subscriber.id
817
+
818
+ keyboard = Keyboard.new(:key_number => 9, :name => 'nice try')
819
+ assert_nil keyboard.id
820
+ end
821
+
822
+ def test_customized_primary_key_remains_protected_when_referred_to_as_id
823
+ subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try')
824
+ assert_nil subscriber.id
825
+
826
+ keyboard = Keyboard.new(:id => 9, :name => 'nice try')
827
+ assert_nil keyboard.id
828
+ end
829
+
830
+ def test_mass_assignment_protection_on_defaults
831
+ firm = Firm.new
832
+ firm.attributes = { "id" => 5, "type" => "Client" }
833
+ assert_nil firm.id
834
+ assert_equal "Firm", firm[:type]
835
+ end
836
+
346
837
  def test_mass_assignment_accessible
347
- reply = Reply.new("title" => "hello", "content" => "world", "approved" => 0)
838
+ reply = Reply.new("title" => "hello", "content" => "world", "approved" => true)
348
839
  reply.save
840
+
841
+ assert reply.approved?
349
842
 
350
- assert_equal 1, reply.approved
351
-
352
- reply.approved = 0
843
+ reply.approved = false
353
844
  reply.save
354
845
 
355
- assert_equal 0, reply.approved
846
+ assert !reply.approved?
356
847
  end
357
848
 
358
849
  def test_mass_assignment_protection_inheritance
359
- assert_equal [ :credit_rating, :administrator ], LoosePerson.protected_attributes
850
+ assert_nil LoosePerson.accessible_attributes
851
+ assert_equal Set.new([ 'credit_rating', 'administrator' ]), LoosePerson.protected_attributes
852
+
853
+ assert_nil LooseDescendant.accessible_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'
858
+
360
859
  assert_nil TightPerson.protected_attributes
860
+ assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes
861
+
862
+ assert_nil TightDescendant.protected_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
361
877
  end
362
878
 
363
879
  def test_multiparameter_attributes_on_date
364
880
  attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
365
881
  topic = Topic.find(1)
366
882
  topic.attributes = attributes
367
- assert_equal Date.new(2004, 6, 24).to_s, topic.last_read.to_s
883
+ # note that extra #to_date call allows test to pass for Oracle, which
884
+ # treats dates/times the same
885
+ assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
368
886
  end
369
887
 
370
888
  def test_multiparameter_attributes_on_date_with_empty_date
371
889
  attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
372
890
  topic = Topic.find(1)
373
891
  topic.attributes = attributes
374
- assert_equal Date.new(2004, 6, 1).to_s, topic.last_read.to_s
892
+ # note that extra #to_date call allows test to pass for Oracle, which
893
+ # treats dates/times the same
894
+ assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
375
895
  end
376
896
 
377
897
  def test_multiparameter_attributes_on_date_with_all_empty
@@ -401,6 +921,35 @@ class BasicsTest < Test::Unit::TestCase
401
921
  assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
402
922
  end
403
923
 
924
+ def test_multiparameter_mass_assignment_protector
925
+ task = Task.new
926
+ time = Time.mktime(2000, 1, 1, 1)
927
+ task.starting = time
928
+ attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
929
+ task.attributes = attributes
930
+ assert_equal time, task.starting
931
+ end
932
+
933
+ def test_multiparameter_assignment_of_aggregation
934
+ customer = Customer.new
935
+ address = Address.new("The Street", "The City", "The Country")
936
+ attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
937
+ customer.attributes = attributes
938
+ assert_equal address, customer.address
939
+ end
940
+
941
+ def test_attributes_on_dummy_time
942
+ # Oracle, SQL Server, and Sybase do not have a TIME datatype.
943
+ return true if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
944
+
945
+ attributes = {
946
+ "bonus_time" => "5:42:00AM"
947
+ }
948
+ topic = Topic.find(1)
949
+ topic.attributes = attributes
950
+ assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
951
+ end
952
+
404
953
  def test_boolean
405
954
  b_false = Booleantest.create({ "value" => false })
406
955
  false_id = b_false.id
@@ -412,10 +961,23 @@ class BasicsTest < Test::Unit::TestCase
412
961
  b_true = Booleantest.find(true_id)
413
962
  assert b_true.value?
414
963
  end
964
+
965
+ def test_boolean_cast_from_string
966
+ b_false = Booleantest.create({ "value" => "0" })
967
+ false_id = b_false.id
968
+ b_true = Booleantest.create({ "value" => "1" })
969
+ true_id = b_true.id
970
+
971
+ b_false = Booleantest.find(false_id)
972
+ assert !b_false.value?
973
+ b_true = Booleantest.find(true_id)
974
+ assert b_true.value?
975
+ end
415
976
 
416
977
  def test_clone
417
978
  topic = Topic.find(1)
418
- cloned_topic = topic.clone
979
+ cloned_topic = nil
980
+ assert_nothing_raised { cloned_topic = topic.clone }
419
981
  assert_equal topic.title, cloned_topic.title
420
982
  assert cloned_topic.new_record?
421
983
 
@@ -430,8 +992,42 @@ class BasicsTest < Test::Unit::TestCase
430
992
  cloned_topic = topic.clone
431
993
  cloned_topic.title["a"] = "c"
432
994
  assert_equal "b", topic.title["a"]
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
1000
+ cloned_topic.save
1001
+ assert !cloned_topic.new_record?
1002
+ assert cloned_topic.id != topic.id
433
1003
  end
434
-
1004
+
1005
+ def test_clone_with_aggregate_of_same_name_as_attribute
1006
+ dev = DeveloperWithAggregate.find(1)
1007
+ assert_kind_of DeveloperSalary, dev.salary
1008
+
1009
+ clone = nil
1010
+ assert_nothing_raised { clone = dev.clone }
1011
+ assert_kind_of DeveloperSalary, clone.salary
1012
+ assert_equal dev.salary.amount, clone.salary.amount
1013
+ assert clone.new_record?
1014
+
1015
+ # test if the attributes have been cloned
1016
+ original_amount = clone.salary.amount
1017
+ dev.salary.amount = 1
1018
+ assert_equal original_amount, clone.salary.amount
1019
+
1020
+ assert clone.save
1021
+ assert !clone.new_record?
1022
+ assert clone.id != dev.id
1023
+ end
1024
+
1025
+ def test_clone_preserves_subtype
1026
+ clone = nil
1027
+ assert_nothing_raised { clone = Company.find(3).clone }
1028
+ assert_kind_of Client, clone
1029
+ end
1030
+
435
1031
  def test_bignum
436
1032
  company = Company.find(1)
437
1033
  company.rating = 2147483647
@@ -440,15 +1036,11 @@ class BasicsTest < Test::Unit::TestCase
440
1036
  assert_equal 2147483647, company.rating
441
1037
  end
442
1038
 
443
- def test_default
444
- if Default.connection.class.name == 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
1039
+ # TODO: extend defaults tests to other databases!
1040
+ if current_adapter?(:PostgreSQLAdapter)
1041
+ def test_default
445
1042
  default = Default.new
446
1043
 
447
- # dates / timestampts
448
- time_format = "%m/%d/%Y %H:%M"
449
- assert_equal Time.now.strftime(time_format), default.modified_time.strftime(time_format)
450
- assert_equal Date.today, default.modified_date
451
-
452
1044
  # fixed dates / times
453
1045
  assert_equal Date.new(2004, 1, 1), default.fixed_date
454
1046
  assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
@@ -458,6 +1050,100 @@ class BasicsTest < Test::Unit::TestCase
458
1050
  assert_equal 'a varchar field', default.char2
459
1051
  assert_equal 'a text field', default.char3
460
1052
  end
1053
+
1054
+ class Geometric < ActiveRecord::Base; end
1055
+ def test_geometric_content
1056
+
1057
+ # accepted format notes:
1058
+ # ()'s aren't required
1059
+ # values can be a mix of float or integer
1060
+
1061
+ g = Geometric.new(
1062
+ :a_point => '(5.0, 6.1)',
1063
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
1064
+ :a_line_segment => '(2.0, 3), (5.5, 7.0)',
1065
+ :a_box => '2.0, 3, 5.5, 7.0',
1066
+ :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path
1067
+ :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
1068
+ :a_circle => '<(5.3, 10.4), 2>'
1069
+ )
1070
+
1071
+ assert g.save
1072
+
1073
+ # Reload and check that we have all the geometric attributes.
1074
+ h = Geometric.find(g.id)
1075
+
1076
+ assert_equal '(5,6.1)', h.a_point
1077
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
1078
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
1079
+ assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
1080
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
1081
+ assert_equal '<(5.3,10.4),2>', h.a_circle
1082
+
1083
+ # use a geometric function to test for an open path
1084
+ objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
1085
+ assert_equal objs[0].isopen, 't'
1086
+
1087
+ # test alternate formats when defining the geometric types
1088
+
1089
+ g = Geometric.new(
1090
+ :a_point => '5.0, 6.1',
1091
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
1092
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
1093
+ :a_box => '(2.0, 3), (5.5, 7.0)',
1094
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
1095
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
1096
+ :a_circle => '((5.3, 10.4), 2)'
1097
+ )
1098
+
1099
+ assert g.save
1100
+
1101
+ # Reload and check that we have all the geometric attributes.
1102
+ h = Geometric.find(g.id)
1103
+
1104
+ assert_equal '(5,6.1)', h.a_point
1105
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
1106
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
1107
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
1108
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
1109
+ assert_equal '<(5.3,10.4),2>', h.a_circle
1110
+
1111
+ # use a geometric function to test for an closed path
1112
+ objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
1113
+ assert_equal objs[0].isclosed, 't'
1114
+ end
1115
+ end
1116
+
1117
+ class NumericData < ActiveRecord::Base
1118
+ self.table_name = 'numeric_data'
1119
+ end
1120
+
1121
+ def test_numeric_fields
1122
+ m = NumericData.new(
1123
+ :bank_balance => 1586.43,
1124
+ :big_bank_balance => BigDecimal("1000234000567.95"),
1125
+ :world_population => 6000000000,
1126
+ :my_house_population => 3
1127
+ )
1128
+ assert m.save
1129
+
1130
+ m1 = NumericData.find(m.id)
1131
+ assert_not_nil m1
1132
+
1133
+ # As with migration_test.rb, we should make world_population >= 2**62
1134
+ # to cover 64-bit platforms and test it is a Bignum, but the main thing
1135
+ # is that it's an Integer.
1136
+ assert_kind_of Integer, m1.world_population
1137
+ assert_equal 6000000000, m1.world_population
1138
+
1139
+ assert_kind_of Fixnum, m1.my_house_population
1140
+ assert_equal 3, m1.my_house_population
1141
+
1142
+ assert_kind_of BigDecimal, m1.bank_balance
1143
+ assert_equal BigDecimal("1586.43"), m1.bank_balance
1144
+
1145
+ assert_kind_of BigDecimal, m1.big_bank_balance
1146
+ assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance
461
1147
  end
462
1148
 
463
1149
  def test_auto_id
@@ -465,7 +1151,7 @@ class BasicsTest < Test::Unit::TestCase
465
1151
  auto.save
466
1152
  assert (auto.id > 0)
467
1153
  end
468
-
1154
+
469
1155
  def quote_column_name(name)
470
1156
  "<#{name}>"
471
1157
  end
@@ -479,16 +1165,30 @@ class BasicsTest < Test::Unit::TestCase
479
1165
  assert_equal("<baz>", inverted["quux"])
480
1166
  end
481
1167
 
1168
+ def test_sql_injection_via_find
1169
+ assert_raises(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do
1170
+ Topic.find("123456 OR id > 0")
1171
+ end
1172
+ end
1173
+
482
1174
  def test_column_name_properly_quoted
483
1175
  col_record = ColumnName.new
484
1176
  col_record.references = 40
485
- col_record.save
1177
+ assert col_record.save
486
1178
  col_record.references = 41
487
- col_record.save
488
- c2 = ColumnName.find(col_record.id)
1179
+ assert col_record.save
1180
+ assert_not_nil c2 = ColumnName.find(col_record.id)
489
1181
  assert_equal(41, c2.references)
490
1182
  end
491
1183
 
1184
+ def test_quoting_arrays
1185
+ replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ])
1186
+ assert_equal topics(:first).replies.size, replies.size
1187
+
1188
+ replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ])
1189
+ assert_equal 0, replies.size
1190
+ end
1191
+
492
1192
  MyObject = Struct.new :attribute1, :attribute2
493
1193
 
494
1194
  def test_serialized_attribute
@@ -498,16 +1198,548 @@ class BasicsTest < Test::Unit::TestCase
498
1198
  assert_equal(myobj, topic.content)
499
1199
  end
500
1200
 
501
- def test_serialized_attribute_with_class_constraint
1201
+ def test_nil_serialized_attribute_with_class_constraint
502
1202
  myobj = MyObject.new('value1', 'value2')
503
- topic = Topic.create("content" => myobj)
504
- Topic.serialize(:content, Hash)
1203
+ topic = Topic.new
1204
+ assert_nil topic.content
1205
+ end
505
1206
 
506
- assert_raises(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
1207
+ def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
1208
+ myobj = MyObject.new('value1', 'value2')
1209
+ topic = Topic.new(:content => myobj)
1210
+ assert topic.save
1211
+ Topic.serialize(:content, Hash)
1212
+ assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
1213
+ ensure
1214
+ Topic.serialize(:content)
1215
+ end
507
1216
 
1217
+ def test_serialized_attribute_with_class_constraint
508
1218
  settings = { "color" => "blue" }
509
- Topic.find(topic.id).update_attribute("content", settings)
1219
+ Topic.serialize(:content, Hash)
1220
+ topic = Topic.new(:content => settings)
1221
+ assert topic.save
510
1222
  assert_equal(settings, Topic.find(topic.id).content)
1223
+ ensure
511
1224
  Topic.serialize(:content)
512
1225
  end
513
- end
1226
+
1227
+ def test_quote
1228
+ author_name = "\\ \001 ' \n \\n \""
1229
+ topic = Topic.create('author_name' => author_name)
1230
+ assert_equal author_name, Topic.find(topic.id).author_name
1231
+ end
1232
+
1233
+ def test_quote_chars
1234
+ str = 'The Narrator'
1235
+ topic = Topic.create(:author_name => str)
1236
+ assert_equal str, topic.author_name
1237
+
1238
+ assert_kind_of ActiveSupport::Multibyte::Chars, str.chars
1239
+ topic = Topic.find_by_author_name(str.chars)
1240
+
1241
+ assert_kind_of Topic, topic
1242
+ assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars"
1243
+ end
1244
+
1245
+ def test_class_level_destroy
1246
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
1247
+ Topic.find(1).replies << should_be_destroyed_reply
1248
+
1249
+ Topic.destroy(1)
1250
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
1251
+ assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
1252
+ end
1253
+
1254
+ def test_class_level_delete
1255
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
1256
+ Topic.find(1).replies << should_be_destroyed_reply
1257
+
1258
+ Topic.delete(1)
1259
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
1260
+ assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
1261
+ end
1262
+
1263
+ def test_increment_attribute
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
1270
+ end
1271
+
1272
+ def test_increment_nil_attribute
1273
+ assert_nil topics(:first).parent_id
1274
+ topics(:first).increment! :parent_id
1275
+ assert_equal 1, topics(:first).parent_id
1276
+ end
1277
+
1278
+ def test_decrement_attribute
1279
+ assert_equal 50, accounts(:signals37).credit_limit
1280
+
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
1286
+ end
1287
+
1288
+ def test_toggle_attribute
1289
+ assert !topics(:first).approved?
1290
+ topics(:first).toggle!(:approved)
1291
+ assert topics(:first).approved?
1292
+ topic = topics(:first)
1293
+ topic.toggle(:approved)
1294
+ assert !topic.approved?
1295
+ topic.reload
1296
+ assert topic.approved?
1297
+ end
1298
+
1299
+ def test_reload
1300
+ t1 = Topic.find(1)
1301
+ t2 = Topic.find(1)
1302
+ t1.title = "something else"
1303
+ t1.save
1304
+ t2.reload
1305
+ assert_equal t1.title, t2.title
1306
+ end
1307
+
1308
+ def test_define_attr_method_with_value
1309
+ k = Class.new( ActiveRecord::Base )
1310
+ k.send(:define_attr_method, :table_name, "foo")
1311
+ assert_equal "foo", k.table_name
1312
+ end
1313
+
1314
+ def test_define_attr_method_with_block
1315
+ k = Class.new( ActiveRecord::Base )
1316
+ k.send(:define_attr_method, :primary_key) { "sys_" + original_primary_key }
1317
+ assert_equal "sys_id", k.primary_key
1318
+ end
1319
+
1320
+ def test_set_table_name_with_value
1321
+ k = Class.new( ActiveRecord::Base )
1322
+ k.table_name = "foo"
1323
+ assert_equal "foo", k.table_name
1324
+ k.set_table_name "bar"
1325
+ assert_equal "bar", k.table_name
1326
+ end
1327
+
1328
+ def test_set_table_name_with_block
1329
+ k = Class.new( ActiveRecord::Base )
1330
+ k.set_table_name { "ks" }
1331
+ assert_equal "ks", k.table_name
1332
+ end
1333
+
1334
+ def test_set_primary_key_with_value
1335
+ k = Class.new( ActiveRecord::Base )
1336
+ k.primary_key = "foo"
1337
+ assert_equal "foo", k.primary_key
1338
+ k.set_primary_key "bar"
1339
+ assert_equal "bar", k.primary_key
1340
+ end
1341
+
1342
+ def test_set_primary_key_with_block
1343
+ k = Class.new( ActiveRecord::Base )
1344
+ k.set_primary_key { "sys_" + original_primary_key }
1345
+ assert_equal "sys_id", k.primary_key
1346
+ end
1347
+
1348
+ def test_set_inheritance_column_with_value
1349
+ k = Class.new( ActiveRecord::Base )
1350
+ k.inheritance_column = "foo"
1351
+ assert_equal "foo", k.inheritance_column
1352
+ k.set_inheritance_column "bar"
1353
+ assert_equal "bar", k.inheritance_column
1354
+ end
1355
+
1356
+ def test_set_inheritance_column_with_block
1357
+ k = Class.new( ActiveRecord::Base )
1358
+ k.set_inheritance_column { original_inheritance_column + "_id" }
1359
+ assert_equal "type_id", k.inheritance_column
1360
+ end
1361
+
1362
+ def test_count_with_join
1363
+ res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
1364
+
1365
+ res2 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
1366
+ assert_equal res, res2
1367
+
1368
+ res3 = nil
1369
+ assert_nothing_raised do
1370
+ res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
1371
+ :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
1372
+ end
1373
+ assert_equal res, res3
1374
+
1375
+ res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
1376
+ res5 = nil
1377
+ assert_nothing_raised do
1378
+ res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
1379
+ :joins => "p, comments co",
1380
+ :select => "p.id")
1381
+ end
1382
+
1383
+ assert_equal res4, res5
1384
+
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
1395
+ end
1396
+ end
1397
+
1398
+ def test_clear_association_cache_stored
1399
+ firm = Firm.find(1)
1400
+ assert_kind_of Firm, firm
1401
+
1402
+ firm.clear_association_cache
1403
+ assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
1404
+ end
1405
+
1406
+ def test_clear_association_cache_new_record
1407
+ firm = Firm.new
1408
+ client_stored = Client.find(3)
1409
+ client_new = Client.new
1410
+ client_new.name = "The Joneses"
1411
+ clients = [ client_stored, client_new ]
1412
+
1413
+ firm.clients << clients
1414
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
1415
+
1416
+ firm.clear_association_cache
1417
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
1418
+ end
1419
+
1420
+ def test_interpolate_sql
1421
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') }
1422
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') }
1423
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar} baz') }
1424
+ end
1425
+
1426
+ def test_scoped_find_conditions
1427
+ scoped_developers = Developer.with_scope(:find => { :conditions => 'salary > 90000' }) do
1428
+ Developer.find(:all, :conditions => 'id < 5')
1429
+ end
1430
+ assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
1431
+ assert_equal 3, scoped_developers.size
1432
+ end
1433
+
1434
+ def test_scoped_find_limit_offset
1435
+ scoped_developers = Developer.with_scope(:find => { :limit => 3, :offset => 2 }) do
1436
+ Developer.find(:all, :order => 'id')
1437
+ end
1438
+ assert !scoped_developers.include?(developers(:david))
1439
+ assert !scoped_developers.include?(developers(:jamis))
1440
+ assert_equal 3, scoped_developers.size
1441
+
1442
+ # Test without scoped find conditions to ensure we get the whole thing
1443
+ developers = Developer.find(:all, :order => 'id')
1444
+ assert_equal Developer.count, developers.size
1445
+ end
1446
+
1447
+ def test_scoped_find_order
1448
+ # Test order in scope
1449
+ scoped_developers = Developer.with_scope(:find => { :limit => 1, :order => 'salary DESC' }) do
1450
+ Developer.find(:all)
1451
+ end
1452
+ assert_equal 'Jamis', scoped_developers.first.name
1453
+ assert scoped_developers.include?(developers(:jamis))
1454
+ # Test scope without order and order in find
1455
+ scoped_developers = Developer.with_scope(:find => { :limit => 1 }) do
1456
+ Developer.find(:all, :order => 'salary DESC')
1457
+ end
1458
+ # Test scope order + find order, find has priority
1459
+ scoped_developers = Developer.with_scope(:find => { :limit => 3, :order => 'id DESC' }) do
1460
+ Developer.find(:all, :order => 'salary ASC')
1461
+ end
1462
+ assert scoped_developers.include?(developers(:poor_jamis))
1463
+ assert scoped_developers.include?(developers(:david))
1464
+ assert scoped_developers.include?(developers(:dev_10))
1465
+ # Test without scoped find conditions to ensure we get the right thing
1466
+ developers = Developer.find(:all, :order => 'id', :limit => 1)
1467
+ assert scoped_developers.include?(developers(:david))
1468
+ end
1469
+
1470
+ def test_scoped_find_limit_offset_including_has_many_association
1471
+ topics = Topic.with_scope(:find => {:limit => 1, :offset => 1, :include => :replies}) do
1472
+ Topic.find(:all, :order => "topics.id")
1473
+ end
1474
+ assert_equal 1, topics.size
1475
+ assert_equal 2, topics.first.id
1476
+ end
1477
+
1478
+ def test_scoped_find_order_including_has_many_association
1479
+ developers = Developer.with_scope(:find => { :order => 'developers.salary DESC', :include => :projects }) do
1480
+ Developer.find(:all)
1481
+ end
1482
+ assert developers.size >= 2
1483
+ for i in 1...developers.size
1484
+ assert developers[i-1].salary >= developers[i].salary
1485
+ end
1486
+ end
1487
+
1488
+ def test_abstract_class
1489
+ assert !ActiveRecord::Base.abstract_class?
1490
+ assert LoosePerson.abstract_class?
1491
+ assert !LooseDescendant.abstract_class?
1492
+ end
1493
+
1494
+ def test_base_class
1495
+ assert_equal LoosePerson, LoosePerson.base_class
1496
+ assert_equal LooseDescendant, LooseDescendant.base_class
1497
+ assert_equal TightPerson, TightPerson.base_class
1498
+ assert_equal TightPerson, TightDescendant.base_class
1499
+
1500
+ assert_equal Post, Post.base_class
1501
+ assert_equal Post, SpecialPost.base_class
1502
+ assert_equal Post, StiPost.base_class
1503
+ assert_equal SubStiPost, SubStiPost.base_class
1504
+ end
1505
+
1506
+ def test_descends_from_active_record
1507
+ # Tries to call Object.abstract_class?
1508
+ assert_raise(NoMethodError) do
1509
+ ActiveRecord::Base.descends_from_active_record?
1510
+ end
1511
+
1512
+ # Abstract subclass of AR::Base.
1513
+ assert LoosePerson.descends_from_active_record?
1514
+
1515
+ # Concrete subclass of an abstract class.
1516
+ assert LooseDescendant.descends_from_active_record?
1517
+
1518
+ # Concrete subclass of AR::Base.
1519
+ assert TightPerson.descends_from_active_record?
1520
+
1521
+ # Concrete subclass of a concrete class but has no type column.
1522
+ assert TightDescendant.descends_from_active_record?
1523
+
1524
+ # Concrete subclass of AR::Base.
1525
+ assert Post.descends_from_active_record?
1526
+
1527
+ # Abstract subclass of a concrete class which has a type column.
1528
+ # This is pathological, as you'll never have Sub < Abstract < Concrete.
1529
+ assert !StiPost.descends_from_active_record?
1530
+
1531
+ # Concrete subclasses an abstract class which has a type column.
1532
+ assert !SubStiPost.descends_from_active_record?
1533
+ end
1534
+
1535
+ def test_find_on_abstract_base_class_doesnt_use_type_condition
1536
+ old_class = LooseDescendant
1537
+ Object.send :remove_const, :LooseDescendant
1538
+
1539
+ descendant = old_class.create!
1540
+ assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}"
1541
+ ensure
1542
+ unless Object.const_defined?(:LooseDescendant)
1543
+ Object.const_set :LooseDescendant, old_class
1544
+ end
1545
+ end
1546
+
1547
+ def test_assert_queries
1548
+ query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' }
1549
+ assert_queries(2) { 2.times { query.call } }
1550
+ assert_queries 1, &query
1551
+ assert_no_queries { assert true }
1552
+ end
1553
+
1554
+ def test_to_xml
1555
+ xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
1556
+ bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
1557
+ written_on_in_current_timezone = topics(:first).written_on.xmlschema
1558
+ last_read_in_current_timezone = topics(:first).last_read.xmlschema
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
+
1582
+ if current_adapter?(:SybaseAdapter, :SQLServerAdapter, :OracleAdapter)
1583
+ assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
1584
+ assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
1585
+ else
1586
+ assert_equal "2004-04-15", xml.elements["//last-read"].text
1587
+ assert_equal "date" , xml.elements["//last-read"].attributes['type']
1588
+ end
1589
+
1590
+ # Oracle and DB2 don't have true boolean or time-only fields
1591
+ unless current_adapter?(:OracleAdapter, :DB2Adapter)
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']
1597
+ end
1598
+ end
1599
+
1600
+ def test_to_xml_skipping_attributes
1601
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
1602
+ assert_equal "<topic>", xml.first(7)
1603
+ assert !xml.include?(%(<title>The First Topic</title>))
1604
+ assert xml.include?(%(<author-name>David</author-name>))
1605
+
1606
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
1607
+ assert !xml.include?(%(<title>The First Topic</title>))
1608
+ assert !xml.include?(%(<author-name>David</author-name>))
1609
+ end
1610
+
1611
+ def test_to_xml_including_has_many_association
1612
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
1613
+ assert_equal "<topic>", xml.first(7)
1614
+ assert xml.include?(%(<replies type="array"><reply>))
1615
+ assert xml.include?(%(<title>The Second Topic's of the day</title>))
1616
+ end
1617
+
1618
+ def test_array_to_xml_including_has_many_association
1619
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
1620
+ assert xml.include?(%(<replies type="array"><reply>))
1621
+ end
1622
+
1623
+ def test_array_to_xml_including_methods
1624
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
1625
+ assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
1626
+ assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
1627
+ end
1628
+
1629
+ def test_array_to_xml_including_has_one_association
1630
+ xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
1631
+ assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
1632
+ assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
1633
+ end
1634
+
1635
+ def test_array_to_xml_including_belongs_to_association
1636
+ xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
1637
+ assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
1638
+ assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
1639
+ assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
1640
+ end
1641
+
1642
+ def test_to_xml_including_belongs_to_association
1643
+ xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
1644
+ assert !xml.include?("<firm>")
1645
+
1646
+ xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
1647
+ assert xml.include?("<firm>")
1648
+ end
1649
+
1650
+ def test_to_xml_including_multiple_associations
1651
+ xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
1652
+ assert_equal "<firm>", xml.first(6)
1653
+ assert xml.include?(%(<account>))
1654
+ assert xml.include?(%(<clients type="array"><client>))
1655
+ end
1656
+
1657
+ def test_to_xml_including_multiple_associations_with_options
1658
+ xml = companies(:first_firm).to_xml(
1659
+ :indent => 0, :skip_instruct => true,
1660
+ :include => { :clients => { :only => :name } }
1661
+ )
1662
+
1663
+ assert_equal "<firm>", xml.first(6)
1664
+ assert xml.include?(%(<client><name>Summit</name></client>))
1665
+ assert xml.include?(%(<clients type="array"><client>))
1666
+ end
1667
+
1668
+ def test_to_xml_including_methods
1669
+ xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true)
1670
+ assert_equal "<company>", xml.first(9)
1671
+ assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>))
1672
+ end
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
+
1683
+ def test_except_attributes
1684
+ assert_equal(
1685
+ %w( author_name type id approved replies_count bonus_time written_on content author_email_address parent_id last_read),
1686
+ topics(:first).attributes(:except => :title).keys
1687
+ )
1688
+
1689
+ assert_equal(
1690
+ %w( replies_count bonus_time written_on content author_email_address parent_id last_read),
1691
+ topics(:first).attributes(:except => [ :title, :id, :type, :approved, :author_name ]).keys
1692
+ )
1693
+ end
1694
+
1695
+ def test_include_attributes
1696
+ assert_equal(%w( title ), topics(:first).attributes(:only => :title).keys)
1697
+ assert_equal(%w( title author_name type id approved ), topics(:first).attributes(:only => [ :title, :id, :type, :approved, :author_name ]).keys)
1698
+ end
1699
+
1700
+ def test_type_name_with_module_should_handle_beginning
1701
+ assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person')
1702
+ assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person')
1703
+ end
1704
+
1705
+ def test_to_param_should_return_string
1706
+ assert_kind_of String, Client.find(:first).to_param
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
1723
+
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
1728
+
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
1745
+ end