activerecord 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (311) hide show
  1. data/CHANGELOG +4928 -3
  2. data/README +45 -46
  3. data/RUNNING_UNIT_TESTS +8 -11
  4. data/Rakefile +247 -0
  5. data/install.rb +8 -38
  6. data/lib/active_record/aggregations.rb +64 -49
  7. data/lib/active_record/associations/association_collection.rb +217 -47
  8. data/lib/active_record/associations/association_proxy.rb +159 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
  12. data/lib/active_record/associations/has_many_association.rb +145 -75
  13. data/lib/active_record/associations/has_many_through_association.rb +283 -0
  14. data/lib/active_record/associations/has_one_association.rb +96 -0
  15. data/lib/active_record/associations.rb +1537 -304
  16. data/lib/active_record/attribute_methods.rb +328 -0
  17. data/lib/active_record/base.rb +2001 -588
  18. data/lib/active_record/calculations.rb +269 -0
  19. data/lib/active_record/callbacks.rb +169 -165
  20. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
  22. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  23. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
  31. data/lib/active_record/fixtures.rb +946 -100
  32. data/lib/active_record/locking/optimistic.rb +144 -0
  33. data/lib/active_record/locking/pessimistic.rb +77 -0
  34. data/lib/active_record/migration.rb +417 -0
  35. data/lib/active_record/observer.rb +142 -32
  36. data/lib/active_record/query_cache.rb +23 -0
  37. data/lib/active_record/reflection.rb +163 -70
  38. data/lib/active_record/schema.rb +58 -0
  39. data/lib/active_record/schema_dumper.rb +171 -0
  40. data/lib/active_record/serialization.rb +98 -0
  41. data/lib/active_record/serializers/json_serializer.rb +71 -0
  42. data/lib/active_record/serializers/xml_serializer.rb +315 -0
  43. data/lib/active_record/timestamp.rb +41 -0
  44. data/lib/active_record/transactions.rb +87 -57
  45. data/lib/active_record/validations.rb +909 -122
  46. data/lib/active_record/vendor/db2.rb +362 -0
  47. data/lib/active_record/vendor/mysql.rb +126 -29
  48. data/lib/active_record/version.rb +9 -0
  49. data/lib/active_record.rb +35 -7
  50. data/lib/activerecord.rb +1 -0
  51. data/test/aaa_create_tables_test.rb +72 -0
  52. data/test/abstract_unit.rb +73 -5
  53. data/test/active_schema_test_mysql.rb +43 -0
  54. data/test/adapter_test.rb +105 -0
  55. data/test/adapter_test_sqlserver.rb +95 -0
  56. data/test/aggregations_test.rb +110 -16
  57. data/test/all.sh +2 -2
  58. data/test/ar_schema_test.rb +33 -0
  59. data/test/association_inheritance_reload.rb +14 -0
  60. data/test/associations/ar_joins_test.rb +0 -0
  61. data/test/associations/callbacks_test.rb +147 -0
  62. data/test/associations/cascaded_eager_loading_test.rb +110 -0
  63. data/test/associations/eager_singularization_test.rb +145 -0
  64. data/test/associations/eager_test.rb +442 -0
  65. data/test/associations/extension_test.rb +47 -0
  66. data/test/associations/inner_join_association_test.rb +88 -0
  67. data/test/associations/join_model_test.rb +553 -0
  68. data/test/associations_test.rb +1930 -267
  69. data/test/attribute_methods_test.rb +146 -0
  70. data/test/base_test.rb +1316 -84
  71. data/test/binary_test.rb +32 -0
  72. data/test/calculations_test.rb +251 -0
  73. data/test/callbacks_test.rb +400 -0
  74. data/test/class_inheritable_attributes_test.rb +3 -4
  75. data/test/column_alias_test.rb +17 -0
  76. data/test/connection_test_firebird.rb +8 -0
  77. data/test/connection_test_mysql.rb +30 -0
  78. data/test/connections/native_db2/connection.rb +25 -0
  79. data/test/connections/native_firebird/connection.rb +26 -0
  80. data/test/connections/native_frontbase/connection.rb +27 -0
  81. data/test/connections/native_mysql/connection.rb +21 -18
  82. data/test/connections/native_openbase/connection.rb +21 -0
  83. data/test/connections/native_oracle/connection.rb +27 -0
  84. data/test/connections/native_postgresql/connection.rb +17 -18
  85. data/test/connections/native_sqlite/connection.rb +17 -16
  86. data/test/connections/native_sqlite3/connection.rb +25 -0
  87. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  88. data/test/connections/native_sybase/connection.rb +23 -0
  89. data/test/copy_table_test_sqlite.rb +69 -0
  90. data/test/datatype_test_postgresql.rb +203 -0
  91. data/test/date_time_test.rb +37 -0
  92. data/test/default_test_firebird.rb +16 -0
  93. data/test/defaults_test.rb +67 -0
  94. data/test/deprecated_finder_test.rb +30 -0
  95. data/test/finder_test.rb +607 -32
  96. data/test/fixtures/accounts.yml +28 -0
  97. data/test/fixtures/all/developers.yml +0 -0
  98. data/test/fixtures/all/people.csv +0 -0
  99. data/test/fixtures/all/tasks.yml +0 -0
  100. data/test/fixtures/author.rb +107 -0
  101. data/test/fixtures/author_favorites.yml +4 -0
  102. data/test/fixtures/authors.yml +7 -0
  103. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  104. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  105. data/test/fixtures/bad_fixtures/blank_line +3 -0
  106. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  107. data/test/fixtures/bad_fixtures/missing_value +1 -0
  108. data/test/fixtures/binaries.yml +132 -0
  109. data/test/fixtures/binary.rb +2 -0
  110. data/test/fixtures/book.rb +4 -0
  111. data/test/fixtures/books.yml +7 -0
  112. data/test/fixtures/categories/special_categories.yml +9 -0
  113. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  114. data/test/fixtures/categories.yml +14 -0
  115. data/test/fixtures/categories_ordered.yml +7 -0
  116. data/test/fixtures/categories_posts.yml +23 -0
  117. data/test/fixtures/categorization.rb +5 -0
  118. data/test/fixtures/categorizations.yml +17 -0
  119. data/test/fixtures/category.rb +26 -0
  120. data/test/fixtures/citation.rb +6 -0
  121. data/test/fixtures/comment.rb +23 -0
  122. data/test/fixtures/comments.yml +59 -0
  123. data/test/fixtures/companies.yml +55 -0
  124. data/test/fixtures/company.rb +81 -4
  125. data/test/fixtures/company_in_module.rb +32 -6
  126. data/test/fixtures/computer.rb +4 -0
  127. data/test/fixtures/computers.yml +4 -0
  128. data/test/fixtures/contact.rb +16 -0
  129. data/test/fixtures/courses.yml +7 -0
  130. data/test/fixtures/customer.rb +28 -3
  131. data/test/fixtures/customers.yml +17 -0
  132. data/test/fixtures/db_definitions/db2.drop.sql +33 -0
  133. data/test/fixtures/db_definitions/db2.sql +235 -0
  134. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  135. data/test/fixtures/db_definitions/db22.sql +5 -0
  136. data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
  137. data/test/fixtures/db_definitions/firebird.sql +310 -0
  138. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  139. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  140. data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
  141. data/test/fixtures/db_definitions/frontbase.sql +273 -0
  142. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  143. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  144. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  145. data/test/fixtures/db_definitions/openbase.sql +318 -0
  146. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  148. data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
  149. data/test/fixtures/db_definitions/oracle.sql +330 -0
  150. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  151. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  152. data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
  153. data/test/fixtures/db_definitions/postgresql.sql +217 -38
  154. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/postgresql2.sql +2 -2
  156. data/test/fixtures/db_definitions/schema.rb +354 -0
  157. data/test/fixtures/db_definitions/schema2.rb +11 -0
  158. data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
  159. data/test/fixtures/db_definitions/sqlite.sql +139 -5
  160. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  161. data/test/fixtures/db_definitions/sqlite2.sql +1 -0
  162. data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
  163. data/test/fixtures/db_definitions/sybase.sql +222 -0
  164. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  165. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  166. data/test/fixtures/developer.rb +70 -6
  167. data/test/fixtures/developers.yml +21 -0
  168. data/test/fixtures/developers_projects/david_action_controller +2 -1
  169. data/test/fixtures/developers_projects/david_active_record +2 -1
  170. data/test/fixtures/developers_projects.yml +17 -0
  171. data/test/fixtures/edge.rb +5 -0
  172. data/test/fixtures/edges.yml +6 -0
  173. data/test/fixtures/entrants.yml +14 -0
  174. data/test/fixtures/example.log +1 -0
  175. data/test/fixtures/fk_test_has_fk.yml +3 -0
  176. data/test/fixtures/fk_test_has_pk.yml +2 -0
  177. data/test/fixtures/flowers.jpg +0 -0
  178. data/test/fixtures/funny_jokes.yml +10 -0
  179. data/test/fixtures/item.rb +7 -0
  180. data/test/fixtures/items.yml +4 -0
  181. data/test/fixtures/joke.rb +3 -0
  182. data/test/fixtures/keyboard.rb +3 -0
  183. data/test/fixtures/legacy_thing.rb +3 -0
  184. data/test/fixtures/legacy_things.yml +3 -0
  185. data/test/fixtures/matey.rb +4 -0
  186. data/test/fixtures/mateys.yml +4 -0
  187. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  188. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  189. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  190. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  191. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  192. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  193. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  194. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  195. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  196. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  197. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  198. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  199. data/test/fixtures/minimalistic.rb +2 -0
  200. data/test/fixtures/minimalistics.yml +2 -0
  201. data/test/fixtures/mixed_case_monkey.rb +3 -0
  202. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  203. data/test/fixtures/mixins.yml +29 -0
  204. data/test/fixtures/movies.yml +7 -0
  205. data/test/fixtures/naked/csv/accounts.csv +1 -0
  206. data/test/fixtures/naked/yml/accounts.yml +1 -0
  207. data/test/fixtures/naked/yml/companies.yml +1 -0
  208. data/test/fixtures/naked/yml/courses.yml +1 -0
  209. data/test/fixtures/order.rb +4 -0
  210. data/test/fixtures/parrot.rb +13 -0
  211. data/test/fixtures/parrots.yml +27 -0
  212. data/test/fixtures/parrots_pirates.yml +7 -0
  213. data/test/fixtures/people.yml +3 -0
  214. data/test/fixtures/person.rb +4 -0
  215. data/test/fixtures/pirate.rb +5 -0
  216. data/test/fixtures/pirates.yml +9 -0
  217. data/test/fixtures/post.rb +59 -0
  218. data/test/fixtures/posts.yml +48 -0
  219. data/test/fixtures/project.rb +27 -2
  220. data/test/fixtures/projects.yml +7 -0
  221. data/test/fixtures/reader.rb +4 -0
  222. data/test/fixtures/readers.yml +4 -0
  223. data/test/fixtures/reply.rb +18 -2
  224. data/test/fixtures/reserved_words/distinct.yml +5 -0
  225. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  226. data/test/fixtures/reserved_words/group.yml +14 -0
  227. data/test/fixtures/reserved_words/select.yml +8 -0
  228. data/test/fixtures/reserved_words/values.yml +7 -0
  229. data/test/fixtures/ship.rb +3 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/subject.rb +4 -0
  232. data/test/fixtures/subscriber.rb +4 -3
  233. data/test/fixtures/tag.rb +7 -0
  234. data/test/fixtures/tagging.rb +10 -0
  235. data/test/fixtures/taggings.yml +25 -0
  236. data/test/fixtures/tags.yml +7 -0
  237. data/test/fixtures/task.rb +3 -0
  238. data/test/fixtures/tasks.yml +7 -0
  239. data/test/fixtures/topic.rb +20 -3
  240. data/test/fixtures/topics.yml +22 -0
  241. data/test/fixtures/treasure.rb +4 -0
  242. data/test/fixtures/treasures.yml +10 -0
  243. data/test/fixtures/vertex.rb +9 -0
  244. data/test/fixtures/vertices.yml +4 -0
  245. data/test/fixtures_test.rb +574 -8
  246. data/test/inheritance_test.rb +113 -27
  247. data/test/json_serialization_test.rb +180 -0
  248. data/test/lifecycle_test.rb +56 -29
  249. data/test/locking_test.rb +273 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +933 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_test.rb +95 -0
  254. data/test/modules_test.rb +23 -10
  255. data/test/multiple_db_test.rb +17 -3
  256. data/test/pk_test.rb +59 -15
  257. data/test/query_cache_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +124 -27
  260. data/test/reserved_word_test_mysql.rb +177 -0
  261. data/test/schema_authorization_test_postgresql.rb +75 -0
  262. data/test/schema_dumper_test.rb +131 -0
  263. data/test/schema_test_postgresql.rb +64 -0
  264. data/test/serialization_test.rb +47 -0
  265. data/test/synonym_test_oracle.rb +17 -0
  266. data/test/table_name_test_sqlserver.rb +23 -0
  267. data/test/threaded_connections_test.rb +48 -0
  268. data/test/transactions_test.rb +227 -29
  269. data/test/unconnected_test.rb +14 -6
  270. data/test/validations_test.rb +1293 -32
  271. data/test/xml_serialization_test.rb +202 -0
  272. metadata +347 -143
  273. data/dev-utils/eval_debugger.rb +0 -9
  274. data/examples/associations.rb +0 -87
  275. data/examples/shared_setup.rb +0 -15
  276. data/examples/validation.rb +0 -88
  277. data/lib/active_record/deprecated_associations.rb +0 -70
  278. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  279. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  280. data/lib/active_record/support/clean_logger.rb +0 -10
  281. data/lib/active_record/support/inflector.rb +0 -70
  282. data/lib/active_record/vendor/simple.rb +0 -702
  283. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  284. data/lib/active_record/wrappings.rb +0 -59
  285. data/rakefile +0 -122
  286. data/test/deprecated_associations_test.rb +0 -336
  287. data/test/fixtures/accounts/signals37 +0 -3
  288. data/test/fixtures/accounts/unknown +0 -2
  289. data/test/fixtures/companies/first_client +0 -6
  290. data/test/fixtures/companies/first_firm +0 -4
  291. data/test/fixtures/companies/second_client +0 -6
  292. data/test/fixtures/courses/java +0 -2
  293. data/test/fixtures/courses/ruby +0 -2
  294. data/test/fixtures/customers/david +0 -6
  295. data/test/fixtures/db_definitions/mysql.sql +0 -96
  296. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  297. data/test/fixtures/developers/david +0 -2
  298. data/test/fixtures/developers/jamis +0 -2
  299. data/test/fixtures/entrants/first +0 -3
  300. data/test/fixtures/entrants/second +0 -3
  301. data/test/fixtures/entrants/third +0 -3
  302. data/test/fixtures/fixture_database.sqlite +0 -0
  303. data/test/fixtures/fixture_database_2.sqlite +0 -0
  304. data/test/fixtures/movies/first +0 -2
  305. data/test/fixtures/movies/second +0 -2
  306. data/test/fixtures/projects/action_controller +0 -2
  307. data/test/fixtures/projects/active_record +0 -2
  308. data/test/fixtures/topics/first +0 -9
  309. data/test/fixtures/topics/second +0 -8
  310. data/test/inflector_test.rb +0 -104
  311. data/test/thread_safety_test.rb +0 -33
