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,34 @@
1
+ require 'active_record/connection_adapters/sqlite_adapter'
2
+
3
+ module ActiveRecord
4
+ class Base
5
+ # sqlite3 adapter reuses sqlite_connection.
6
+ def self.sqlite3_connection(config) # :nodoc:
7
+ parse_sqlite_config!(config)
8
+
9
+ unless self.class.const_defined?(:SQLite3)
10
+ require_library_or_gem(config[:adapter])
11
+ end
12
+
13
+ db = SQLite3::Database.new(
14
+ config[:database],
15
+ :results_as_hash => true,
16
+ :type_translation => false
17
+ )
18
+
19
+ db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
20
+
21
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
22
+ end
23
+ end
24
+
25
+ module ConnectionAdapters #:nodoc:
26
+ class SQLite3Adapter < SQLiteAdapter # :nodoc:
27
+ def table_structure(table_name)
28
+ returning structure = @connection.table_info(quote_table_name(table_name)) do
29
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,453 @@
1
+ # encoding: binary
2
+ require 'active_record/connection_adapters/abstract_adapter'
3
+
4
+ module ActiveRecord
5
+ class Base
6
+ class << self
7
+ # Establishes a connection to the database that's used by all Active Record objects
8
+ def sqlite_connection(config) # :nodoc:
9
+ parse_sqlite_config!(config)
10
+
11
+ unless self.class.const_defined?(:SQLite)
12
+ require_library_or_gem(config[:adapter])
13
+
14
+ db = SQLite::Database.new(config[:database], 0)
15
+ db.show_datatypes = "ON" if !defined? SQLite::Version
16
+ db.results_as_hash = true if defined? SQLite::Version
17
+ db.type_translation = false
18
+
19
+ message = "Support for SQLite2Adapter and DeprecatedSQLiteAdapter has been removed from Rails 3. "
20
+ message << "You should migrate to SQLite 3+ or use the plugin from git://github.com/rails/sqlite2_adapter.git with Rails 3."
21
+ ActiveSupport::Deprecation.warn(message)
22
+
23
+ # "Downgrade" deprecated sqlite API
24
+ if SQLite.const_defined?(:Version)
25
+ ConnectionAdapters::SQLite2Adapter.new(db, logger, config)
26
+ else
27
+ ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger, config)
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+ def parse_sqlite_config!(config)
34
+ if config.include?(:dbfile)
35
+ ActiveSupport::Deprecation.warn "Please update config/database.yml to use 'database' instead of 'dbfile'"
36
+ end
37
+
38
+ config[:database] ||= config[:dbfile]
39
+ # Require database.
40
+ unless config[:database]
41
+ raise ArgumentError, "No database file specified. Missing argument: database"
42
+ end
43
+
44
+ # Allow database path relative to RAILS_ROOT, but only if
45
+ # the database path is not the special path that tells
46
+ # Sqlite to build a database only in memory.
47
+ if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
48
+ config[:database] = File.expand_path(config[:database], RAILS_ROOT)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ module ConnectionAdapters #:nodoc:
55
+ class SQLiteColumn < Column #:nodoc:
56
+ class << self
57
+ def string_to_binary(value)
58
+ value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
59
+ value.gsub(/\0|\%/n) do |b|
60
+ case b
61
+ when "\0" then "%00"
62
+ when "%" then "%25"
63
+ end
64
+ end
65
+ end
66
+
67
+ def binary_to_string(value)
68
+ value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
69
+ value.gsub(/%00|%25/n) do |b|
70
+ case b
71
+ when "%00" then "\0"
72
+ when "%25" then "%"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and
80
+ # from http://rubyforge.org/projects/sqlite-ruby/).
81
+ #
82
+ # Options:
83
+ #
84
+ # * <tt>:database</tt> - Path to the database file.
85
+ class SQLiteAdapter < AbstractAdapter
86
+ class Version
87
+ include Comparable
88
+
89
+ def initialize(version_string)
90
+ @version = version_string.split('.').map(&:to_i)
91
+ end
92
+
93
+ def <=>(version_string)
94
+ @version <=> version_string.split('.').map(&:to_i)
95
+ end
96
+ end
97
+
98
+ def initialize(connection, logger, config)
99
+ super(connection, logger)
100
+ @config = config
101
+ end
102
+
103
+ def adapter_name #:nodoc:
104
+ 'SQLite'
105
+ end
106
+
107
+ def supports_ddl_transactions?
108
+ sqlite_version >= '2.0.0'
109
+ end
110
+
111
+ def supports_migrations? #:nodoc:
112
+ true
113
+ end
114
+
115
+ def supports_primary_key? #:nodoc:
116
+ true
117
+ end
118
+
119
+ def requires_reloading?
120
+ true
121
+ end
122
+
123
+ def supports_add_column?
124
+ sqlite_version >= '3.1.6'
125
+ end
126
+
127
+ def disconnect!
128
+ super
129
+ @connection.close rescue nil
130
+ end
131
+
132
+ def supports_count_distinct? #:nodoc:
133
+ sqlite_version >= '3.2.6'
134
+ end
135
+
136
+ def supports_autoincrement? #:nodoc:
137
+ sqlite_version >= '3.1.0'
138
+ end
139
+
140
+ def native_database_types #:nodoc:
141
+ {
142
+ :primary_key => default_primary_key_type,
143
+ :string => { :name => "varchar", :limit => 255 },
144
+ :text => { :name => "text" },
145
+ :integer => { :name => "integer" },
146
+ :float => { :name => "float" },
147
+ :decimal => { :name => "decimal" },
148
+ :datetime => { :name => "datetime" },
149
+ :timestamp => { :name => "datetime" },
150
+ :time => { :name => "time" },
151
+ :date => { :name => "date" },
152
+ :binary => { :name => "blob" },
153
+ :boolean => { :name => "boolean" }
154
+ }
155
+ end
156
+
157
+
158
+ # QUOTING ==================================================
159
+
160
+ def quote_string(s) #:nodoc:
161
+ @connection.class.quote(s)
162
+ end
163
+
164
+ def quote_column_name(name) #:nodoc:
165
+ %Q("#{name}")
166
+ end
167
+
168
+
169
+ # DATABASE STATEMENTS ======================================
170
+
171
+ def execute(sql, name = nil) #:nodoc:
172
+ catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
173
+ end
174
+
175
+ def update_sql(sql, name = nil) #:nodoc:
176
+ super
177
+ @connection.changes
178
+ end
179
+
180
+ def delete_sql(sql, name = nil) #:nodoc:
181
+ sql += " WHERE 1=1" unless sql =~ /WHERE/i
182
+ super sql, name
183
+ end
184
+
185
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
186
+ super || @connection.last_insert_row_id
187
+ end
188
+
189
+ def select_rows(sql, name = nil)
190
+ execute(sql, name).map do |row|
191
+ (0...(row.size / 2)).map { |i| row[i] }
192
+ end
193
+ end
194
+
195
+ def begin_db_transaction #:nodoc:
196
+ catch_schema_changes { @connection.transaction }
197
+ end
198
+
199
+ def commit_db_transaction #:nodoc:
200
+ catch_schema_changes { @connection.commit }
201
+ end
202
+
203
+ def rollback_db_transaction #:nodoc:
204
+ catch_schema_changes { @connection.rollback }
205
+ end
206
+
207
+ # SELECT ... FOR UPDATE is redundant since the table is locked.
208
+ def add_lock!(sql, options) #:nodoc:
209
+ sql
210
+ end
211
+
212
+
213
+ # SCHEMA STATEMENTS ========================================
214
+
215
+ def tables(name = nil) #:nodoc:
216
+ sql = <<-SQL
217
+ SELECT name
218
+ FROM sqlite_master
219
+ WHERE type = 'table' AND NOT name = 'sqlite_sequence'
220
+ SQL
221
+
222
+ execute(sql, name).map do |row|
223
+ row[0]
224
+ end
225
+ end
226
+
227
+ def columns(table_name, name = nil) #:nodoc:
228
+ table_structure(table_name).map do |field|
229
+ SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
230
+ end
231
+ end
232
+
233
+ def indexes(table_name, name = nil) #:nodoc:
234
+ execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
235
+ index = IndexDefinition.new(table_name, row['name'])
236
+ index.unique = row['unique'] != '0'
237
+ index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
238
+ index
239
+ end
240
+ end
241
+
242
+ def primary_key(table_name) #:nodoc:
243
+ column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
244
+ column ? column['name'] : nil
245
+ end
246
+
247
+ def remove_index(table_name, options={}) #:nodoc:
248
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
249
+ end
250
+
251
+ def rename_table(name, new_name)
252
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
253
+ end
254
+
255
+ # See: http://www.sqlite.org/lang_altertable.html
256
+ # SQLite has an additional restriction on the ALTER TABLE statement
257
+ def valid_alter_table_options( type, options)
258
+ type.to_sym != :primary_key
259
+ end
260
+
261
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
262
+ if supports_add_column? && valid_alter_table_options( type, options )
263
+ super(table_name, column_name, type, options)
264
+ else
265
+ alter_table(table_name) do |definition|
266
+ definition.column(column_name, type, options)
267
+ end
268
+ end
269
+ end
270
+
271
+ def remove_column(table_name, *column_names) #:nodoc:
272
+ column_names.flatten.each do |column_name|
273
+ alter_table(table_name) do |definition|
274
+ definition.columns.delete(definition[column_name])
275
+ end
276
+ end
277
+ end
278
+ alias :remove_columns :remove_column
279
+
280
+ def change_column_default(table_name, column_name, default) #:nodoc:
281
+ alter_table(table_name) do |definition|
282
+ definition[column_name].default = default
283
+ end
284
+ end
285
+
286
+ def change_column_null(table_name, column_name, null, default = nil)
287
+ unless null || default.nil?
288
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
289
+ end
290
+ alter_table(table_name) do |definition|
291
+ definition[column_name].null = null
292
+ end
293
+ end
294
+
295
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
296
+ alter_table(table_name) do |definition|
297
+ include_default = options_include_default?(options)
298
+ definition[column_name].instance_eval do
299
+ self.type = type
300
+ self.limit = options[:limit] if options.include?(:limit)
301
+ self.default = options[:default] if include_default
302
+ self.null = options[:null] if options.include?(:null)
303
+ end
304
+ end
305
+ end
306
+
307
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
308
+ unless columns(table_name).detect{|c| c.name == column_name.to_s }
309
+ raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
310
+ end
311
+ alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
312
+ end
313
+
314
+ def empty_insert_statement(table_name)
315
+ "INSERT INTO #{table_name} VALUES(NULL)"
316
+ end
317
+
318
+ protected
319
+ def select(sql, name = nil) #:nodoc:
320
+ execute(sql, name).map do |row|
321
+ record = {}
322
+ row.each_key do |key|
323
+ if key.is_a?(String)
324
+ record[key.sub(/^"?\w+"?\./, '')] = row[key]
325
+ end
326
+ end
327
+ record
328
+ end
329
+ end
330
+
331
+ def table_structure(table_name)
332
+ returning structure = execute("PRAGMA table_info(#{quote_table_name(table_name)})") do
333
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
334
+ end
335
+ end
336
+
337
+ def alter_table(table_name, options = {}) #:nodoc:
338
+ altered_table_name = "altered_#{table_name}"
339
+ caller = lambda {|definition| yield definition if block_given?}
340
+
341
+ transaction do
342
+ move_table(table_name, altered_table_name,
343
+ options.merge(:temporary => true))
344
+ move_table(altered_table_name, table_name, &caller)
345
+ end
346
+ end
347
+
348
+ def move_table(from, to, options = {}, &block) #:nodoc:
349
+ copy_table(from, to, options, &block)
350
+ drop_table(from)
351
+ end
352
+
353
+ def copy_table(from, to, options = {}) #:nodoc:
354
+ options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
355
+ create_table(to, options) do |definition|
356
+ @definition = definition
357
+ columns(from).each do |column|
358
+ column_name = options[:rename] ?
359
+ (options[:rename][column.name] ||
360
+ options[:rename][column.name.to_sym] ||
361
+ column.name) : column.name
362
+
363
+ @definition.column(column_name, column.type,
364
+ :limit => column.limit, :default => column.default,
365
+ :null => column.null)
366
+ end
367
+ @definition.primary_key(primary_key(from)) if primary_key(from)
368
+ yield @definition if block_given?
369
+ end
370
+
371
+ copy_table_indexes(from, to, options[:rename] || {})
372
+ copy_table_contents(from, to,
373
+ @definition.columns.map {|column| column.name},
374
+ options[:rename] || {})
375
+ end
376
+
377
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
378
+ indexes(from).each do |index|
379
+ name = index.name
380
+ if to == "altered_#{from}"
381
+ name = "temp_#{name}"
382
+ elsif from == "altered_#{to}"
383
+ name = name[5..-1]
384
+ end
385
+
386
+ to_column_names = columns(to).map(&:name)
387
+ columns = index.columns.map {|c| rename[c] || c }.select do |column|
388
+ to_column_names.include?(column)
389
+ end
390
+
391
+ unless columns.empty?
392
+ # index name can't be the same
393
+ opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
394
+ opts[:unique] = true if index.unique
395
+ add_index(to, columns, opts)
396
+ end
397
+ end
398
+ end
399
+
400
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
401
+ column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
402
+ rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
403
+ from_columns = columns(from).collect {|col| col.name}
404
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
405
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
406
+
407
+ quoted_to = quote_table_name(to)
408
+ @connection.execute "SELECT * FROM #{quote_table_name(from)}" do |row|
409
+ sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
410
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
411
+ sql << ')'
412
+ @connection.execute sql
413
+ end
414
+ end
415
+
416
+ def catch_schema_changes
417
+ return yield
418
+ rescue ActiveRecord::StatementInvalid => exception
419
+ if exception.message =~ /database schema has changed/
420
+ reconnect!
421
+ retry
422
+ else
423
+ raise
424
+ end
425
+ end
426
+
427
+ def sqlite_version
428
+ @sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
429
+ end
430
+
431
+ def default_primary_key_type
432
+ if supports_autoincrement?
433
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
434
+ else
435
+ 'INTEGER PRIMARY KEY NOT NULL'.freeze
436
+ end
437
+ end
438
+ end
439
+
440
+ class SQLite2Adapter < SQLiteAdapter # :nodoc:
441
+ def rename_table(name, new_name)
442
+ move_table(name, new_name)
443
+ end
444
+ end
445
+
446
+ class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
447
+ def insert(sql, name = nil, pk = nil, id_value = nil)
448
+ execute(sql, name = nil)
449
+ id_value || @connection.last_insert_rowid
450
+ end
451
+ end
452
+ end
453
+ end
@@ -0,0 +1,183 @@
1
+ module ActiveRecord
2
+ # Track unsaved attribute changes.
3
+ #
4
+ # A newly instantiated object is unchanged:
5
+ # person = Person.find_by_name('uncle bob')
6
+ # person.changed? # => false
7
+ #
8
+ # Change the name:
9
+ # person.name = 'Bob'
10
+ # person.changed? # => true
11
+ # person.name_changed? # => true
12
+ # person.name_was # => 'uncle bob'
13
+ # person.name_change # => ['uncle bob', 'Bob']
14
+ # person.name = 'Bill'
15
+ # person.name_change # => ['uncle bob', 'Bill']
16
+ #
17
+ # Save the changes:
18
+ # person.save
19
+ # person.changed? # => false
20
+ # person.name_changed? # => false
21
+ #
22
+ # Assigning the same value leaves the attribute unchanged:
23
+ # person.name = 'Bill'
24
+ # person.name_changed? # => false
25
+ # person.name_change # => nil
26
+ #
27
+ # Which attributes have changed?
28
+ # person.name = 'bob'
29
+ # person.changed # => ['name']
30
+ # person.changes # => { 'name' => ['Bill', 'bob'] }
31
+ #
32
+ # Before modifying an attribute in-place:
33
+ # person.name_will_change!
34
+ # person.name << 'by'
35
+ # person.name_change # => ['uncle bob', 'uncle bobby']
36
+ module Dirty
37
+ DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
38
+
39
+ def self.included(base)
40
+ base.attribute_method_suffix *DIRTY_SUFFIXES
41
+ base.alias_method_chain :write_attribute, :dirty
42
+ base.alias_method_chain :save, :dirty
43
+ base.alias_method_chain :save!, :dirty
44
+ base.alias_method_chain :update, :dirty
45
+ base.alias_method_chain :reload, :dirty
46
+
47
+ base.superclass_delegating_accessor :partial_updates
48
+ base.partial_updates = true
49
+
50
+ base.send(:extend, ClassMethods)
51
+ end
52
+
53
+ # Do any attributes have unsaved changes?
54
+ # person.changed? # => false
55
+ # person.name = 'bob'
56
+ # person.changed? # => true
57
+ def changed?
58
+ !changed_attributes.empty?
59
+ end
60
+
61
+ # List of attributes with unsaved changes.
62
+ # person.changed # => []
63
+ # person.name = 'bob'
64
+ # person.changed # => ['name']
65
+ def changed
66
+ changed_attributes.keys
67
+ end
68
+
69
+ # Map of changed attrs => [original value, new value].
70
+ # person.changes # => {}
71
+ # person.name = 'bob'
72
+ # person.changes # => { 'name' => ['bill', 'bob'] }
73
+ def changes
74
+ changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
75
+ end
76
+
77
+ # Attempts to +save+ the record and clears changed attributes if successful.
78
+ def save_with_dirty(*args) #:nodoc:
79
+ if status = save_without_dirty(*args)
80
+ changed_attributes.clear
81
+ end
82
+ status
83
+ end
84
+
85
+ # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
86
+ def save_with_dirty!(*args) #:nodoc:
87
+ status = save_without_dirty!(*args)
88
+ changed_attributes.clear
89
+ status
90
+ end
91
+
92
+ # <tt>reload</tt> the record and clears changed attributes.
93
+ def reload_with_dirty(*args) #:nodoc:
94
+ record = reload_without_dirty(*args)
95
+ changed_attributes.clear
96
+ record
97
+ end
98
+
99
+ private
100
+ # Map of change <tt>attr => original value</tt>.
101
+ def changed_attributes
102
+ @changed_attributes ||= {}
103
+ end
104
+
105
+ # Handle <tt>*_changed?</tt> for +method_missing+.
106
+ def attribute_changed?(attr)
107
+ changed_attributes.include?(attr)
108
+ end
109
+
110
+ # Handle <tt>*_change</tt> for +method_missing+.
111
+ def attribute_change(attr)
112
+ [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
113
+ end
114
+
115
+ # Handle <tt>*_was</tt> for +method_missing+.
116
+ def attribute_was(attr)
117
+ attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
118
+ end
119
+
120
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
121
+ def attribute_will_change!(attr)
122
+ changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
123
+ end
124
+
125
+ # Wrap write_attribute to remember original attribute value.
126
+ def write_attribute_with_dirty(attr, value)
127
+ attr = attr.to_s
128
+
129
+ # The attribute already has an unsaved change.
130
+ if changed_attributes.include?(attr)
131
+ old = changed_attributes[attr]
132
+ changed_attributes.delete(attr) unless field_changed?(attr, old, value)
133
+ else
134
+ old = clone_attribute_value(:read_attribute, attr)
135
+ changed_attributes[attr] = old if field_changed?(attr, old, value)
136
+ end
137
+
138
+ # Carry on.
139
+ write_attribute_without_dirty(attr, value)
140
+ end
141
+
142
+ def update_with_dirty
143
+ if partial_updates?
144
+ # Serialized attributes should always be written in case they've been
145
+ # changed in place.
146
+ update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
147
+ else
148
+ update_without_dirty
149
+ end
150
+ end
151
+
152
+ def field_changed?(attr, old, value)
153
+ if column = column_for_attribute(attr)
154
+ if column.number? && column.null && (old.nil? || old == 0) && value.blank?
155
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
156
+ # Hence we don't record it as a change if the value changes from nil to ''.
157
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
158
+ # be typecast back to 0 (''.to_i => 0)
159
+ value = nil
160
+ else
161
+ value = column.type_cast(value)
162
+ end
163
+ end
164
+
165
+ old != value
166
+ end
167
+
168
+ module ClassMethods
169
+ def self.extended(base)
170
+ base.metaclass.alias_method_chain(:alias_attribute, :dirty)
171
+ end
172
+
173
+ def alias_attribute_with_dirty(new_name, old_name)
174
+ alias_attribute_without_dirty(new_name, old_name)
175
+ DIRTY_SUFFIXES.each do |suffix|
176
+ module_eval <<-STR, __FILE__, __LINE__+1
177
+ def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end
178
+ STR
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,41 @@
1
+ module ActiveRecord
2
+ class DynamicFinderMatch
3
+ def self.match(method)
4
+ df_match = self.new(method)
5
+ df_match.finder ? df_match : nil
6
+ end
7
+
8
+ def initialize(method)
9
+ @finder = :first
10
+ case method.to_s
11
+ when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/
12
+ @finder = :last if $1 == 'last_by'
13
+ @finder = :all if $1 == 'all_by'
14
+ names = $2
15
+ when /^find_by_([_a-zA-Z]\w*)\!$/
16
+ @bang = true
17
+ names = $1
18
+ when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
19
+ @instantiator = $1 == 'initialize' ? :new : :create
20
+ names = $2
21
+ else
22
+ @finder = nil
23
+ end
24
+ @attribute_names = names && names.split('_and_')
25
+ end
26
+
27
+ attr_reader :finder, :attribute_names, :instantiator
28
+
29
+ def finder?
30
+ !@finder.nil? && @instantiator.nil?
31
+ end
32
+
33
+ def instantiator?
34
+ @finder == :first && !@instantiator.nil?
35
+ end
36
+
37
+ def bang?
38
+ @bang
39
+ end
40
+ end
41
+ end