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,357 @@
1
+ module ActiveRecord #:nodoc:
2
+ module Serialization
3
+ # Builds an XML document to represent the model. Some configuration is
4
+ # available through +options+. However more complicated cases should
5
+ # override ActiveRecord::Base#to_xml.
6
+ #
7
+ # By default the generated XML document will include the processing
8
+ # instruction and all the object's attributes. For example:
9
+ #
10
+ # <?xml version="1.0" encoding="UTF-8"?>
11
+ # <topic>
12
+ # <title>The First Topic</title>
13
+ # <author-name>David</author-name>
14
+ # <id type="integer">1</id>
15
+ # <approved type="boolean">false</approved>
16
+ # <replies-count type="integer">0</replies-count>
17
+ # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
18
+ # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
19
+ # <content>Have a nice day</content>
20
+ # <author-email-address>david@loudthinking.com</author-email-address>
21
+ # <parent-id></parent-id>
22
+ # <last-read type="date">2004-04-15</last-read>
23
+ # </topic>
24
+ #
25
+ # This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
26
+ # <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
27
+ # The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
28
+ # +attributes+ method. The default is to dasherize all column names, but you
29
+ # can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
30
+ # to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
31
+ # To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
32
+ #
33
+ # For instance:
34
+ #
35
+ # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
36
+ #
37
+ # <topic>
38
+ # <title>The First Topic</title>
39
+ # <author-name>David</author-name>
40
+ # <approved type="boolean">false</approved>
41
+ # <content>Have a nice day</content>
42
+ # <author-email-address>david@loudthinking.com</author-email-address>
43
+ # <parent-id></parent-id>
44
+ # <last-read type="date">2004-04-15</last-read>
45
+ # </topic>
46
+ #
47
+ # To include first level associations use <tt>:include</tt>:
48
+ #
49
+ # firm.to_xml :include => [ :account, :clients ]
50
+ #
51
+ # <?xml version="1.0" encoding="UTF-8"?>
52
+ # <firm>
53
+ # <id type="integer">1</id>
54
+ # <rating type="integer">1</rating>
55
+ # <name>37signals</name>
56
+ # <clients type="array">
57
+ # <client>
58
+ # <rating type="integer">1</rating>
59
+ # <name>Summit</name>
60
+ # </client>
61
+ # <client>
62
+ # <rating type="integer">1</rating>
63
+ # <name>Microsoft</name>
64
+ # </client>
65
+ # </clients>
66
+ # <account>
67
+ # <id type="integer">1</id>
68
+ # <credit-limit type="integer">50</credit-limit>
69
+ # </account>
70
+ # </firm>
71
+ #
72
+ # To include deeper levels of associations pass a hash like this:
73
+ #
74
+ # firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
75
+ # <?xml version="1.0" encoding="UTF-8"?>
76
+ # <firm>
77
+ # <id type="integer">1</id>
78
+ # <rating type="integer">1</rating>
79
+ # <name>37signals</name>
80
+ # <clients type="array">
81
+ # <client>
82
+ # <rating type="integer">1</rating>
83
+ # <name>Summit</name>
84
+ # <address>
85
+ # ...
86
+ # </address>
87
+ # </client>
88
+ # <client>
89
+ # <rating type="integer">1</rating>
90
+ # <name>Microsoft</name>
91
+ # <address>
92
+ # ...
93
+ # </address>
94
+ # </client>
95
+ # </clients>
96
+ # <account>
97
+ # <id type="integer">1</id>
98
+ # <credit-limit type="integer">50</credit-limit>
99
+ # </account>
100
+ # </firm>
101
+ #
102
+ # To include any methods on the model being called use <tt>:methods</tt>:
103
+ #
104
+ # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
105
+ #
106
+ # <firm>
107
+ # # ... normal attributes as shown above ...
108
+ # <calculated-earnings>100000000000000000</calculated-earnings>
109
+ # <real-earnings>5</real-earnings>
110
+ # </firm>
111
+ #
112
+ # To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
113
+ # modified version of the options hash that was given to +to_xml+:
114
+ #
115
+ # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
116
+ # firm.to_xml :procs => [ proc ]
117
+ #
118
+ # <firm>
119
+ # # ... normal attributes as shown above ...
120
+ # <abc>def</abc>
121
+ # </firm>
122
+ #
123
+ # Alternatively, you can yield the builder object as part of the +to_xml+ call:
124
+ #
125
+ # firm.to_xml do |xml|
126
+ # xml.creator do
127
+ # xml.first_name "David"
128
+ # xml.last_name "Heinemeier Hansson"
129
+ # end
130
+ # end
131
+ #
132
+ # <firm>
133
+ # # ... normal attributes as shown above ...
134
+ # <creator>
135
+ # <first_name>David</first_name>
136
+ # <last_name>Heinemeier Hansson</last_name>
137
+ # </creator>
138
+ # </firm>
139
+ #
140
+ # As noted above, you may override +to_xml+ in your ActiveRecord::Base
141
+ # subclasses to have complete control about what's generated. The general
142
+ # form of doing this is:
143
+ #
144
+ # class IHaveMyOwnXML < ActiveRecord::Base
145
+ # def to_xml(options = {})
146
+ # options[:indent] ||= 2
147
+ # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
148
+ # xml.instruct! unless options[:skip_instruct]
149
+ # xml.level_one do
150
+ # xml.tag!(:second_level, 'content')
151
+ # end
152
+ # end
153
+ # end
154
+ def to_xml(options = {}, &block)
155
+ serializer = XmlSerializer.new(self, options)
156
+ block_given? ? serializer.to_s(&block) : serializer.to_s
157
+ end
158
+
159
+ def from_xml(xml)
160
+ self.attributes = Hash.from_xml(xml).values.first
161
+ self
162
+ end
163
+ end
164
+
165
+ class XmlSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
166
+ def builder
167
+ @builder ||= begin
168
+ options[:indent] ||= 2
169
+ builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
170
+
171
+ unless options[:skip_instruct]
172
+ builder.instruct!
173
+ options[:skip_instruct] = true
174
+ end
175
+
176
+ builder
177
+ end
178
+ end
179
+
180
+ def root
181
+ root = (options[:root] || @record.class.model_name.singular).to_s
182
+ reformat_name(root)
183
+ end
184
+
185
+ def dasherize?
186
+ !options.has_key?(:dasherize) || options[:dasherize]
187
+ end
188
+
189
+ def camelize?
190
+ options.has_key?(:camelize) && options[:camelize]
191
+ end
192
+
193
+ def reformat_name(name)
194
+ name = name.camelize if camelize?
195
+ dasherize? ? name.dasherize : name
196
+ end
197
+
198
+ def serializable_attributes
199
+ serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
200
+ end
201
+
202
+ def serializable_method_attributes
203
+ Array(options[:methods]).inject([]) do |method_attributes, name|
204
+ method_attributes << MethodAttribute.new(name.to_s, @record) if @record.respond_to?(name.to_s)
205
+ method_attributes
206
+ end
207
+ end
208
+
209
+ def add_attributes
210
+ (serializable_attributes + serializable_method_attributes).each do |attribute|
211
+ add_tag(attribute)
212
+ end
213
+ end
214
+
215
+ def add_procs
216
+ if procs = options.delete(:procs)
217
+ [ *procs ].each do |proc|
218
+ proc.call(options)
219
+ end
220
+ end
221
+ end
222
+
223
+ def add_tag(attribute)
224
+ builder.tag!(
225
+ reformat_name(attribute.name),
226
+ attribute.value.to_s,
227
+ attribute.decorations(!options[:skip_types])
228
+ )
229
+ end
230
+
231
+ def add_associations(association, records, opts)
232
+ if records.is_a?(Enumerable)
233
+ tag = reformat_name(association.to_s)
234
+ type = options[:skip_types] ? {} : {:type => "array"}
235
+
236
+ if records.empty?
237
+ builder.tag!(tag, type)
238
+ else
239
+ builder.tag!(tag, type) do
240
+ association_name = association.to_s.singularize
241
+ records.each do |record|
242
+ if options[:skip_types]
243
+ record_type = {}
244
+ else
245
+ record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
246
+ record_type = {:type => record_class}
247
+ end
248
+
249
+ record.to_xml opts.merge(:root => association_name).merge(record_type)
250
+ end
251
+ end
252
+ end
253
+ else
254
+ if record = @record.send(association)
255
+ record.to_xml(opts.merge(:root => association))
256
+ end
257
+ end
258
+ end
259
+
260
+ def serialize
261
+ args = [root]
262
+ if options[:namespace]
263
+ args << {:xmlns=>options[:namespace]}
264
+ end
265
+
266
+ if options[:type]
267
+ args << {:type=>options[:type]}
268
+ end
269
+
270
+ builder.tag!(*args) do
271
+ add_attributes
272
+ procs = options.delete(:procs)
273
+ add_includes { |association, records, opts| add_associations(association, records, opts) }
274
+ options[:procs] = procs
275
+ add_procs
276
+ yield builder if block_given?
277
+ end
278
+ end
279
+
280
+ class Attribute #:nodoc:
281
+ attr_reader :name, :value, :type
282
+
283
+ def initialize(name, record)
284
+ @name, @record = name, record
285
+
286
+ @type = compute_type
287
+ @value = compute_value
288
+ end
289
+
290
+ # There is a significant speed improvement if the value
291
+ # does not need to be escaped, as <tt>tag!</tt> escapes all values
292
+ # to ensure that valid XML is generated. For known binary
293
+ # values, it is at least an order of magnitude faster to
294
+ # Base64 encode binary values and directly put them in the
295
+ # output XML than to pass the original value or the Base64
296
+ # encoded value to the <tt>tag!</tt> method. It definitely makes
297
+ # no sense to Base64 encode the value and then give it to
298
+ # <tt>tag!</tt>, since that just adds additional overhead.
299
+ def needs_encoding?
300
+ ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
301
+ end
302
+
303
+ def decorations(include_types = true)
304
+ decorations = {}
305
+
306
+ if type == :binary
307
+ decorations[:encoding] = 'base64'
308
+ end
309
+
310
+ if include_types && type != :string
311
+ decorations[:type] = type
312
+ end
313
+
314
+ if value.nil?
315
+ decorations[:nil] = true
316
+ end
317
+
318
+ decorations
319
+ end
320
+
321
+ protected
322
+ def compute_type
323
+ type = if @record.class.serialized_attributes.has_key?(name)
324
+ :yaml
325
+ else
326
+ @record.class.columns_hash[name].try(:type)
327
+ end
328
+
329
+ case type
330
+ when :text
331
+ :string
332
+ when :time
333
+ :datetime
334
+ else
335
+ type
336
+ end
337
+ end
338
+
339
+ def compute_value
340
+ value = @record.send(name)
341
+
342
+ if formatter = Hash::XML_FORMATTING[type.to_s]
343
+ value ? formatter.call(value) : nil
344
+ else
345
+ value
346
+ end
347
+ end
348
+ end
349
+
350
+ class MethodAttribute < Attribute #:nodoc:
351
+ protected
352
+ def compute_type
353
+ Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string
354
+ end
355
+ end
356
+ end
357
+ end
@@ -0,0 +1,326 @@
1
+ module ActiveRecord
2
+ # A session store backed by an Active Record class. A default class is
3
+ # provided, but any object duck-typing to an Active Record Session class
4
+ # with text +session_id+ and +data+ attributes is sufficient.
5
+ #
6
+ # The default assumes a +sessions+ tables with columns:
7
+ # +id+ (numeric primary key),
8
+ # +session_id+ (text, or longtext if your session data exceeds 65K), and
9
+ # +data+ (text or longtext; careful if your session data exceeds 65KB).
10
+ # The +session_id+ column should always be indexed for speedy lookups.
11
+ # Session data is marshaled to the +data+ column in Base64 format.
12
+ # If the data you write is larger than the column's size limit,
13
+ # ActionController::SessionOverflowError will be raised.
14
+ #
15
+ # You may configure the table name, primary key, and data column.
16
+ # For example, at the end of <tt>config/environment.rb</tt>:
17
+ # ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
18
+ # ActiveRecord::SessionStore::Session.primary_key = 'session_id'
19
+ # ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
20
+ # Note that setting the primary key to the +session_id+ frees you from
21
+ # having a separate +id+ column if you don't want it. However, you must
22
+ # set <tt>session.model.id = session.session_id</tt> by hand! A before filter
23
+ # on ApplicationController is a good place.
24
+ #
25
+ # Since the default class is a simple Active Record, you get timestamps
26
+ # for free if you add +created_at+ and +updated_at+ datetime columns to
27
+ # the +sessions+ table, making periodic session expiration a snap.
28
+ #
29
+ # You may provide your own session class implementation, whether a
30
+ # feature-packed Active Record or a bare-metal high-performance SQL
31
+ # store, by setting
32
+ # ActiveRecord::SessionStore.session_class = MySessionClass
33
+ # You must implement these methods:
34
+ # self.find_by_session_id(session_id)
35
+ # initialize(hash_of_session_id_and_data)
36
+ # attr_reader :session_id
37
+ # attr_accessor :data
38
+ # save
39
+ # destroy
40
+ #
41
+ # The example SqlBypass class is a generic SQL session store. You may
42
+ # use it as a basis for high-performance database-specific stores.
43
+ class SessionStore < ActionController::Session::AbstractStore
44
+ # The default Active Record class.
45
+ class Session < ActiveRecord::Base
46
+ ##
47
+ # :singleton-method:
48
+ # Customizable data column name. Defaults to 'data'.
49
+ cattr_accessor :data_column_name
50
+ self.data_column_name = 'data'
51
+
52
+ before_save :marshal_data!
53
+ before_save :raise_on_session_data_overflow!
54
+
55
+ class << self
56
+ def data_column_size_limit
57
+ @data_column_size_limit ||= columns_hash[@@data_column_name].limit
58
+ end
59
+
60
+ # Hook to set up sessid compatibility.
61
+ def find_by_session_id(session_id)
62
+ setup_sessid_compatibility!
63
+ find_by_session_id(session_id)
64
+ end
65
+
66
+ def marshal(data)
67
+ ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
68
+ end
69
+
70
+ def unmarshal(data)
71
+ Marshal.load(ActiveSupport::Base64.decode64(data)) if data
72
+ end
73
+
74
+ def create_table!
75
+ connection.execute <<-end_sql
76
+ CREATE TABLE #{table_name} (
77
+ id INTEGER PRIMARY KEY,
78
+ #{connection.quote_column_name('session_id')} TEXT UNIQUE,
79
+ #{connection.quote_column_name(@@data_column_name)} TEXT(255)
80
+ )
81
+ end_sql
82
+ end
83
+
84
+ def drop_table!
85
+ connection.execute "DROP TABLE #{table_name}"
86
+ end
87
+
88
+ private
89
+ # Compatibility with tables using sessid instead of session_id.
90
+ def setup_sessid_compatibility!
91
+ # Reset column info since it may be stale.
92
+ reset_column_information
93
+ if columns_hash['sessid']
94
+ def self.find_by_session_id(*args)
95
+ find_by_sessid(*args)
96
+ end
97
+
98
+ define_method(:session_id) { sessid }
99
+ define_method(:session_id=) { |session_id| self.sessid = session_id }
100
+ else
101
+ def self.find_by_session_id(session_id)
102
+ find :first, :conditions => {:session_id=>session_id}
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ # Lazy-unmarshal session state.
109
+ def data
110
+ @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
111
+ end
112
+
113
+ attr_writer :data
114
+
115
+ # Has the session been loaded yet?
116
+ def loaded?
117
+ !!@data
118
+ end
119
+
120
+ private
121
+ def marshal_data!
122
+ return false if !loaded?
123
+ write_attribute(@@data_column_name, self.class.marshal(self.data))
124
+ end
125
+
126
+ # Ensures that the data about to be stored in the database is not
127
+ # larger than the data storage column. Raises
128
+ # ActionController::SessionOverflowError.
129
+ def raise_on_session_data_overflow!
130
+ return false if !loaded?
131
+ limit = self.class.data_column_size_limit
132
+ if loaded? and limit and read_attribute(@@data_column_name).size > limit
133
+ raise ActionController::SessionOverflowError
134
+ end
135
+ end
136
+ end
137
+
138
+ # A barebones session store which duck-types with the default session
139
+ # store but bypasses Active Record and issues SQL directly. This is
140
+ # an example session model class meant as a basis for your own classes.
141
+ #
142
+ # The database connection, table name, and session id and data columns
143
+ # are configurable class attributes. Marshaling and unmarshaling
144
+ # are implemented as class methods that you may override. By default,
145
+ # marshaling data is
146
+ #
147
+ # ActiveSupport::Base64.encode64(Marshal.dump(data))
148
+ #
149
+ # and unmarshaling data is
150
+ #
151
+ # Marshal.load(ActiveSupport::Base64.decode64(data))
152
+ #
153
+ # This marshaling behavior is intended to store the widest range of
154
+ # binary session data in a +text+ column. For higher performance,
155
+ # store in a +blob+ column instead and forgo the Base64 encoding.
156
+ class SqlBypass
157
+ ##
158
+ # :singleton-method:
159
+ # Use the ActiveRecord::Base.connection by default.
160
+ cattr_accessor :connection
161
+
162
+ ##
163
+ # :singleton-method:
164
+ # The table name defaults to 'sessions'.
165
+ cattr_accessor :table_name
166
+ @@table_name = 'sessions'
167
+
168
+ ##
169
+ # :singleton-method:
170
+ # The session id field defaults to 'session_id'.
171
+ cattr_accessor :session_id_column
172
+ @@session_id_column = 'session_id'
173
+
174
+ ##
175
+ # :singleton-method:
176
+ # The data field defaults to 'data'.
177
+ cattr_accessor :data_column
178
+ @@data_column = 'data'
179
+
180
+ class << self
181
+ def connection
182
+ @@connection ||= ActiveRecord::Base.connection
183
+ end
184
+
185
+ # Look up a session by id and unmarshal its data if found.
186
+ def find_by_session_id(session_id)
187
+ if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
188
+ new(:session_id => session_id, :marshaled_data => record['data'])
189
+ end
190
+ end
191
+
192
+ def marshal(data)
193
+ ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
194
+ end
195
+
196
+ def unmarshal(data)
197
+ Marshal.load(ActiveSupport::Base64.decode64(data)) if data
198
+ end
199
+
200
+ def create_table!
201
+ @@connection.execute <<-end_sql
202
+ CREATE TABLE #{table_name} (
203
+ id INTEGER PRIMARY KEY,
204
+ #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
205
+ #{@@connection.quote_column_name(data_column)} TEXT
206
+ )
207
+ end_sql
208
+ end
209
+
210
+ def drop_table!
211
+ @@connection.execute "DROP TABLE #{table_name}"
212
+ end
213
+ end
214
+
215
+ attr_reader :session_id
216
+ attr_writer :data
217
+
218
+ # Look for normal and marshaled data, self.find_by_session_id's way of
219
+ # telling us to postpone unmarshaling until the data is requested.
220
+ # We need to handle a normal data attribute in case of a new record.
221
+ def initialize(attributes)
222
+ @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
223
+ @new_record = @marshaled_data.nil?
224
+ end
225
+
226
+ def new_record?
227
+ @new_record
228
+ end
229
+
230
+ # Lazy-unmarshal session state.
231
+ def data
232
+ unless @data
233
+ if @marshaled_data
234
+ @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
235
+ else
236
+ @data = {}
237
+ end
238
+ end
239
+ @data
240
+ end
241
+
242
+ def loaded?
243
+ !!@data
244
+ end
245
+
246
+ def save
247
+ return false if !loaded?
248
+ marshaled_data = self.class.marshal(data)
249
+
250
+ if @new_record
251
+ @new_record = false
252
+ @@connection.update <<-end_sql, 'Create session'
253
+ INSERT INTO #{@@table_name} (
254
+ #{@@connection.quote_column_name(@@session_id_column)},
255
+ #{@@connection.quote_column_name(@@data_column)} )
256
+ VALUES (
257
+ #{@@connection.quote(session_id)},
258
+ #{@@connection.quote(marshaled_data)} )
259
+ end_sql
260
+ else
261
+ @@connection.update <<-end_sql, 'Update session'
262
+ UPDATE #{@@table_name}
263
+ SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
264
+ WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
265
+ end_sql
266
+ end
267
+ end
268
+
269
+ def destroy
270
+ unless @new_record
271
+ @@connection.delete <<-end_sql, 'Destroy session'
272
+ DELETE FROM #{@@table_name}
273
+ WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
274
+ end_sql
275
+ end
276
+ end
277
+ end
278
+
279
+ # The class used for session storage. Defaults to
280
+ # ActiveRecord::SessionStore::Session
281
+ cattr_accessor :session_class
282
+ self.session_class = Session
283
+
284
+ SESSION_RECORD_KEY = 'rack.session.record'.freeze
285
+
286
+ private
287
+ def get_session(env, sid)
288
+ Base.silence do
289
+ sid ||= generate_sid
290
+ session = find_session(sid)
291
+ env[SESSION_RECORD_KEY] = session
292
+ [sid, session.data]
293
+ end
294
+ end
295
+
296
+ def set_session(env, sid, session_data)
297
+ Base.silence do
298
+ record = get_session_model(env, sid)
299
+ record.data = session_data
300
+ return false unless record.save
301
+
302
+ session_data = record.data
303
+ if session_data && session_data.respond_to?(:each_value)
304
+ session_data.each_value do |obj|
305
+ obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
306
+ end
307
+ end
308
+ end
309
+
310
+ return true
311
+ end
312
+
313
+ def get_session_model(env, sid)
314
+ if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
315
+ env[SESSION_RECORD_KEY] = find_session(sid)
316
+ else
317
+ env[SESSION_RECORD_KEY] ||= find_session(sid)
318
+ end
319
+ end
320
+
321
+ def find_session(id)
322
+ @@session_class.find_by_session_id(id) ||
323
+ @@session_class.new(:session_id => id, :data => {})
324
+ end
325
+ end
326
+ end