@@ -0,0 +1,472 @@
1
+ require 'date'
2
+ require 'bigdecimal'
3
+ require 'bigdecimal/util'
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters #:nodoc:
7
+ # An abstract definition of a column in a table.
8
+ class Column
9
+ module Format
10
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
11
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
12
+ end
13
+
14
+ attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
15
+ attr_accessor :primary
16
+
17
+ # Instantiates a new column in the table.
18
+ #
19
+ # +name+ is the column's name, as in <tt><b>supplier_id</b> int(11)</tt>.
20
+ # +default+ is the type-casted default value, such as <tt>sales_stage varchar(20) default <b>'new'</b></tt>.
21
+ # +sql_type+ is only used to extract the column's length, if necessary. For example, <tt>company_name varchar(<b>60</b>)</tt>.
22
+ # +null+ determines if this column allows +NULL+ values.
23
+ def initialize(name, default, sql_type = nil, null = true)
24
+ @name, @sql_type, @null = name, sql_type, null
25
+ @limit, @precision, @scale = extract_limit(sql_type), extract_precision(sql_type), extract_scale(sql_type)
26
+ @type = simplified_type(sql_type)
27
+ @default = extract_default(default)
28
+
29
+ @primary = nil
30
+ end
31
+
32
+ def text?
33
+ [:string, :text].include? type
34
+ end
35
+
36
+ def number?
37
+ [:float, :integer, :decimal].include? type
38
+ end
39
+
40
+ # Returns the Ruby class that corresponds to the abstract data type.
41
+ def klass
42
+ case type
43
+ when :integer then Fixnum
44
+ when :float then Float
45
+ when :decimal then BigDecimal
46
+ when :datetime then Time
47
+ when :date then Date
48
+ when :timestamp then Time
49
+ when :time then Time
50
+ when :text, :string then String
51
+ when :binary then String
52
+ when :boolean then Object
53
+ end
54
+ end
55
+
56
+ # Casts value (which is a String) to an appropriate instance.
57
+ def type_cast(value)
58
+ return nil if value.nil?
59
+ case type
60
+ when :string then value
61
+ when :text then value
62
+ when :integer then value.to_i rescue value ? 1 : 0
63
+ when :float then value.to_f
64
+ when :decimal then self.class.value_to_decimal(value)
65
+ when :datetime then self.class.string_to_time(value)
66
+ when :timestamp then self.class.string_to_time(value)
67
+ when :time then self.class.string_to_dummy_time(value)
68
+ when :date then self.class.string_to_date(value)
69
+ when :binary then self.class.binary_to_string(value)
70
+ when :boolean then self.class.value_to_boolean(value)
71
+ else value
72
+ end
73
+ end
74
+
75
+ def type_cast_code(var_name)
76
+ case type
77
+ when :string then nil
78
+ when :text then nil
79
+ when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
80
+ when :float then "#{var_name}.to_f"
81
+ when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
82
+ when :datetime then "#{self.class.name}.string_to_time(#{var_name})"
83
+ when :timestamp then "#{self.class.name}.string_to_time(#{var_name})"
84
+ when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})"
85
+ when :date then "#{self.class.name}.string_to_date(#{var_name})"
86
+ when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
87
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
88
+ else nil
89
+ end
90
+ end
91
+
92
+ # Returns the human name of the column name.
93
+ #
94
+ # ===== Examples
95
+ # Column.new('sales_stage', ...).human_name #=> 'Sales stage'
96
+ def human_name
97
+ Base.human_attribute_name(@name)
98
+ end
99
+
100
+ def extract_default(default)
101
+ type_cast(default)
102
+ end
103
+
104
+ class << self
105
+ # Used to convert from Strings to BLOBs
106
+ def string_to_binary(value)
107
+ value
108
+ end
109
+
110
+ # Used to convert from BLOBs to Strings
111
+ def binary_to_string(value)
112
+ value
113
+ end
114
+
115
+ def string_to_date(string)
116
+ return string unless string.is_a?(String)
117
+ return nil if string.empty?
118
+
119
+ fast_string_to_date(string) || fallback_string_to_date(string)
120
+ end
121
+
122
+ def string_to_time(string)
123
+ return string unless string.is_a?(String)
124
+ return nil if string.empty?
125
+
126
+ fast_string_to_time(string) || fallback_string_to_time(string)
127
+ end
128
+
129
+ def string_to_dummy_time(string)
130
+ return string unless string.is_a?(String)
131
+ return nil if string.empty?
132
+
133
+ string_to_time "2000-01-01 #{string}"
134
+ end
135
+
136
+ # convert something to a boolean
137
+ def value_to_boolean(value)
138
+ if value == true || value == false
139
+ value
140
+ else
141
+ %w(true t 1).include?(value.to_s.downcase)
142
+ end
143
+ end
144
+
145
+ # convert something to a BigDecimal
146
+ def value_to_decimal(value)
147
+ if value.is_a?(BigDecimal)
148
+ value
149
+ elsif value.respond_to?(:to_d)
150
+ value.to_d
151
+ else
152
+ value.to_s.to_d
153
+ end
154
+ end
155
+
156
+ protected
157
+ # '0.123456' -> 123456
158
+ # '1.123456' -> 123456
159
+ def microseconds(time)
160
+ ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
161
+ end
162
+
163
+ def new_date(year, mon, mday)
164
+ if year && year != 0
165
+ Date.new(year, mon, mday) rescue nil
166
+ end
167
+ end
168
+
169
+ def new_time(year, mon, mday, hour, min, sec, microsec)
170
+ # Treat 0000-00-00 00:00:00 as nil.
171
+ return nil if year.nil? || year == 0
172
+
173
+ Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec)
174
+ # Over/underflow to DateTime
175
+ rescue ArgumentError, TypeError
176
+ zone_offset = Base.default_timezone == :local ? DateTime.local_offset : 0
177
+ DateTime.civil(year, mon, mday, hour, min, sec, zone_offset) rescue nil
178
+ end
179
+
180
+ def fast_string_to_date(string)
181
+ if string =~ Format::ISO_DATE
182
+ new_date $1.to_i, $2.to_i, $3.to_i
183
+ end
184
+ end
185
+
186
+ # Doesn't handle time zones.
187
+ def fast_string_to_time(string)
188
+ if string =~ Format::ISO_DATETIME
189
+ microsec = ($7.to_f * 1_000_000).to_i
190
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
191
+ end
192
+ end
193
+
194
+ def fallback_string_to_date(string)
195
+ new_date *ParseDate.parsedate(string)[0..2]
196
+ end
197
+
198
+ def fallback_string_to_time(string)
199
+ time_hash = Date._parse(string)
200
+ time_hash[:sec_fraction] = microseconds(time_hash)
201
+
202
+ new_time *time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)
203
+ end
204
+ end
205
+
206
+ private
207
+ def extract_limit(sql_type)
208
+ $1.to_i if sql_type =~ /\((.*)\)/
209
+ end
210
+
211
+ def extract_precision(sql_type)
212
+ $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
213
+ end
214
+
215
+ def extract_scale(sql_type)
216
+ case sql_type
217
+ when /^(numeric|decimal|number)\((\d+)\)/i then 0
218
+ when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
219
+ end
220
+ end
221
+
222
+ def simplified_type(field_type)
223
+ case field_type
224
+ when /int/i
225
+ :integer
226
+ when /float|double/i
227
+ :float
228
+ when /decimal|numeric|number/i
229
+ extract_scale(field_type) == 0 ? :integer : :decimal
230
+ when /datetime/i
231
+ :datetime
232
+ when /timestamp/i
233
+ :timestamp
234
+ when /time/i
235
+ :time
236
+ when /date/i
237
+ :date
238
+ when /clob/i, /text/i
239
+ :text
240
+ when /blob/i, /binary/i
241
+ :binary
242
+ when /char/i, /string/i
243
+ :string
244
+ when /boolean/i
245
+ :boolean
246
+ end
247
+ end
248
+ end
249
+
250
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
251
+ end
252
+
253
+ class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
254
+
255
+ def sql_type
256
+ base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
257
+ end
258
+
259
+ def to_sql
260
+ column_sql = "#{base.quote_column_name(name)} #{sql_type}"
261
+ add_column_options!(column_sql, :null => null, :default => default) unless type.to_sym == :primary_key
262
+ column_sql
263
+ end
264
+ alias to_s :to_sql
265
+
266
+ private
267
+
268
+ def add_column_options!(sql, options)
269
+ base.add_column_options!(sql, options.merge(:column => self))
270
+ end
271
+ end
272
+
273
+ # Represents a SQL table in an abstract way.
274
+ # Columns are stored as a ColumnDefinition in the #columns attribute.
275
+ class TableDefinition
276
+ attr_accessor :columns
277
+
278
+ def initialize(base)
279
+ @columns = []
280
+ @base = base
281
+ end
282
+
283
+ # Appends a primary key definition to the table definition.
284
+ # Can be called multiple times, but this is probably not a good idea.
285
+ def primary_key(name)
286
+ column(name, :primary_key)
287
+ end
288
+
289
+ # Returns a ColumnDefinition for the column with name +name+.
290
+ def [](name)
291
+ @columns.find {|column| column.name.to_s == name.to_s}
292
+ end
293
+
294
+ # Instantiates a new column for the table.
295
+ # The +type+ parameter is normally one of the migrations native types,
296
+ # which is one of the following:
297
+ # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
298
+ # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
299
+ # <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
300
+ # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>.
301
+ #
302
+ # You may use a type not in this list as long as it is supported by your
303
+ # database (for example, "polygon" in MySQL), but this will not be database
304
+ # agnostic and should usually be avoided.
305
+ #
306
+ # Available options are (none of these exists by default):
307
+ # * <tt>:limit</tt> -
308
+ # Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
309
+ # <tt>:binary</tt> or <tt>:integer</tt> columns only)
310
+ # * <tt>:default</tt> -
311
+ # The column's default value. Use nil for NULL.
312
+ # * <tt>:null</tt> -
313
+ # Allows or disallows +NULL+ values in the column. This option could
314
+ # have been named <tt>:null_allowed</tt>.
315
+ # * <tt>:precision</tt> -
316
+ # Specifies the precision for a <tt>:decimal</tt> column.
317
+ # * <tt>:scale</tt> -
318
+ # Specifies the scale for a <tt>:decimal</tt> column.
319
+ #
320
+ # Please be aware of different RDBMS implementations behavior with
321
+ # <tt>:decimal</tt> columns:
322
+ # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
323
+ # <tt>:precision</tt>, and makes no comments about the requirements of
324
+ # <tt>:precision</tt>.
325
+ # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
326
+ # Default is (10,0).
327
+ # * PostgreSQL: <tt>:precision</tt> [1..infinity],
328
+ # <tt>:scale</tt> [0..infinity]. No default.
329
+ # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
330
+ # Internal storage as strings. No default.
331
+ # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
332
+ # but the maximum supported <tt>:precision</tt> is 16. No default.
333
+ # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
334
+ # Default is (38,0).
335
+ # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
336
+ # Default unknown.
337
+ # * Firebird: <tt>:precision</tt> [1..18], <tt>:scale</tt> [0..18].
338
+ # Default (9,0). Internal types NUMERIC and DECIMAL have different
339
+ # storage rules, decimal being better.
340
+ # * FrontBase?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
341
+ # Default (38,0). WARNING Max <tt>:precision</tt>/<tt>:scale</tt> for
342
+ # NUMERIC is 19, and DECIMAL is 38.
343
+ # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
344
+ # Default (38,0).
345
+ # * Sybase: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
346
+ # Default (38,0).
347
+ # * OpenBase?: Documentation unclear. Claims storage in <tt>double</tt>.
348
+ #
349
+ # This method returns <tt>self</tt>.
350
+ #
351
+ # == Examples
352
+ # # Assuming td is an instance of TableDefinition
353
+ # td.column(:granted, :boolean)
354
+ # #=> granted BOOLEAN
355
+ #
356
+ # td.column(:picture, :binary, :limit => 2.megabytes)
357
+ # #=> picture BLOB(2097152)
358
+ #
359
+ # td.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
360
+ # #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
361
+ #
362
+ # def.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2)
363
+ # #=> bill_gates_money DECIMAL(15,2)
364
+ #
365
+ # def.column(:sensor_reading, :decimal, :precision => 30, :scale => 20)
366
+ # #=> sensor_reading DECIMAL(30,20)
367
+ #
368
+ # # While <tt>:scale</tt> defaults to zero on most databases, it
369
+ # # probably wouldn't hurt to include it.
370
+ # def.column(:huge_integer, :decimal, :precision => 30)
371
+ # #=> huge_integer DECIMAL(30)
372
+ #
373
+ # == Short-hand examples
374
+ #
375
+ # Instead of calling column directly, you can also work with the short-hand definitions for the default types.
376
+ # They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
377
+ # in a single statement.
378
+ #
379
+ # What can be written like this with the regular calls to column:
380
+ #
381
+ # create_table "products", :force => true do |t|
382
+ # t.column "shop_id", :integer
383
+ # t.column "creator_id", :integer
384
+ # t.column "name", :string, :default => "Untitled"
385
+ # t.column "value", :string, :default => "Untitled"
386
+ # t.column "created_at", :datetime
387
+ # t.column "updated_at", :datetime
388
+ # end
389
+ #
390
+ # Can also be written as follows using the short-hand:
391
+ #
392
+ # create_table :products do |t|
393
+ # t.integer :shop_id, :creator_id
394
+ # t.string :name, :value, :default => "Untitled"
395
+ # t.timestamps
396
+ # end
397
+ #
398
+ # There's a short-hand method for each of the type values declared at the top. And then there's
399
+ # TableDefinition#timestamps that'll add created_at and updated_at as datetimes.
400
+ #
401
+ # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
402
+ # column if the :polymorphic option is supplied. If :polymorphic is a hash of options, these will be
403
+ # used when creating the _type column. So what can be written like this:
404
+ #
405
+ # create_table :taggings do |t|
406
+ # t.integer :tag_id, :tagger_id, :taggable_id
407
+ # t.string :tagger_type
408
+ # t.string :taggable_type, :default => 'Photo'
409
+ # end
410
+ #
411
+ # Can also be written as follows using references:
412
+ #
413
+ # create_table :taggings do |t|
414
+ # t.references :tag
415
+ # t.references :tagger, :polymorphic => true
416
+ # t.references :taggable, :polymorphic => { :default => 'Photo' }
417
+ # end
418
+ def column(name, type, options = {})
419
+ column = self[name] || ColumnDefinition.new(@base, name, type)
420
+ column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
421
+ column.precision = options[:precision]
422
+ column.scale = options[:scale]
423
+ column.default = options[:default]
424
+ column.null = options[:null]
425
+ @columns << column unless @columns.include? column
426
+ self
427
+ end
428
+
429
+ %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
430
+ class_eval <<-EOV
431
+ def #{column_type}(*args)
432
+ options = args.extract_options!
433
+ column_names = args
434
+
435
+ column_names.each { |name| column(name, '#{column_type}', options) }
436
+ end
437
+ EOV
438
+ end
439
+
440
+ # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
441
+ # <tt>:updated_at</tt> to the table.
442
+ def timestamps
443
+ column(:created_at, :datetime)
444
+ column(:updated_at, :datetime)
445
+ end
446
+
447
+ def references(*args)
448
+ options = args.extract_options!
449
+ polymorphic = options.delete(:polymorphic)
450
+ args.each do |col|
451
+ column("#{col}_id", :integer, options)
452
+ unless polymorphic.nil?
453
+ column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : {})
454
+ end
455
+ end
456
+ end
457
+ alias :belongs_to :references
458
+
459
+ # Returns a String whose contents are the column definitions
460
+ # concatenated together. This string can then be prepended and appended to
461
+ # to generate the final SQL to create the table.
462
+ def to_sql
463
+ @columns * ', '
464
+ end
465
+
466
+ private
467
+ def native
468
+ @base.native_database_types
469
+ end
470
+ end
471
+ end
472
+ end