activerecord_csi 2.3.5.p6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. data/CHANGELOG +5858 -0
  2. data/README +351 -0
  3. data/RUNNING_UNIT_TESTS +36 -0
  4. data/Rakefile +270 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/performance.rb +162 -0
  7. data/install.rb +30 -0
  8. data/lib/active_record/aggregations.rb +261 -0
  9. data/lib/active_record/association_preload.rb +389 -0
  10. data/lib/active_record/associations/association_collection.rb +475 -0
  11. data/lib/active_record/associations/association_proxy.rb +278 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +76 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +53 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +122 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +266 -0
  17. data/lib/active_record/associations/has_one_association.rb +133 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +37 -0
  19. data/lib/active_record/associations.rb +2241 -0
  20. data/lib/active_record/attribute_methods.rb +388 -0
  21. data/lib/active_record/autosave_association.rb +364 -0
  22. data/lib/active_record/base.rb +3171 -0
  23. data/lib/active_record/batches.rb +81 -0
  24. data/lib/active_record/calculations.rb +311 -0
  25. data/lib/active_record/callbacks.rb +360 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +371 -0
  27. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +139 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +289 -0
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +94 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +722 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +434 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +241 -0
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +630 -0
  35. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1113 -0
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  37. data/lib/active_record/connection_adapters/sqlite_adapter.rb +453 -0
  38. data/lib/active_record/dirty.rb +183 -0
  39. data/lib/active_record/dynamic_finder_match.rb +41 -0
  40. data/lib/active_record/dynamic_scope_match.rb +25 -0
  41. data/lib/active_record/fixtures.rb +996 -0
  42. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  43. data/lib/active_record/locale/en.yml +58 -0
  44. data/lib/active_record/locking/optimistic.rb +148 -0
  45. data/lib/active_record/locking/pessimistic.rb +55 -0
  46. data/lib/active_record/migration.rb +566 -0
  47. data/lib/active_record/named_scope.rb +192 -0
  48. data/lib/active_record/nested_attributes.rb +392 -0
  49. data/lib/active_record/observer.rb +197 -0
  50. data/lib/active_record/query_cache.rb +33 -0
  51. data/lib/active_record/reflection.rb +320 -0
  52. data/lib/active_record/schema.rb +51 -0
  53. data/lib/active_record/schema_dumper.rb +182 -0
  54. data/lib/active_record/serialization.rb +101 -0
  55. data/lib/active_record/serializers/json_serializer.rb +91 -0
  56. data/lib/active_record/serializers/xml_serializer.rb +357 -0
  57. data/lib/active_record/session_store.rb +326 -0
  58. data/lib/active_record/test_case.rb +66 -0
  59. data/lib/active_record/timestamp.rb +71 -0
  60. data/lib/active_record/transactions.rb +235 -0
  61. data/lib/active_record/validations.rb +1135 -0
  62. data/lib/active_record/version.rb +9 -0
  63. data/lib/active_record.rb +84 -0
  64. data/lib/activerecord.rb +2 -0
  65. data/test/assets/example.log +1 -0
  66. data/test/assets/flowers.jpg +0 -0
  67. data/test/cases/aaa_create_tables_test.rb +24 -0
  68. data/test/cases/active_schema_test_mysql.rb +100 -0
  69. data/test/cases/active_schema_test_postgresql.rb +24 -0
  70. data/test/cases/adapter_test.rb +145 -0
  71. data/test/cases/aggregations_test.rb +167 -0
  72. data/test/cases/ar_schema_test.rb +32 -0
  73. data/test/cases/associations/belongs_to_associations_test.rb +425 -0
  74. data/test/cases/associations/callbacks_test.rb +161 -0
  75. data/test/cases/associations/cascaded_eager_loading_test.rb +131 -0
  76. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
  77. data/test/cases/associations/eager_load_nested_include_test.rb +130 -0
  78. data/test/cases/associations/eager_singularization_test.rb +145 -0
  79. data/test/cases/associations/eager_test.rb +834 -0
  80. data/test/cases/associations/extension_test.rb +62 -0
  81. data/test/cases/associations/habtm_join_table_test.rb +56 -0
  82. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +822 -0
  83. data/test/cases/associations/has_many_associations_test.rb +1134 -0
  84. data/test/cases/associations/has_many_through_associations_test.rb +346 -0
  85. data/test/cases/associations/has_one_associations_test.rb +330 -0
  86. data/test/cases/associations/has_one_through_associations_test.rb +209 -0
  87. data/test/cases/associations/inner_join_association_test.rb +93 -0
  88. data/test/cases/associations/join_model_test.rb +712 -0
  89. data/test/cases/associations_test.rb +262 -0
  90. data/test/cases/attribute_methods_test.rb +305 -0
  91. data/test/cases/autosave_association_test.rb +1142 -0
  92. data/test/cases/base_test.rb +2154 -0
  93. data/test/cases/batches_test.rb +61 -0
  94. data/test/cases/binary_test.rb +30 -0
  95. data/test/cases/calculations_test.rb +348 -0
  96. data/test/cases/callbacks_observers_test.rb +38 -0
  97. data/test/cases/callbacks_test.rb +438 -0
  98. data/test/cases/class_inheritable_attributes_test.rb +32 -0
  99. data/test/cases/column_alias_test.rb +17 -0
  100. data/test/cases/column_definition_test.rb +70 -0
  101. data/test/cases/connection_pool_test.rb +25 -0
  102. data/test/cases/connection_test_firebird.rb +8 -0
  103. data/test/cases/connection_test_mysql.rb +64 -0
  104. data/test/cases/copy_table_test_sqlite.rb +80 -0
  105. data/test/cases/database_statements_test.rb +12 -0
  106. data/test/cases/datatype_test_postgresql.rb +204 -0
  107. data/test/cases/date_time_test.rb +37 -0
  108. data/test/cases/default_test_firebird.rb +16 -0
  109. data/test/cases/defaults_test.rb +111 -0
  110. data/test/cases/deprecated_finder_test.rb +30 -0
  111. data/test/cases/dirty_test.rb +316 -0
  112. data/test/cases/finder_respond_to_test.rb +76 -0
  113. data/test/cases/finder_test.rb +1066 -0
  114. data/test/cases/fixtures_test.rb +656 -0
  115. data/test/cases/helper.rb +68 -0
  116. data/test/cases/i18n_test.rb +46 -0
  117. data/test/cases/inheritance_test.rb +262 -0
  118. data/test/cases/invalid_date_test.rb +24 -0
  119. data/test/cases/json_serialization_test.rb +205 -0
  120. data/test/cases/lifecycle_test.rb +193 -0
  121. data/test/cases/locking_test.rb +304 -0
  122. data/test/cases/method_scoping_test.rb +704 -0
  123. data/test/cases/migration_test.rb +1523 -0
  124. data/test/cases/migration_test_firebird.rb +124 -0
  125. data/test/cases/mixin_test.rb +96 -0
  126. data/test/cases/modules_test.rb +81 -0
  127. data/test/cases/multiple_db_test.rb +85 -0
  128. data/test/cases/named_scope_test.rb +361 -0
  129. data/test/cases/nested_attributes_test.rb +581 -0
  130. data/test/cases/pk_test.rb +119 -0
  131. data/test/cases/pooled_connections_test.rb +103 -0
  132. data/test/cases/query_cache_test.rb +123 -0
  133. data/test/cases/readonly_test.rb +107 -0
  134. data/test/cases/reflection_test.rb +194 -0
  135. data/test/cases/reload_models_test.rb +22 -0
  136. data/test/cases/repair_helper.rb +50 -0
  137. data/test/cases/reserved_word_test_mysql.rb +176 -0
  138. data/test/cases/sanitize_test.rb +25 -0
  139. data/test/cases/schema_authorization_test_postgresql.rb +75 -0
  140. data/test/cases/schema_dumper_test.rb +211 -0
  141. data/test/cases/schema_test_postgresql.rb +178 -0
  142. data/test/cases/serialization_test.rb +47 -0
  143. data/test/cases/synonym_test_oracle.rb +17 -0
  144. data/test/cases/timestamp_test.rb +75 -0
  145. data/test/cases/transactions_test.rb +522 -0
  146. data/test/cases/unconnected_test.rb +32 -0
  147. data/test/cases/validations_i18n_test.rb +955 -0
  148. data/test/cases/validations_test.rb +1640 -0
  149. data/test/cases/xml_serialization_test.rb +240 -0
  150. data/test/config.rb +5 -0
  151. data/test/connections/jdbc_jdbcderby/connection.rb +18 -0
  152. data/test/connections/jdbc_jdbch2/connection.rb +18 -0
  153. data/test/connections/jdbc_jdbchsqldb/connection.rb +18 -0
  154. data/test/connections/jdbc_jdbcmysql/connection.rb +26 -0
  155. data/test/connections/jdbc_jdbcpostgresql/connection.rb +26 -0
  156. data/test/connections/jdbc_jdbcsqlite3/connection.rb +25 -0
  157. data/test/connections/native_db2/connection.rb +25 -0
  158. data/test/connections/native_firebird/connection.rb +26 -0
  159. data/test/connections/native_frontbase/connection.rb +27 -0
  160. data/test/connections/native_mysql/connection.rb +25 -0
  161. data/test/connections/native_openbase/connection.rb +21 -0
  162. data/test/connections/native_oracle/connection.rb +27 -0
  163. data/test/connections/native_postgresql/connection.rb +25 -0
  164. data/test/connections/native_sqlite/connection.rb +25 -0
  165. data/test/connections/native_sqlite3/connection.rb +25 -0
  166. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  167. data/test/connections/native_sybase/connection.rb +23 -0
  168. data/test/fixtures/accounts.yml +29 -0
  169. data/test/fixtures/all/developers.yml +0 -0
  170. data/test/fixtures/all/people.csv +0 -0
  171. data/test/fixtures/all/tasks.yml +0 -0
  172. data/test/fixtures/author_addresses.yml +5 -0
  173. data/test/fixtures/author_favorites.yml +4 -0
  174. data/test/fixtures/authors.yml +9 -0
  175. data/test/fixtures/binaries.yml +132 -0
  176. data/test/fixtures/books.yml +7 -0
  177. data/test/fixtures/categories/special_categories.yml +9 -0
  178. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  179. data/test/fixtures/categories.yml +14 -0
  180. data/test/fixtures/categories_ordered.yml +7 -0
  181. data/test/fixtures/categories_posts.yml +23 -0
  182. data/test/fixtures/categorizations.yml +17 -0
  183. data/test/fixtures/clubs.yml +6 -0
  184. data/test/fixtures/comments.yml +59 -0
  185. data/test/fixtures/companies.yml +56 -0
  186. data/test/fixtures/computers.yml +4 -0
  187. data/test/fixtures/courses.yml +7 -0
  188. data/test/fixtures/customers.yml +26 -0
  189. data/test/fixtures/developers.yml +21 -0
  190. data/test/fixtures/developers_projects.yml +17 -0
  191. data/test/fixtures/edges.yml +6 -0
  192. data/test/fixtures/entrants.yml +14 -0
  193. data/test/fixtures/fixture_database.sqlite3 +0 -0
  194. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  195. data/test/fixtures/fk_test_has_fk.yml +3 -0
  196. data/test/fixtures/fk_test_has_pk.yml +2 -0
  197. data/test/fixtures/funny_jokes.yml +10 -0
  198. data/test/fixtures/items.yml +4 -0
  199. data/test/fixtures/jobs.yml +7 -0
  200. data/test/fixtures/legacy_things.yml +3 -0
  201. data/test/fixtures/mateys.yml +4 -0
  202. data/test/fixtures/member_types.yml +6 -0
  203. data/test/fixtures/members.yml +6 -0
  204. data/test/fixtures/memberships.yml +20 -0
  205. data/test/fixtures/minimalistics.yml +2 -0
  206. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  207. data/test/fixtures/mixins.yml +29 -0
  208. data/test/fixtures/movies.yml +7 -0
  209. data/test/fixtures/naked/csv/accounts.csv +1 -0
  210. data/test/fixtures/naked/yml/accounts.yml +1 -0
  211. data/test/fixtures/naked/yml/companies.yml +1 -0
  212. data/test/fixtures/naked/yml/courses.yml +1 -0
  213. data/test/fixtures/organizations.yml +5 -0
  214. data/test/fixtures/owners.yml +7 -0
  215. data/test/fixtures/parrots.yml +27 -0
  216. data/test/fixtures/parrots_pirates.yml +7 -0
  217. data/test/fixtures/people.yml +15 -0
  218. data/test/fixtures/pets.yml +14 -0
  219. data/test/fixtures/pirates.yml +9 -0
  220. data/test/fixtures/posts.yml +52 -0
  221. data/test/fixtures/price_estimates.yml +7 -0
  222. data/test/fixtures/projects.yml +7 -0
  223. data/test/fixtures/readers.yml +9 -0
  224. data/test/fixtures/references.yml +17 -0
  225. data/test/fixtures/reserved_words/distinct.yml +5 -0
  226. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  227. data/test/fixtures/reserved_words/group.yml +14 -0
  228. data/test/fixtures/reserved_words/select.yml +8 -0
  229. data/test/fixtures/reserved_words/values.yml +7 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/sponsors.yml +9 -0
  232. data/test/fixtures/subscribers.yml +7 -0
  233. data/test/fixtures/subscriptions.yml +12 -0
  234. data/test/fixtures/taggings.yml +28 -0
  235. data/test/fixtures/tags.yml +7 -0
  236. data/test/fixtures/tasks.yml +7 -0
  237. data/test/fixtures/topics.yml +42 -0
  238. data/test/fixtures/toys.yml +4 -0
  239. data/test/fixtures/treasures.yml +10 -0
  240. data/test/fixtures/vertices.yml +4 -0
  241. data/test/fixtures/warehouse-things.yml +3 -0
  242. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  243. data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -0
  244. data/test/migrations/duplicate/1_people_have_last_names.rb +9 -0
  245. data/test/migrations/duplicate/2_we_need_reminders.rb +12 -0
  246. data/test/migrations/duplicate/3_foo.rb +7 -0
  247. data/test/migrations/duplicate/3_innocent_jointable.rb +12 -0
  248. data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
  249. data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
  250. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +12 -0
  251. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +9 -0
  252. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +12 -0
  253. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +9 -0
  254. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
  255. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
  256. data/test/migrations/missing/1000_people_have_middle_names.rb +9 -0
  257. data/test/migrations/missing/1_people_have_last_names.rb +9 -0
  258. data/test/migrations/missing/3_we_need_reminders.rb +12 -0
  259. data/test/migrations/missing/4_innocent_jointable.rb +12 -0
  260. data/test/migrations/valid/1_people_have_last_names.rb +9 -0
  261. data/test/migrations/valid/2_we_need_reminders.rb +12 -0
  262. data/test/migrations/valid/3_innocent_jointable.rb +12 -0
  263. data/test/models/author.rb +146 -0
  264. data/test/models/auto_id.rb +4 -0
  265. data/test/models/binary.rb +2 -0
  266. data/test/models/bird.rb +3 -0
  267. data/test/models/book.rb +4 -0
  268. data/test/models/categorization.rb +5 -0
  269. data/test/models/category.rb +34 -0
  270. data/test/models/citation.rb +6 -0
  271. data/test/models/club.rb +13 -0
  272. data/test/models/column_name.rb +3 -0
  273. data/test/models/comment.rb +29 -0
  274. data/test/models/company.rb +171 -0
  275. data/test/models/company_in_module.rb +61 -0
  276. data/test/models/computer.rb +3 -0
  277. data/test/models/contact.rb +16 -0
  278. data/test/models/contract.rb +5 -0
  279. data/test/models/course.rb +3 -0
  280. data/test/models/customer.rb +73 -0
  281. data/test/models/default.rb +2 -0
  282. data/test/models/developer.rb +101 -0
  283. data/test/models/edge.rb +5 -0
  284. data/test/models/entrant.rb +3 -0
  285. data/test/models/essay.rb +3 -0
  286. data/test/models/event.rb +3 -0
  287. data/test/models/guid.rb +2 -0
  288. data/test/models/item.rb +7 -0
  289. data/test/models/job.rb +5 -0
  290. data/test/models/joke.rb +3 -0
  291. data/test/models/keyboard.rb +3 -0
  292. data/test/models/legacy_thing.rb +3 -0
  293. data/test/models/matey.rb +4 -0
  294. data/test/models/member.rb +12 -0
  295. data/test/models/member_detail.rb +5 -0
  296. data/test/models/member_type.rb +3 -0
  297. data/test/models/membership.rb +9 -0
  298. data/test/models/minimalistic.rb +2 -0
  299. data/test/models/mixed_case_monkey.rb +3 -0
  300. data/test/models/movie.rb +5 -0
  301. data/test/models/order.rb +4 -0
  302. data/test/models/organization.rb +6 -0
  303. data/test/models/owner.rb +5 -0
  304. data/test/models/parrot.rb +16 -0
  305. data/test/models/person.rb +16 -0
  306. data/test/models/pet.rb +5 -0
  307. data/test/models/pirate.rb +70 -0
  308. data/test/models/post.rb +100 -0
  309. data/test/models/price_estimate.rb +3 -0
  310. data/test/models/project.rb +30 -0
  311. data/test/models/reader.rb +4 -0
  312. data/test/models/reference.rb +4 -0
  313. data/test/models/reply.rb +46 -0
  314. data/test/models/ship.rb +10 -0
  315. data/test/models/ship_part.rb +5 -0
  316. data/test/models/sponsor.rb +4 -0
  317. data/test/models/subject.rb +4 -0
  318. data/test/models/subscriber.rb +8 -0
  319. data/test/models/subscription.rb +4 -0
  320. data/test/models/tag.rb +7 -0
  321. data/test/models/tagging.rb +10 -0
  322. data/test/models/task.rb +3 -0
  323. data/test/models/topic.rb +80 -0
  324. data/test/models/toy.rb +6 -0
  325. data/test/models/treasure.rb +8 -0
  326. data/test/models/vertex.rb +9 -0
  327. data/test/models/warehouse_thing.rb +5 -0
  328. data/test/schema/mysql_specific_schema.rb +24 -0
  329. data/test/schema/postgresql_specific_schema.rb +114 -0
  330. data/test/schema/schema.rb +493 -0
  331. data/test/schema/schema2.rb +6 -0
  332. data/test/schema/sqlite_specific_schema.rb +25 -0
  333. metadata +420 -0
