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
@@ -0,0 +1,273 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/person'
3
+ require 'fixtures/reader'
4
+ require 'fixtures/legacy_thing'
5
+
6
+ class LockWithoutDefault < ActiveRecord::Base; end
7
+
8
+ class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
9
+ set_table_name :lock_without_defaults_cust
10
+ set_locking_column :custom_lock_version
11
+ end
12
+
13
+ class ReadonlyFirstNamePerson < Person
14
+ attr_readonly :first_name
15
+ end
16
+
17
+ class OptimisticLockingTest < Test::Unit::TestCase
18
+ fixtures :people, :legacy_things
19
+
20
+ # need to disable transactional fixtures, because otherwise the sqlite3
21
+ # adapter (at least) chokes when we try and change the schema in the middle
22
+ # of a test (see test_increment_counter_*).
23
+ self.use_transactional_fixtures = false
24
+
25
+ def test_lock_existing
26
+ p1 = Person.find(1)
27
+ p2 = Person.find(1)
28
+ assert_equal 0, p1.lock_version
29
+ assert_equal 0, p2.lock_version
30
+
31
+ p1.save!
32
+ assert_equal 1, p1.lock_version
33
+ assert_equal 0, p2.lock_version
34
+
35
+ assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
36
+ end
37
+
38
+ def test_lock_repeating
39
+ p1 = Person.find(1)
40
+ p2 = Person.find(1)
41
+ assert_equal 0, p1.lock_version
42
+ assert_equal 0, p2.lock_version
43
+
44
+ p1.save!
45
+ assert_equal 1, p1.lock_version
46
+ assert_equal 0, p2.lock_version
47
+
48
+ assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
49
+ assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
50
+ end
51
+
52
+ def test_lock_new
53
+ p1 = Person.new(:first_name => 'anika')
54
+ assert_equal 0, p1.lock_version
55
+
56
+ p1.save!
57
+ p2 = Person.find(p1.id)
58
+ assert_equal 0, p1.lock_version
59
+ assert_equal 0, p2.lock_version
60
+
61
+ p1.save!
62
+ assert_equal 1, p1.lock_version
63
+ assert_equal 0, p2.lock_version
64
+
65
+ assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
66
+ end
67
+
68
+ def test_lock_column_name_existing
69
+ t1 = LegacyThing.find(1)
70
+ t2 = LegacyThing.find(1)
71
+ assert_equal 0, t1.version
72
+ assert_equal 0, t2.version
73
+
74
+ t1.save!
75
+ assert_equal 1, t1.version
76
+ assert_equal 0, t2.version
77
+
78
+ assert_raises(ActiveRecord::StaleObjectError) { t2.save! }
79
+ end
80
+
81
+ def test_lock_column_is_mass_assignable
82
+ p1 = Person.create(:first_name => 'bianca')
83
+ assert_equal 0, p1.lock_version
84
+ assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
85
+
86
+ p1.save!
87
+ assert_equal 1, p1.lock_version
88
+ assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
89
+ end
90
+
91
+ def test_lock_without_default_sets_version_to_zero
92
+ t1 = LockWithoutDefault.new
93
+ assert_equal 0, t1.lock_version
94
+ end
95
+
96
+ def test_lock_with_custom_column_without_default_sets_version_to_zero
97
+ t1 = LockWithCustomColumnWithoutDefault.new
98
+ assert_equal 0, t1.custom_lock_version
99
+ end
100
+
101
+ def test_readonly_attributes
102
+ assert_equal Set.new([ 'first_name' ]), ReadonlyFirstNamePerson.readonly_attributes
103
+
104
+ p = ReadonlyFirstNamePerson.create(:first_name => "unchangeable name")
105
+ p.reload
106
+ assert_equal "unchangeable name", p.first_name
107
+
108
+ p.update_attributes(:first_name => "changed name")
109
+ p.reload
110
+ assert_equal "unchangeable name", p.first_name
111
+ end
112
+
113
+ { :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model|
114
+ define_method("test_increment_counter_updates_#{name}") do
115
+ counter_test model, 1 do |id|
116
+ model.increment_counter :test_count, id
117
+ end
118
+ end
119
+
120
+ define_method("test_decrement_counter_updates_#{name}") do
121
+ counter_test model, -1 do |id|
122
+ model.decrement_counter :test_count, id
123
+ end
124
+ end
125
+
126
+ define_method("test_update_counters_updates_#{name}") do
127
+ counter_test model, 1 do |id|
128
+ model.update_counters id, :test_count => 1
129
+ end
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def add_counter_column_to(model)
136
+ model.connection.add_column model.table_name, :test_count, :integer, :null => false, :default => 0
137
+ model.reset_column_information
138
+ # OpenBase does not set a value to existing rows when adding a not null default column
139
+ model.update_all(:test_count => 0) if current_adapter?(:OpenBaseAdapter)
140
+ end
141
+
142
+ def remove_counter_column_from(model)
143
+ model.connection.remove_column model.table_name, :test_count
144
+ model.reset_column_information
145
+ end
146
+
147
+ def counter_test(model, expected_count)
148
+ add_counter_column_to(model)
149
+ object = model.find(:first)
150
+ assert_equal 0, object.test_count
151
+ assert_equal 0, object.send(model.locking_column)
152
+ yield object.id
153
+ object.reload
154
+ assert_equal expected_count, object.test_count
155
+ assert_equal 1, object.send(model.locking_column)
156
+ ensure
157
+ remove_counter_column_from(model)
158
+ end
159
+ end
160
+
161
+
162
+ # TODO: test against the generated SQL since testing locking behavior itself
163
+ # is so cumbersome. Will deadlock Ruby threads if the underlying db.execute
164
+ # blocks, so separate script called by Kernel#system is needed.
165
+ # (See exec vs. async_exec in the PostgreSQL adapter.)
166
+
167
+ # TODO: The SQL Server, Sybase, and OpenBase adapters currently have no support for pessimistic locking
168
+
169
+ unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :OpenBaseAdapter)
170
+ class PessimisticLockingTest < Test::Unit::TestCase
171
+ self.use_transactional_fixtures = false
172
+ fixtures :people, :readers
173
+
174
+ def setup
175
+ # Avoid introspection queries during tests.
176
+ Person.columns; Reader.columns
177
+
178
+ @allow_concurrency = ActiveRecord::Base.allow_concurrency
179
+ ActiveRecord::Base.allow_concurrency = true
180
+ end
181
+
182
+ def teardown
183
+ ActiveRecord::Base.allow_concurrency = @allow_concurrency
184
+ end
185
+
186
+ # Test typical find.
187
+ def test_sane_find_with_lock
188
+ assert_nothing_raised do
189
+ Person.transaction do
190
+ Person.find 1, :lock => true
191
+ end
192
+ end
193
+ end
194
+
195
+ # Test scoped lock.
196
+ def test_sane_find_with_scoped_lock
197
+ assert_nothing_raised do
198
+ Person.transaction do
199
+ Person.with_scope(:find => { :lock => true }) do
200
+ Person.find 1
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ # PostgreSQL protests SELECT ... FOR UPDATE on an outer join.
207
+ unless current_adapter?(:PostgreSQLAdapter)
208
+ # Test locked eager find.
209
+ def test_eager_find_with_lock
210
+ assert_nothing_raised do
211
+ Person.transaction do
212
+ Person.find 1, :include => :readers, :lock => true
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+ # Locking a record reloads it.
219
+ def test_sane_lock_method
220
+ assert_nothing_raised do
221
+ Person.transaction do
222
+ person = Person.find 1
223
+ old, person.first_name = person.first_name, 'fooman'
224
+ person.lock!
225
+ assert_equal old, person.first_name
226
+ end
227
+ end
228
+ end
229
+
230
+ if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
231
+ def test_no_locks_no_wait
232
+ first, second = duel { Person.find 1 }
233
+ assert first.end > second.end
234
+ end
235
+
236
+ def test_second_lock_waits
237
+ assert [0.2, 1, 5].any? { |zzz|
238
+ first, second = duel(zzz) { Person.find 1, :lock => true }
239
+ second.end > first.end
240
+ }
241
+ end
242
+
243
+ protected
244
+ def duel(zzz = 5)
245
+ t0, t1, t2, t3 = nil, nil, nil, nil
246
+
247
+ a = Thread.new do
248
+ t0 = Time.now
249
+ Person.transaction do
250
+ yield
251
+ sleep zzz # block thread 2 for zzz seconds
252
+ end
253
+ t1 = Time.now
254
+ end
255
+
256
+ b = Thread.new do
257
+ sleep zzz / 2.0 # ensure thread 1 tx starts first
258
+ t2 = Time.now
259
+ Person.transaction { yield }
260
+ t3 = Time.now
261
+ end
262
+
263
+ a.join
264
+ b.join
265
+
266
+ assert t1 > t0 + zzz
267
+ assert t2 > t0
268
+ assert t3 > t2
269
+ [t0.to_f..t1.to_f, t2.to_f..t3.to_f]
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,416 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/developer'
3
+ require 'fixtures/project'
4
+ require 'fixtures/comment'
5
+ require 'fixtures/post'
6
+ require 'fixtures/category'
7
+
8
+ class MethodScopingTest < Test::Unit::TestCase
9
+ fixtures :developers, :projects, :comments, :posts
10
+
11
+ def test_set_conditions
12
+ Developer.with_scope(:find => { :conditions => 'just a test...' }) do
13
+ assert_equal 'just a test...', Developer.send(:current_scoped_methods)[:find][:conditions]
14
+ end
15
+ end
16
+
17
+ def test_scoped_find
18
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
19
+ assert_nothing_raised { Developer.find(1) }
20
+ end
21
+ end
22
+
23
+ def test_scoped_find_first
24
+ Developer.with_scope(:find => { :conditions => "salary = 100000" }) do
25
+ assert_equal Developer.find(10), Developer.find(:first, :order => 'name')
26
+ end
27
+ end
28
+
29
+ def test_scoped_find_combines_conditions
30
+ Developer.with_scope(:find => { :conditions => "salary = 9000" }) do
31
+ assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'")
32
+ end
33
+ end
34
+
35
+ def test_scoped_find_sanitizes_conditions
36
+ Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
37
+ assert_equal developers(:poor_jamis), Developer.find(:first)
38
+ end
39
+ end
40
+
41
+ def test_scoped_find_combines_and_sanitizes_conditions
42
+ Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
43
+ assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
44
+ end
45
+ end
46
+
47
+ def test_scoped_find_all
48
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
49
+ assert_equal [developers(:david)], Developer.find(:all)
50
+ end
51
+ end
52
+
53
+ def test_scoped_count
54
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
55
+ assert_equal 1, Developer.count
56
+ end
57
+
58
+ Developer.with_scope(:find => { :conditions => 'salary = 100000' }) do
59
+ assert_equal 8, Developer.count
60
+ assert_equal 1, Developer.count(:conditions => "name LIKE 'fixture_1%'")
61
+ end
62
+ end
63
+
64
+ def test_scoped_find_include
65
+ # with the include, will retrieve only developers for the given project
66
+ scoped_developers = Developer.with_scope(:find => { :include => :projects }) do
67
+ Developer.find(:all, :conditions => 'projects.id = 2')
68
+ end
69
+ assert scoped_developers.include?(developers(:david))
70
+ assert !scoped_developers.include?(developers(:jamis))
71
+ assert_equal 1, scoped_developers.size
72
+ end
73
+
74
+ def test_scoped_count_include
75
+ # with the include, will retrieve only developers for the given project
76
+ Developer.with_scope(:find => { :include => :projects }) do
77
+ assert_equal 1, Developer.count(:conditions => 'projects.id = 2')
78
+ end
79
+ end
80
+
81
+ def test_scoped_create
82
+ new_comment = nil
83
+
84
+ VerySpecialComment.with_scope(:create => { :post_id => 1 }) do
85
+ assert_equal({ :post_id => 1 }, VerySpecialComment.send(:current_scoped_methods)[:create])
86
+ new_comment = VerySpecialComment.create :body => "Wonderful world"
87
+ end
88
+
89
+ assert Post.find(1).comments.include?(new_comment)
90
+ end
91
+
92
+ def test_immutable_scope
93
+ options = { :conditions => "name = 'David'" }
94
+ Developer.with_scope(:find => options) do
95
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
96
+ options[:conditions] = "name != 'David'"
97
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
98
+ end
99
+
100
+ scope = { :find => { :conditions => "name = 'David'" }}
101
+ Developer.with_scope(scope) do
102
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
103
+ scope[:find][:conditions] = "name != 'David'"
104
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
105
+ end
106
+ end
107
+
108
+ def test_scoped_with_duck_typing
109
+ scoping = Struct.new(:method_scoping).new(:find => { :conditions => ["name = ?", 'David'] })
110
+ Developer.with_scope(scoping) do
111
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
112
+ end
113
+ end
114
+
115
+ def test_ensure_that_method_scoping_is_correctly_restored
116
+ scoped_methods = Developer.instance_eval('current_scoped_methods')
117
+
118
+ begin
119
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
120
+ raise "an exception"
121
+ end
122
+ rescue
123
+ end
124
+ assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
125
+ end
126
+ end
127
+
128
+ class NestedScopingTest < Test::Unit::TestCase
129
+ fixtures :developers, :projects, :comments, :posts
130
+
131
+ def test_merge_options
132
+ Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
133
+ Developer.with_scope(:find => { :limit => 10 }) do
134
+ merged_option = Developer.instance_eval('current_scoped_methods')[:find]
135
+ assert_equal({ :conditions => 'salary = 80000', :limit => 10 }, merged_option)
136
+ end
137
+ end
138
+ end
139
+
140
+ def test_replace_options
141
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
142
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
143
+ assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.instance_eval('current_scoped_methods'))
144
+ assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.send(:scoped_methods)[-1])
145
+ end
146
+ end
147
+ end
148
+
149
+ def test_append_conditions
150
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
151
+ Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
152
+ appended_condition = Developer.instance_eval('current_scoped_methods')[:find][:conditions]
153
+ assert_equal("( name = 'David' ) AND ( salary = 80000 )", appended_condition)
154
+ assert_equal(1, Developer.count)
155
+ end
156
+ Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do
157
+ assert_equal(0, Developer.count)
158
+ end
159
+ end
160
+ end
161
+
162
+ def test_merge_and_append_options
163
+ Developer.with_scope(:find => { :conditions => 'salary = 80000', :limit => 10 }) do
164
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
165
+ merged_option = Developer.instance_eval('current_scoped_methods')[:find]
166
+ assert_equal({ :conditions => "( salary = 80000 ) AND ( name = 'David' )", :limit => 10 }, merged_option)
167
+ end
168
+ end
169
+ end
170
+
171
+ def test_nested_scoped_find
172
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
173
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'David'" }) do
174
+ assert_nothing_raised { Developer.find(1) }
175
+ assert_equal('David', Developer.find(:first).name)
176
+ end
177
+ assert_equal('Jamis', Developer.find(:first).name)
178
+ end
179
+ end
180
+
181
+ def test_nested_scoped_find_include
182
+ Developer.with_scope(:find => { :include => :projects }) do
183
+ Developer.with_scope(:find => { :conditions => "projects.id = 2" }) do
184
+ assert_nothing_raised { Developer.find(1) }
185
+ assert_equal('David', Developer.find(:first).name)
186
+ end
187
+ end
188
+ end
189
+
190
+ def test_nested_scoped_find_merged_include
191
+ # :include's remain unique and don't "double up" when merging
192
+ Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do
193
+ Developer.with_scope(:find => { :include => :projects }) do
194
+ assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
195
+ assert_equal('David', Developer.find(:first).name)
196
+ end
197
+ end
198
+
199
+ # the nested scope doesn't remove the first :include
200
+ Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do
201
+ Developer.with_scope(:find => { :include => [] }) do
202
+ assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
203
+ assert_equal('David', Developer.find(:first).name)
204
+ end
205
+ end
206
+
207
+ # mixing array and symbol include's will merge correctly
208
+ Developer.with_scope(:find => { :include => [:projects], :conditions => "projects.id = 2" }) do
209
+ Developer.with_scope(:find => { :include => :projects }) do
210
+ assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
211
+ assert_equal('David', Developer.find(:first).name)
212
+ end
213
+ end
214
+ end
215
+
216
+ def test_nested_scoped_find_replace_include
217
+ Developer.with_scope(:find => { :include => :projects }) do
218
+ Developer.with_exclusive_scope(:find => { :include => [] }) do
219
+ assert_equal 0, Developer.instance_eval('current_scoped_methods')[:find][:include].length
220
+ end
221
+ end
222
+ end
223
+
224
+ def test_three_level_nested_exclusive_scoped_find
225
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
226
+ assert_equal('Jamis', Developer.find(:first).name)
227
+
228
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'David'" }) do
229
+ assert_equal('David', Developer.find(:first).name)
230
+
231
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'Maiha'" }) do
232
+ assert_equal(nil, Developer.find(:first))
233
+ end
234
+
235
+ # ensure that scoping is restored
236
+ assert_equal('David', Developer.find(:first).name)
237
+ end
238
+
239
+ # ensure that scoping is restored
240
+ assert_equal('Jamis', Developer.find(:first).name)
241
+ end
242
+ end
243
+
244
+ def test_merged_scoped_find
245
+ poor_jamis = developers(:poor_jamis)
246
+ Developer.with_scope(:find => { :conditions => "salary < 100000" }) do
247
+ Developer.with_scope(:find => { :offset => 1 }) do
248
+ assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
249
+ end
250
+ end
251
+ end
252
+
253
+ def test_merged_scoped_find_sanitizes_conditions
254
+ Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
255
+ Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
256
+ assert_raise(ActiveRecord::RecordNotFound) { developers(:poor_jamis) }
257
+ end
258
+ end
259
+ end
260
+
261
+ def test_nested_scoped_find_combines_and_sanitizes_conditions
262
+ Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
263
+ Developer.with_exclusive_scope(:find => { :conditions => ['salary = ?', 9000] }) do
264
+ assert_equal developers(:poor_jamis), Developer.find(:first)
265
+ assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
266
+ end
267
+ end
268
+ end
269
+
270
+ def test_merged_scoped_find_combines_and_sanitizes_conditions
271
+ Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
272
+ Developer.with_scope(:find => { :conditions => ['salary > ?', 9000] }) do
273
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
274
+ end
275
+ end
276
+ end
277
+
278
+ def test_immutable_nested_scope
279
+ options1 = { :conditions => "name = 'Jamis'" }
280
+ options2 = { :conditions => "name = 'David'" }
281
+ Developer.with_scope(:find => options1) do
282
+ Developer.with_exclusive_scope(:find => options2) do
283
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
284
+ options1[:conditions] = options2[:conditions] = nil
285
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
286
+ end
287
+ end
288
+ end
289
+
290
+ def test_immutable_merged_scope
291
+ options1 = { :conditions => "name = 'Jamis'" }
292
+ options2 = { :conditions => "salary > 10000" }
293
+ Developer.with_scope(:find => options1) do
294
+ Developer.with_scope(:find => options2) do
295
+ assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
296
+ options1[:conditions] = options2[:conditions] = nil
297
+ assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
298
+ end
299
+ end
300
+ end
301
+
302
+ def test_ensure_that_method_scoping_is_correctly_restored
303
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
304
+ scoped_methods = Developer.instance_eval('current_scoped_methods')
305
+ begin
306
+ Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do
307
+ raise "an exception"
308
+ end
309
+ rescue
310
+ end
311
+ assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
312
+ end
313
+ end
314
+ end
315
+
316
+ class HasManyScopingTest< Test::Unit::TestCase
317
+ fixtures :comments, :posts
318
+
319
+ def setup
320
+ @welcome = Post.find(1)
321
+ end
322
+
323
+ def test_forwarding_of_static_methods
324
+ assert_equal 'a comment...', Comment.what_are_you
325
+ assert_equal 'a comment...', @welcome.comments.what_are_you
326
+ end
327
+
328
+ def test_forwarding_to_scoped
329
+ assert_equal 4, Comment.search_by_type('Comment').size
330
+ assert_equal 2, @welcome.comments.search_by_type('Comment').size
331
+ end
332
+
333
+ def test_forwarding_to_dynamic_finders
334
+ assert_equal 4, Comment.find_all_by_type('Comment').size
335
+ assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
336
+ end
337
+
338
+ def test_nested_scope
339
+ Comment.with_scope(:find => { :conditions => '1=1' }) do
340
+ assert_equal 'a comment...', @welcome.comments.what_are_you
341
+ end
342
+ end
343
+ end
344
+
345
+
346
+ class HasAndBelongsToManyScopingTest< Test::Unit::TestCase
347
+ fixtures :posts, :categories, :categories_posts
348
+
349
+ def setup
350
+ @welcome = Post.find(1)
351
+ end
352
+
353
+ def test_forwarding_of_static_methods
354
+ assert_equal 'a category...', Category.what_are_you
355
+ assert_equal 'a category...', @welcome.categories.what_are_you
356
+ end
357
+
358
+ def test_forwarding_to_dynamic_finders
359
+ assert_equal 4, Category.find_all_by_type('SpecialCategory').size
360
+ assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
361
+ assert_equal 2, @welcome.categories.find_all_by_type('Category').size
362
+ end
363
+
364
+ def test_nested_scope
365
+ Category.with_scope(:find => { :conditions => '1=1' }) do
366
+ assert_equal 'a comment...', @welcome.comments.what_are_you
367
+ end
368
+ end
369
+ end
370
+
371
+
372
+ =begin
373
+ # We disabled the scoping for has_one and belongs_to as we can't think of a proper use case
374
+
375
+
376
+ class BelongsToScopingTest< Test::Unit::TestCase
377
+ fixtures :comments, :posts
378
+
379
+ def setup
380
+ @greetings = Comment.find(1)
381
+ end
382
+
383
+ def test_forwarding_of_static_method
384
+ assert_equal 'a post...', Post.what_are_you
385
+ assert_equal 'a post...', @greetings.post.what_are_you
386
+ end
387
+
388
+ def test_forwarding_to_dynamic_finders
389
+ assert_equal 4, Post.find_all_by_type('Post').size
390
+ assert_equal 1, @greetings.post.find_all_by_type('Post').size
391
+ end
392
+
393
+ end
394
+
395
+
396
+ class HasOneScopingTest< Test::Unit::TestCase
397
+ fixtures :comments, :posts
398
+
399
+ def setup
400
+ @sti_comments = Post.find(4)
401
+ end
402
+
403
+ def test_forwarding_of_static_methods
404
+ assert_equal 'a comment...', Comment.what_are_you
405
+ assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you
406
+ end
407
+
408
+ def test_forwarding_to_dynamic_finders
409
+ assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size
410
+ assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size
411
+ assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size
412
+ end
413
+
414
+ end
415
+
416
+ =end