@@ -0,0 +1,193 @@
1
+ require "cases/helper"
2
+ require 'models/topic'
3
+ require 'models/developer'
4
+ require 'models/reply'
5
+ require 'models/minimalistic'
6
+
7
+ class Topic; def after_find() end end
8
+ class Developer; def after_find() end end
9
+ class SpecialDeveloper < Developer; end
10
+
11
+ class TopicManualObserver
12
+ include Singleton
13
+
14
+ attr_reader :action, :object, :callbacks
15
+
16
+ def initialize
17
+ Topic.add_observer(self)
18
+ @callbacks = []
19
+ end
20
+
21
+ def update(callback_method, object)
22
+ @callbacks << { "callback_method" => callback_method, "object" => object }
23
+ end
24
+
25
+ def has_been_notified?
26
+ !@callbacks.empty?
27
+ end
28
+ end
29
+
30
+ class TopicaAuditor < ActiveRecord::Observer
31
+ observe :topic
32
+
33
+ attr_reader :topic
34
+
35
+ def after_find(topic)
36
+ @topic = topic
37
+ end
38
+ end
39
+
40
+ class TopicObserver < ActiveRecord::Observer
41
+ attr_reader :topic
42
+
43
+ def after_find(topic)
44
+ @topic = topic
45
+ end
46
+ end
47
+
48
+ class MinimalisticObserver < ActiveRecord::Observer
49
+ attr_reader :minimalistic
50
+
51
+ def after_find(minimalistic)
52
+ @minimalistic = minimalistic
53
+ end
54
+ end
55
+
56
+ class MultiObserver < ActiveRecord::Observer
57
+ attr_reader :record
58
+
59
+ def self.observed_class() [ Topic, Developer ] end
60
+
61
+ cattr_reader :last_inherited
62
+ @@last_inherited = nil
63
+
64
+ def observed_class_inherited_with_testing(subclass)
65
+ observed_class_inherited_without_testing(subclass)
66
+ @@last_inherited = subclass
67
+ end
68
+
69
+ alias_method_chain :observed_class_inherited, :testing
70
+
71
+ def after_find(record)
72
+ @record = record
73
+ end
74
+ end
75
+
76
+ class LifecycleTest < ActiveRecord::TestCase
77
+ fixtures :topics, :developers, :minimalistics
78
+
79
+ def test_before_destroy
80
+ original_count = Topic.count
81
+ (topic_to_be_destroyed = Topic.find(1)).destroy
82
+ assert_equal original_count - (1 + topic_to_be_destroyed.replies.size), Topic.count
83
+ end
84
+
85
+ def test_after_save
86
+ ActiveRecord::Base.observers = :topic_manual_observer
87
+ ActiveRecord::Base.instantiate_observers
88
+
89
+ topic = Topic.find(1)
90
+ topic.title = "hello"
91
+ topic.save
92
+
93
+ assert TopicManualObserver.instance.has_been_notified?
94
+ assert_equal :after_save, TopicManualObserver.instance.callbacks.last["callback_method"]
95
+ end
96
+
97
+ def test_observer_update_on_save
98
+ ActiveRecord::Base.observers = TopicManualObserver
99
+ ActiveRecord::Base.instantiate_observers
100
+
101
+ topic = Topic.find(1)
102
+ assert TopicManualObserver.instance.has_been_notified?
103
+ assert_equal :after_find, TopicManualObserver.instance.callbacks.first["callback_method"]
104
+ end
105
+
106
+ def test_auto_observer
107
+ topic_observer = TopicaAuditor.instance
108
+ assert_nil TopicaAuditor.observed_class
109
+ assert_equal [Topic], TopicaAuditor.instance.observed_classes.to_a
110
+
111
+ topic = Topic.find(1)
112
+ assert_equal topic.title, topic_observer.topic.title
113
+ end
114
+
115
+ def test_inferred_auto_observer
116
+ topic_observer = TopicObserver.instance
117
+ assert_equal Topic, TopicObserver.observed_class
118
+
119
+ topic = Topic.find(1)
120
+ assert_equal topic.title, topic_observer.topic.title
121
+ end
122
+
123
+ def test_observing_two_classes
124
+ multi_observer = MultiObserver.instance
125
+
126
+ topic = Topic.find(1)
127
+ assert_equal topic.title, multi_observer.record.title
128
+
129
+ developer = Developer.find(1)
130
+ assert_equal developer.name, multi_observer.record.name
131
+ end
132
+
133
+ def test_observing_subclasses
134
+ multi_observer = MultiObserver.instance
135
+
136
+ developer = SpecialDeveloper.find(1)
137
+ assert_equal developer.name, multi_observer.record.name
138
+
139
+ klass = Class.new(Developer)
140
+ assert_equal klass, multi_observer.last_inherited
141
+
142
+ developer = klass.find(1)
143
+ assert_equal developer.name, multi_observer.record.name
144
+ end
145
+
146
+ def test_after_find_can_be_observed_when_its_not_defined_on_the_model
147
+ observer = MinimalisticObserver.instance
148
+ assert_equal Minimalistic, MinimalisticObserver.observed_class
149
+
150
+ minimalistic = Minimalistic.find(1)
151
+ assert_equal minimalistic, observer.minimalistic
152
+ end
153
+
154
+ def test_after_find_can_be_observed_when_its_defined_on_the_model
155
+ observer = TopicObserver.instance
156
+ assert_equal Topic, TopicObserver.observed_class
157
+
158
+ topic = Topic.find(1)
159
+ assert_equal topic, observer.topic
160
+ end
161
+
162
+ def test_after_find_is_not_created_if_its_not_used
163
+ # use a fresh class so an observer can't have defined an
164
+ # after_find on it
165
+ model_class = Class.new(ActiveRecord::Base)
166
+ observer_class = Class.new(ActiveRecord::Observer)
167
+ observer_class.observe(model_class)
168
+
169
+ observer = observer_class.instance
170
+
171
+ assert !model_class.method_defined?(:after_find)
172
+ end
173
+
174
+ def test_after_find_is_not_clobbered_if_it_already_exists
175
+ # use a fresh observer class so we can instantiate it (Observer is
176
+ # a Singleton)
177
+ model_class = Class.new(ActiveRecord::Base) do
178
+ def after_find; end
179
+ end
180
+ original_method = model_class.instance_method(:after_find)
181
+ observer_class = Class.new(ActiveRecord::Observer) do
182
+ def after_find; end
183
+ end
184
+ observer_class.observe(model_class)
185
+
186
+ observer = observer_class.instance
187
+ assert_equal original_method, model_class.instance_method(:after_find)
188
+ end
189
+
190
+ def test_invalid_observer
191
+ assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers }
192
+ end
193
+ end
@@ -0,0 +1,304 @@
1
+ require "cases/helper"
2
+ require 'models/person'
3
+ require 'models/reader'
4
+ require 'models/legacy_thing'
5
+ require 'models/reference'
6
+
7
+ class LockWithoutDefault < ActiveRecord::Base; end
8
+
9
+ class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
10
+ set_table_name :lock_without_defaults_cust
11
+ set_locking_column :custom_lock_version
12
+ end
13
+
14
+ class ReadonlyFirstNamePerson < Person
15
+ attr_readonly :first_name
16
+ end
17
+
18
+ class OptimisticLockingTest < ActiveRecord::TestCase
19
+ fixtures :people, :legacy_things, :references
20
+
21
+ # need to disable transactional fixtures, because otherwise the sqlite3
22
+ # adapter (at least) chokes when we try and change the schema in the middle
23
+ # of a test (see test_increment_counter_*).
24
+ self.use_transactional_fixtures = false
25
+
26
+ def test_lock_existing
27
+ p1 = Person.find(1)
28
+ p2 = Person.find(1)
29
+ assert_equal 0, p1.lock_version
30
+ assert_equal 0, p2.lock_version
31
+
32
+ p1.first_name = 'stu'
33
+ p1.save!
34
+ assert_equal 1, p1.lock_version
35
+ assert_equal 0, p2.lock_version
36
+
37
+ p2.first_name = 'sue'
38
+ assert_raise(ActiveRecord::StaleObjectError) { p2.save! }
39
+ end
40
+
41
+ def test_lock_repeating
42
+ p1 = Person.find(1)
43
+ p2 = Person.find(1)
44
+ assert_equal 0, p1.lock_version
45
+ assert_equal 0, p2.lock_version
46
+
47
+ p1.first_name = 'stu'
48
+ p1.save!
49
+ assert_equal 1, p1.lock_version
50
+ assert_equal 0, p2.lock_version
51
+
52
+ p2.first_name = 'sue'
53
+ assert_raise(ActiveRecord::StaleObjectError) { p2.save! }
54
+ p2.first_name = 'sue2'
55
+ assert_raise(ActiveRecord::StaleObjectError) { p2.save! }
56
+ end
57
+
58
+ def test_lock_new
59
+ p1 = Person.new(:first_name => 'anika')
60
+ assert_equal 0, p1.lock_version
61
+
62
+ p1.first_name = 'anika2'
63
+ p1.save!
64
+ p2 = Person.find(p1.id)
65
+ assert_equal 0, p1.lock_version
66
+ assert_equal 0, p2.lock_version
67
+
68
+ p1.first_name = 'anika3'
69
+ p1.save!
70
+ assert_equal 1, p1.lock_version
71
+ assert_equal 0, p2.lock_version
72
+
73
+ p2.first_name = 'sue'
74
+ assert_raise(ActiveRecord::StaleObjectError) { p2.save! }
75
+ end
76
+
77
+ def test_lock_new_with_nil
78
+ p1 = Person.new(:first_name => 'anika')
79
+ p1.save!
80
+ p1.lock_version = nil # simulate bad fixture or column with no default
81
+ p1.save!
82
+ assert_equal 1, p1.lock_version
83
+ end
84
+
85
+
86
+ def test_lock_column_name_existing
87
+ t1 = LegacyThing.find(1)
88
+ t2 = LegacyThing.find(1)
89
+ assert_equal 0, t1.version
90
+ assert_equal 0, t2.version
91
+
92
+ t1.tps_report_number = 700
93
+ t1.save!
94
+ assert_equal 1, t1.version
95
+ assert_equal 0, t2.version
96
+
97
+ t2.tps_report_number = 800
98
+ assert_raise(ActiveRecord::StaleObjectError) { t2.save! }
99
+ end
100
+
101
+ def test_lock_column_is_mass_assignable
102
+ p1 = Person.create(:first_name => 'bianca')
103
+ assert_equal 0, p1.lock_version
104
+ assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
105
+
106
+ p1.first_name = 'bianca2'
107
+ p1.save!
108
+ assert_equal 1, p1.lock_version
109
+ assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
110
+ end
111
+
112
+ def test_lock_without_default_sets_version_to_zero
113
+ t1 = LockWithoutDefault.new
114
+ assert_equal 0, t1.lock_version
115
+ end
116
+
117
+ def test_lock_with_custom_column_without_default_sets_version_to_zero
118
+ t1 = LockWithCustomColumnWithoutDefault.new
119
+ assert_equal 0, t1.custom_lock_version
120
+ end
121
+
122
+ def test_readonly_attributes
123
+ assert_equal Set.new([ 'first_name' ]), ReadonlyFirstNamePerson.readonly_attributes
124
+
125
+ p = ReadonlyFirstNamePerson.create(:first_name => "unchangeable name")
126
+ p.reload
127
+ assert_equal "unchangeable name", p.first_name
128
+
129
+ p.update_attributes(:first_name => "changed name")
130
+ p.reload
131
+ assert_equal "unchangeable name", p.first_name
132
+ end
133
+
134
+ { :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model|
135
+ define_method("test_increment_counter_updates_#{name}") do
136
+ counter_test model, 1 do |id|
137
+ model.increment_counter :test_count, id
138
+ end
139
+ end
140
+
141
+ define_method("test_decrement_counter_updates_#{name}") do
142
+ counter_test model, -1 do |id|
143
+ model.decrement_counter :test_count, id
144
+ end
145
+ end
146
+
147
+ define_method("test_update_counters_updates_#{name}") do
148
+ counter_test model, 1 do |id|
149
+ model.update_counters id, :test_count => 1
150
+ end
151
+ end
152
+ end
153
+
154
+ def test_quote_table_name
155
+ ref = references(:michael_magician)
156
+ ref.favourite = !ref.favourite
157
+ assert ref.save
158
+ end
159
+
160
+ # Useful for partial updates, don't only update the lock_version if there
161
+ # is nothing else being updated.
162
+ def test_update_without_attributes_does_not_only_update_lock_version
163
+ assert_nothing_raised do
164
+ p1 = Person.new(:first_name => 'anika')
165
+ p1.send(:update_with_lock, [])
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ def add_counter_column_to(model)
172
+ model.connection.add_column model.table_name, :test_count, :integer, :null => false, :default => 0
173
+ model.reset_column_information
174
+ # OpenBase does not set a value to existing rows when adding a not null default column
175
+ model.update_all(:test_count => 0) if current_adapter?(:OpenBaseAdapter)
176
+ end
177
+
178
+ def remove_counter_column_from(model)
179
+ model.connection.remove_column model.table_name, :test_count
180
+ model.reset_column_information
181
+ end
182
+
183
+ def counter_test(model, expected_count)
184
+ add_counter_column_to(model)
185
+ object = model.find(:first)
186
+ assert_equal 0, object.test_count
187
+ assert_equal 0, object.send(model.locking_column)
188
+ yield object.id
189
+ object.reload
190
+ assert_equal expected_count, object.test_count
191
+ assert_equal 1, object.send(model.locking_column)
192
+ ensure
193
+ remove_counter_column_from(model)
194
+ end
195
+ end
196
+
197
+
198
+ # TODO: test against the generated SQL since testing locking behavior itself
199
+ # is so cumbersome. Will deadlock Ruby threads if the underlying db.execute
200
+ # blocks, so separate script called by Kernel#system is needed.
201
+ # (See exec vs. async_exec in the PostgreSQL adapter.)
202
+
203
+ # TODO: The Sybase, and OpenBase adapters currently have no support for pessimistic locking
204
+
205
+ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
206
+ class PessimisticLockingTest < ActiveRecord::TestCase
207
+ self.use_transactional_fixtures = false
208
+ fixtures :people, :readers
209
+
210
+ def setup
211
+ # Avoid introspection queries during tests.
212
+ Person.columns; Reader.columns
213
+ end
214
+
215
+ # Test typical find.
216
+ def test_sane_find_with_lock
217
+ assert_nothing_raised do
218
+ Person.transaction do
219
+ Person.find 1, :lock => true
220
+ end
221
+ end
222
+ end
223
+
224
+ # Test scoped lock.
225
+ def test_sane_find_with_scoped_lock
226
+ assert_nothing_raised do
227
+ Person.transaction do
228
+ Person.with_scope(:find => { :lock => true }) do
229
+ Person.find 1
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ # PostgreSQL protests SELECT ... FOR UPDATE on an outer join.
236
+ unless current_adapter?(:PostgreSQLAdapter)
237
+ # Test locked eager find.
238
+ def test_eager_find_with_lock
239
+ assert_nothing_raised do
240
+ Person.transaction do
241
+ Person.find 1, :include => :readers, :lock => true
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ # Locking a record reloads it.
248
+ def test_sane_lock_method
249
+ assert_nothing_raised do
250
+ Person.transaction do
251
+ person = Person.find 1
252
+ old, person.first_name = person.first_name, 'fooman'
253
+ person.lock!
254
+ assert_equal old, person.first_name
255
+ end
256
+ end
257
+ end
258
+
259
+ if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
260
+ use_concurrent_connections
261
+
262
+ def test_no_locks_no_wait
263
+ first, second = duel { Person.find 1 }
264
+ assert first.end > second.end
265
+ end
266
+
267
+ def test_second_lock_waits
268
+ assert [0.2, 1, 5].any? { |zzz|
269
+ first, second = duel(zzz) { Person.find 1, :lock => true }
270
+ second.end > first.end
271
+ }
272
+ end
273
+
274
+ protected
275
+ def duel(zzz = 5)
276
+ t0, t1, t2, t3 = nil, nil, nil, nil
277
+
278
+ a = Thread.new do
279
+ t0 = Time.now
280
+ Person.transaction do
281
+ yield
282
+ sleep zzz # block thread 2 for zzz seconds
283
+ end
284
+ t1 = Time.now
285
+ end
286
+
287
+ b = Thread.new do
288
+ sleep zzz / 2.0 # ensure thread 1 tx starts first
289
+ t2 = Time.now
290
+ Person.transaction { yield }
291
+ t3 = Time.now
292
+ end
293
+
294
+ a.join
295
+ b.join
296
+
297
+ assert t1 > t0 + zzz
298
+ assert t2 > t0
299
+ assert t3 > t2
300
+ [t0.to_f..t1.to_f, t2.to_f..t3.to_f]
301
+ end
302
+ end
303
+ end
304
+ end