activerecord_authorails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. data/CHANGELOG +3043 -0
  2. data/README +360 -0
  3. data/RUNNING_UNIT_TESTS +64 -0
  4. data/Rakefile +226 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +85 -0
  9. data/install.rb +30 -0
  10. data/lib/active_record.rb +85 -0
  11. data/lib/active_record/acts/list.rb +244 -0
  12. data/lib/active_record/acts/nested_set.rb +211 -0
  13. data/lib/active_record/acts/tree.rb +89 -0
  14. data/lib/active_record/aggregations.rb +191 -0
  15. data/lib/active_record/associations.rb +1637 -0
  16. data/lib/active_record/associations/association_collection.rb +190 -0
  17. data/lib/active_record/associations/association_proxy.rb +158 -0
  18. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  19. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  20. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +169 -0
  21. data/lib/active_record/associations/has_many_association.rb +210 -0
  22. data/lib/active_record/associations/has_many_through_association.rb +247 -0
  23. data/lib/active_record/associations/has_one_association.rb +80 -0
  24. data/lib/active_record/attribute_methods.rb +75 -0
  25. data/lib/active_record/base.rb +2164 -0
  26. data/lib/active_record/calculations.rb +270 -0
  27. data/lib/active_record/callbacks.rb +367 -0
  28. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +279 -0
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +58 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +343 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +161 -0
  34. data/lib/active_record/connection_adapters/db2_adapter.rb +228 -0
  35. data/lib/active_record/connection_adapters/firebird_adapter.rb +728 -0
  36. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +414 -0
  38. data/lib/active_record/connection_adapters/openbase_adapter.rb +350 -0
  39. data/lib/active_record/connection_adapters/oracle_adapter.rb +689 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +584 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +407 -0
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +591 -0
  43. data/lib/active_record/connection_adapters/sybase_adapter.rb +662 -0
  44. data/lib/active_record/deprecated_associations.rb +104 -0
  45. data/lib/active_record/deprecated_finders.rb +44 -0
  46. data/lib/active_record/fixtures.rb +628 -0
  47. data/lib/active_record/locking/optimistic.rb +106 -0
  48. data/lib/active_record/locking/pessimistic.rb +77 -0
  49. data/lib/active_record/migration.rb +394 -0
  50. data/lib/active_record/observer.rb +178 -0
  51. data/lib/active_record/query_cache.rb +64 -0
  52. data/lib/active_record/reflection.rb +222 -0
  53. data/lib/active_record/schema.rb +58 -0
  54. data/lib/active_record/schema_dumper.rb +149 -0
  55. data/lib/active_record/timestamp.rb +51 -0
  56. data/lib/active_record/transactions.rb +136 -0
  57. data/lib/active_record/validations.rb +843 -0
  58. data/lib/active_record/vendor/db2.rb +362 -0
  59. data/lib/active_record/vendor/mysql.rb +1214 -0
  60. data/lib/active_record/vendor/simple.rb +693 -0
  61. data/lib/active_record/version.rb +9 -0
  62. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  63. data/lib/active_record/wrappings.rb +58 -0
  64. data/lib/active_record/xml_serialization.rb +308 -0
  65. data/test/aaa_create_tables_test.rb +59 -0
  66. data/test/abstract_unit.rb +77 -0
  67. data/test/active_schema_test_mysql.rb +31 -0
  68. data/test/adapter_test.rb +87 -0
  69. data/test/adapter_test_sqlserver.rb +81 -0
  70. data/test/aggregations_test.rb +95 -0
  71. data/test/all.sh +8 -0
  72. data/test/ar_schema_test.rb +33 -0
  73. data/test/association_inheritance_reload.rb +14 -0
  74. data/test/associations/callbacks_test.rb +126 -0
  75. data/test/associations/cascaded_eager_loading_test.rb +138 -0
  76. data/test/associations/eager_test.rb +393 -0
  77. data/test/associations/extension_test.rb +42 -0
  78. data/test/associations/join_model_test.rb +497 -0
  79. data/test/associations_test.rb +1809 -0
  80. data/test/attribute_methods_test.rb +49 -0
  81. data/test/base_test.rb +1586 -0
  82. data/test/binary_test.rb +37 -0
  83. data/test/calculations_test.rb +219 -0
  84. data/test/callbacks_test.rb +377 -0
  85. data/test/class_inheritable_attributes_test.rb +32 -0
  86. data/test/column_alias_test.rb +17 -0
  87. data/test/connection_test_firebird.rb +8 -0
  88. data/test/connections/native_db2/connection.rb +25 -0
  89. data/test/connections/native_firebird/connection.rb +26 -0
  90. data/test/connections/native_frontbase/connection.rb +27 -0
  91. data/test/connections/native_mysql/connection.rb +24 -0
  92. data/test/connections/native_openbase/connection.rb +21 -0
  93. data/test/connections/native_oracle/connection.rb +27 -0
  94. data/test/connections/native_postgresql/connection.rb +23 -0
  95. data/test/connections/native_sqlite/connection.rb +34 -0
  96. data/test/connections/native_sqlite3/connection.rb +34 -0
  97. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  98. data/test/connections/native_sqlserver/connection.rb +23 -0
  99. data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
  100. data/test/connections/native_sybase/connection.rb +23 -0
  101. data/test/copy_table_sqlite.rb +64 -0
  102. data/test/datatype_test_postgresql.rb +52 -0
  103. data/test/default_test_firebird.rb +16 -0
  104. data/test/defaults_test.rb +60 -0
  105. data/test/deprecated_associations_test.rb +396 -0
  106. data/test/deprecated_finder_test.rb +151 -0
  107. data/test/empty_date_time_test.rb +25 -0
  108. data/test/finder_test.rb +504 -0
  109. data/test/fixtures/accounts.yml +28 -0
  110. data/test/fixtures/author.rb +99 -0
  111. data/test/fixtures/author_favorites.yml +4 -0
  112. data/test/fixtures/authors.yml +7 -0
  113. data/test/fixtures/auto_id.rb +4 -0
  114. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  115. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  116. data/test/fixtures/bad_fixtures/blank_line +3 -0
  117. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  118. data/test/fixtures/bad_fixtures/missing_value +1 -0
  119. data/test/fixtures/binary.rb +2 -0
  120. data/test/fixtures/categories.yml +14 -0
  121. data/test/fixtures/categories/special_categories.yml +9 -0
  122. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  123. data/test/fixtures/categories_ordered.yml +7 -0
  124. data/test/fixtures/categories_posts.yml +23 -0
  125. data/test/fixtures/categorization.rb +5 -0
  126. data/test/fixtures/categorizations.yml +17 -0
  127. data/test/fixtures/category.rb +20 -0
  128. data/test/fixtures/column_name.rb +3 -0
  129. data/test/fixtures/comment.rb +23 -0
  130. data/test/fixtures/comments.yml +59 -0
  131. data/test/fixtures/companies.yml +55 -0
  132. data/test/fixtures/company.rb +107 -0
  133. data/test/fixtures/company_in_module.rb +59 -0
  134. data/test/fixtures/computer.rb +3 -0
  135. data/test/fixtures/computers.yml +4 -0
  136. data/test/fixtures/course.rb +3 -0
  137. data/test/fixtures/courses.yml +7 -0
  138. data/test/fixtures/customer.rb +55 -0
  139. data/test/fixtures/customers.yml +17 -0
  140. data/test/fixtures/db_definitions/db2.drop.sql +32 -0
  141. data/test/fixtures/db_definitions/db2.sql +231 -0
  142. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  143. data/test/fixtures/db_definitions/db22.sql +5 -0
  144. data/test/fixtures/db_definitions/firebird.drop.sql +63 -0
  145. data/test/fixtures/db_definitions/firebird.sql +304 -0
  146. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  148. data/test/fixtures/db_definitions/frontbase.drop.sql +32 -0
  149. data/test/fixtures/db_definitions/frontbase.sql +268 -0
  150. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  151. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  152. data/test/fixtures/db_definitions/mysql.drop.sql +32 -0
  153. data/test/fixtures/db_definitions/mysql.sql +234 -0
  154. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  156. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  157. data/test/fixtures/db_definitions/openbase.sql +302 -0
  158. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  159. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  160. data/test/fixtures/db_definitions/oracle.drop.sql +65 -0
  161. data/test/fixtures/db_definitions/oracle.sql +325 -0
  162. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  163. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  164. data/test/fixtures/db_definitions/postgresql.drop.sql +37 -0
  165. data/test/fixtures/db_definitions/postgresql.sql +263 -0
  166. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  167. data/test/fixtures/db_definitions/postgresql2.sql +5 -0
  168. data/test/fixtures/db_definitions/schema.rb +60 -0
  169. data/test/fixtures/db_definitions/sqlite.drop.sql +32 -0
  170. data/test/fixtures/db_definitions/sqlite.sql +215 -0
  171. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  172. data/test/fixtures/db_definitions/sqlite2.sql +5 -0
  173. data/test/fixtures/db_definitions/sqlserver.drop.sql +34 -0
  174. data/test/fixtures/db_definitions/sqlserver.sql +243 -0
  175. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  176. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  177. data/test/fixtures/db_definitions/sybase.drop.sql +34 -0
  178. data/test/fixtures/db_definitions/sybase.sql +218 -0
  179. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  180. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  181. data/test/fixtures/default.rb +2 -0
  182. data/test/fixtures/developer.rb +52 -0
  183. data/test/fixtures/developers.yml +21 -0
  184. data/test/fixtures/developers_projects.yml +17 -0
  185. data/test/fixtures/developers_projects/david_action_controller +3 -0
  186. data/test/fixtures/developers_projects/david_active_record +3 -0
  187. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  188. data/test/fixtures/edge.rb +5 -0
  189. data/test/fixtures/edges.yml +6 -0
  190. data/test/fixtures/entrant.rb +3 -0
  191. data/test/fixtures/entrants.yml +14 -0
  192. data/test/fixtures/fk_test_has_fk.yml +3 -0
  193. data/test/fixtures/fk_test_has_pk.yml +2 -0
  194. data/test/fixtures/flowers.jpg +0 -0
  195. data/test/fixtures/funny_jokes.yml +10 -0
  196. data/test/fixtures/joke.rb +6 -0
  197. data/test/fixtures/keyboard.rb +3 -0
  198. data/test/fixtures/legacy_thing.rb +3 -0
  199. data/test/fixtures/legacy_things.yml +3 -0
  200. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  201. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  202. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  203. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  204. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  205. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  206. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  207. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  208. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  209. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  210. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  211. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  212. data/test/fixtures/mixed_case_monkey.rb +3 -0
  213. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  214. data/test/fixtures/mixin.rb +63 -0
  215. data/test/fixtures/mixins.yml +127 -0
  216. data/test/fixtures/movie.rb +5 -0
  217. data/test/fixtures/movies.yml +7 -0
  218. data/test/fixtures/naked/csv/accounts.csv +1 -0
  219. data/test/fixtures/naked/yml/accounts.yml +1 -0
  220. data/test/fixtures/naked/yml/companies.yml +1 -0
  221. data/test/fixtures/naked/yml/courses.yml +1 -0
  222. data/test/fixtures/order.rb +4 -0
  223. data/test/fixtures/people.yml +3 -0
  224. data/test/fixtures/person.rb +4 -0
  225. data/test/fixtures/post.rb +58 -0
  226. data/test/fixtures/posts.yml +48 -0
  227. data/test/fixtures/project.rb +27 -0
  228. data/test/fixtures/projects.yml +7 -0
  229. data/test/fixtures/reader.rb +4 -0
  230. data/test/fixtures/readers.yml +4 -0
  231. data/test/fixtures/reply.rb +37 -0
  232. data/test/fixtures/subject.rb +4 -0
  233. data/test/fixtures/subscriber.rb +6 -0
  234. data/test/fixtures/subscribers/first +2 -0
  235. data/test/fixtures/subscribers/second +2 -0
  236. data/test/fixtures/tag.rb +7 -0
  237. data/test/fixtures/tagging.rb +6 -0
  238. data/test/fixtures/taggings.yml +18 -0
  239. data/test/fixtures/tags.yml +7 -0
  240. data/test/fixtures/task.rb +3 -0
  241. data/test/fixtures/tasks.yml +7 -0
  242. data/test/fixtures/topic.rb +25 -0
  243. data/test/fixtures/topics.yml +22 -0
  244. data/test/fixtures/vertex.rb +9 -0
  245. data/test/fixtures/vertices.yml +4 -0
  246. data/test/fixtures_test.rb +401 -0
  247. data/test/inheritance_test.rb +205 -0
  248. data/test/lifecycle_test.rb +137 -0
  249. data/test/locking_test.rb +190 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +768 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_nested_set_test.rb +196 -0
  254. data/test/mixin_test.rb +550 -0
  255. data/test/modules_test.rb +34 -0
  256. data/test/multiple_db_test.rb +60 -0
  257. data/test/pk_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +159 -0
  260. data/test/schema_authorization_test_postgresql.rb +75 -0
  261. data/test/schema_dumper_test.rb +96 -0
  262. data/test/schema_test_postgresql.rb +64 -0
  263. data/test/synonym_test_oracle.rb +17 -0
  264. data/test/table_name_test_sqlserver.rb +23 -0
  265. data/test/threaded_connections_test.rb +48 -0
  266. data/test/transactions_test.rb +230 -0
  267. data/test/unconnected_test.rb +32 -0
  268. data/test/validations_test.rb +1097 -0
  269. data/test/xml_serialization_test.rb +125 -0
  270. metadata +365 -0
@@ -0,0 +1,343 @@
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
+ attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
10
+ attr_accessor :primary
11
+
12
+ # Instantiates a new column in the table.
13
+ #
14
+ # +name+ is the column's name, as in <tt><b>supplier_id</b> int(11)</tt>.
15
+ # +default+ is the type-casted default value, such as <tt>sales_stage varchar(20) default <b>'new'</b></tt>.
16
+ # +sql_type+ is only used to extract the column's length, if necessary. For example, <tt>company_name varchar(<b>60</b>)</tt>.
17
+ # +null+ determines if this column allows +NULL+ values.
18
+ def initialize(name, default, sql_type = nil, null = true)
19
+ @name, @sql_type, @null = name, sql_type, null
20
+ @limit, @precision, @scale = extract_limit(sql_type), extract_precision(sql_type), extract_scale(sql_type)
21
+ @type = simplified_type(sql_type)
22
+ @default = type_cast(default)
23
+
24
+ @primary = nil
25
+ end
26
+
27
+ def text?
28
+ [:string, :text].include? type
29
+ end
30
+
31
+ def number?
32
+ [:float, :integer, :decimal].include? type
33
+ end
34
+
35
+ # Returns the Ruby class that corresponds to the abstract data type.
36
+ def klass
37
+ case type
38
+ when :integer then Fixnum
39
+ when :float then Float
40
+ when :decimal then BigDecimal
41
+ when :datetime then Time
42
+ when :date then Date
43
+ when :timestamp then Time
44
+ when :time then Time
45
+ when :text, :string then String
46
+ when :binary then String
47
+ when :boolean then Object
48
+ end
49
+ end
50
+
51
+ # Casts value (which is a String) to an appropriate instance.
52
+ def type_cast(value)
53
+ return nil if value.nil?
54
+ case type
55
+ when :string then value
56
+ when :text then value
57
+ when :integer then value.to_i rescue value ? 1 : 0
58
+ when :float then value.to_f
59
+ when :decimal then self.class.value_to_decimal(value)
60
+ when :datetime then self.class.string_to_time(value)
61
+ when :timestamp then self.class.string_to_time(value)
62
+ when :time then self.class.string_to_dummy_time(value)
63
+ when :date then self.class.string_to_date(value)
64
+ when :binary then self.class.binary_to_string(value)
65
+ when :boolean then self.class.value_to_boolean(value)
66
+ else value
67
+ end
68
+ end
69
+
70
+ def type_cast_code(var_name)
71
+ case type
72
+ when :string then nil
73
+ when :text then nil
74
+ when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
75
+ when :float then "#{var_name}.to_f"
76
+ when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
77
+ when :datetime then "#{self.class.name}.string_to_time(#{var_name})"
78
+ when :timestamp then "#{self.class.name}.string_to_time(#{var_name})"
79
+ when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})"
80
+ when :date then "#{self.class.name}.string_to_date(#{var_name})"
81
+ when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
82
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
83
+ else nil
84
+ end
85
+ end
86
+
87
+ # Returns the human name of the column name.
88
+ #
89
+ # ===== Examples
90
+ # Column.new('sales_stage', ...).human_name #=> 'Sales stage'
91
+ def human_name
92
+ Base.human_attribute_name(@name)
93
+ end
94
+
95
+ # Used to convert from Strings to BLOBs
96
+ def self.string_to_binary(value)
97
+ value
98
+ end
99
+
100
+ # Used to convert from BLOBs to Strings
101
+ def self.binary_to_string(value)
102
+ value
103
+ end
104
+
105
+ def self.string_to_date(string)
106
+ return string unless string.is_a?(String)
107
+ date_array = ParseDate.parsedate(string)
108
+ # treat 0000-00-00 as nil
109
+ Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
110
+ end
111
+
112
+ def self.string_to_time(string)
113
+ return string unless string.is_a?(String)
114
+ time_hash = Date._parse(string)
115
+ time_hash[:sec_fraction] = microseconds(time_hash)
116
+ time_array = time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)
117
+ # treat 0000-00-00 00:00:00 as nil
118
+ Time.send(Base.default_timezone, *time_array) rescue DateTime.new(*time_array[0..5]) rescue nil
119
+ end
120
+
121
+ def self.string_to_dummy_time(string)
122
+ return string unless string.is_a?(String)
123
+ return nil if string.empty?
124
+ time_hash = Date._parse(string)
125
+ time_hash[:sec_fraction] = microseconds(time_hash)
126
+ # pad the resulting array with dummy date information
127
+ time_array = [2000, 1, 1]
128
+ time_array += time_hash.values_at(:hour, :min, :sec, :sec_fraction)
129
+ Time.send(Base.default_timezone, *time_array) rescue nil
130
+ end
131
+
132
+ # convert something to a boolean
133
+ def self.value_to_boolean(value)
134
+ if value == true || value == false
135
+ value
136
+ else
137
+ %w(true t 1).include?(value.to_s.downcase)
138
+ end
139
+ end
140
+
141
+ # convert something to a BigDecimal
142
+ def self.value_to_decimal(value)
143
+ if value.is_a?(BigDecimal)
144
+ value
145
+ elsif value.respond_to?(:to_d)
146
+ value.to_d
147
+ else
148
+ value.to_s.to_d
149
+ end
150
+ end
151
+
152
+ private
153
+ # '0.123456' -> 123456
154
+ # '1.123456' -> 123456
155
+ def self.microseconds(time)
156
+ ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
157
+ end
158
+
159
+ def extract_limit(sql_type)
160
+ $1.to_i if sql_type =~ /\((.*)\)/
161
+ end
162
+
163
+ def extract_precision(sql_type)
164
+ $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
165
+ end
166
+
167
+ def extract_scale(sql_type)
168
+ case sql_type
169
+ when /^(numeric|decimal|number)\((\d+)\)/i then 0
170
+ when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
171
+ end
172
+ end
173
+
174
+ def simplified_type(field_type)
175
+ case field_type
176
+ when /int/i
177
+ :integer
178
+ when /float|double/i
179
+ :float
180
+ when /decimal|numeric|number/i
181
+ extract_scale(field_type) == 0 ? :integer : :decimal
182
+ when /datetime/i
183
+ :datetime
184
+ when /timestamp/i
185
+ :timestamp
186
+ when /time/i
187
+ :time
188
+ when /date/i
189
+ :date
190
+ when /clob/i, /text/i
191
+ :text
192
+ when /blob/i, /binary/i
193
+ :binary
194
+ when /char/i, /string/i
195
+ :string
196
+ when /boolean/i
197
+ :boolean
198
+ end
199
+ end
200
+ end
201
+
202
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
203
+ end
204
+
205
+ class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
206
+
207
+ def sql_type
208
+ base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
209
+ end
210
+
211
+ def to_sql
212
+ column_sql = "#{base.quote_column_name(name)} #{sql_type}"
213
+ add_column_options!(column_sql, :null => null, :default => default) unless type.to_sym == :primary_key
214
+ column_sql
215
+ end
216
+ alias to_s :to_sql
217
+
218
+ private
219
+
220
+ def add_column_options!(sql, options)
221
+ base.add_column_options!(sql, options.merge(:column => self))
222
+ end
223
+ end
224
+
225
+ # Represents a SQL table in an abstract way.
226
+ # Columns are stored as ColumnDefinition in the #columns attribute.
227
+ class TableDefinition
228
+ attr_accessor :columns
229
+
230
+ def initialize(base)
231
+ @columns = []
232
+ @base = base
233
+ end
234
+
235
+ # Appends a primary key definition to the table definition.
236
+ # Can be called multiple times, but this is probably not a good idea.
237
+ def primary_key(name)
238
+ column(name, :primary_key)
239
+ end
240
+
241
+ # Returns a ColumnDefinition for the column with name +name+.
242
+ def [](name)
243
+ @columns.find {|column| column.name.to_s == name.to_s}
244
+ end
245
+
246
+ # Instantiates a new column for the table.
247
+ # The +type+ parameter must be one of the following values:
248
+ # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
249
+ # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
250
+ # <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
251
+ # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>.
252
+ #
253
+ # Available options are (none of these exists by default):
254
+ # * <tt>:limit</tt>:
255
+ # Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
256
+ # <tt>:binary</tt> or <tt>:integer</tt> columns only)
257
+ # * <tt>:default</tt>:
258
+ # The column's default value. Use nil for NULL.
259
+ # * <tt>:null</tt>:
260
+ # Allows or disallows +NULL+ values in the column. This option could
261
+ # have been named <tt>:null_allowed</tt>.
262
+ # * <tt>:precision</tt>:
263
+ # Specifies the precision for a <tt>:decimal</tt> column.
264
+ # * <tt>:scale</tt>:
265
+ # Specifies the scale for a <tt>:decimal</tt> column.
266
+ #
267
+ # Please be aware of different RDBMS implementations behavior with
268
+ # <tt>:decimal</tt> columns:
269
+ # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
270
+ # <tt>:precision</tt>, and makes no comments about the requirements of
271
+ # <tt>:precision</tt>.
272
+ # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
273
+ # Default is (10,0).
274
+ # * PostgreSQL: <tt>:precision</tt> [1..infinity],
275
+ # <tt>:scale</tt> [0..infinity]. No default.
276
+ # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
277
+ # Internal storage as strings. No default.
278
+ # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
279
+ # but the maximum supported <tt>:precision</tt> is 16. No default.
280
+ # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
281
+ # Default is (38,0).
282
+ # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
283
+ # Default unknown.
284
+ # * Firebird: <tt>:precision</tt> [1..18], <tt>:scale</tt> [0..18].
285
+ # Default (9,0). Internal types NUMERIC and DECIMAL have different
286
+ # storage rules, decimal being better.
287
+ # * FrontBase?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
288
+ # Default (38,0). WARNING Max <tt>:precision</tt>/<tt>:scale</tt> for
289
+ # NUMERIC is 19, and DECIMAL is 38.
290
+ # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
291
+ # Default (38,0).
292
+ # * Sybase: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
293
+ # Default (38,0).
294
+ # * OpenBase?: Documentation unclear. Claims storage in <tt>double</tt>.
295
+ #
296
+ # This method returns <tt>self</tt>.
297
+ #
298
+ # ===== Examples
299
+ # # Assuming td is an instance of TableDefinition
300
+ # td.column(:granted, :boolean)
301
+ # #=> granted BOOLEAN
302
+ #
303
+ # td.column(:picture, :binary, :limit => 2.megabytes)
304
+ # #=> picture BLOB(2097152)
305
+ #
306
+ # td.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
307
+ # #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
308
+ #
309
+ # def.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2)
310
+ # #=> bill_gates_money DECIMAL(15,2)
311
+ #
312
+ # def.column(:sensor_reading, :decimal, :precision => 30, :scale => 20)
313
+ # #=> sensor_reading DECIMAL(30,20)
314
+ #
315
+ # # While <tt>:scale</tt> defaults to zero on most databases, it
316
+ # # probably wouldn't hurt to include it.
317
+ # def.column(:huge_integer, :decimal, :precision => 30)
318
+ # #=> huge_integer DECIMAL(30)
319
+ def column(name, type, options = {})
320
+ column = self[name] || ColumnDefinition.new(@base, name, type)
321
+ column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
322
+ column.precision = options[:precision]
323
+ column.scale = options[:scale]
324
+ column.default = options[:default]
325
+ column.null = options[:null]
326
+ @columns << column unless @columns.include? column
327
+ self
328
+ end
329
+
330
+ # Returns a String whose contents are the column definitions
331
+ # concatenated together. This string can then be pre and appended to
332
+ # to generate the final SQL to create the table.
333
+ def to_sql
334
+ @columns * ', '
335
+ end
336
+
337
+ private
338
+ def native
339
+ @base.native_database_types
340
+ end
341
+ end
342
+ end
343
+ end
@@ -0,0 +1,310 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters # :nodoc:
3
+ module SchemaStatements
4
+ # Returns a Hash of mappings from the abstract data types to the native
5
+ # database types. See TableDefinition#column for details on the recognized
6
+ # abstract data types.
7
+ def native_database_types
8
+ {}
9
+ end
10
+
11
+ # This is the maximum length a table alias can be
12
+ def table_alias_length
13
+ 255
14
+ end
15
+
16
+ # Truncates a table alias according to the limits of the current adapter.
17
+ def table_alias_for(table_name)
18
+ table_name[0..table_alias_length-1].gsub(/\./, '_')
19
+ end
20
+
21
+ # def tables(name = nil) end
22
+
23
+ # Returns an array of indexes for the given table.
24
+ # def indexes(table_name, name = nil) end
25
+
26
+ # Returns an array of Column objects for the table specified by +table_name+.
27
+ # See the concrete implementation for details on the expected parameter values.
28
+ def columns(table_name, name = nil) end
29
+
30
+ # Creates a new table
31
+ # There are two ways to work with #create_table. You can use the block
32
+ # form or the regular form, like this:
33
+ #
34
+ # === Block form
35
+ # # create_table() yields a TableDefinition instance
36
+ # create_table(:suppliers) do |t|
37
+ # t.column :name, :string, :limit => 60
38
+ # # Other fields here
39
+ # end
40
+ #
41
+ # === Regular form
42
+ # create_table(:suppliers)
43
+ # add_column(:suppliers, :name, :string, {:limit => 60})
44
+ #
45
+ # The +options+ hash can include the following keys:
46
+ # [<tt>:id</tt>]
47
+ # Whether to automatically add a primary key column. Defaults to true.
48
+ # Join tables for has_and_belongs_to_many should set :id => false.
49
+ # [<tt>:primary_key</tt>]
50
+ # The name of the primary key, if one is to be added automatically.
51
+ # Defaults to +id+.
52
+ # [<tt>:options</tt>]
53
+ # Any extra options you want appended to the table definition.
54
+ # [<tt>:temporary</tt>]
55
+ # Make a temporary table.
56
+ # [<tt>:force</tt>]
57
+ # Set to true or false to drop the table before creating it.
58
+ # Defaults to false.
59
+ #
60
+ # ===== Examples
61
+ # ====== Add a backend specific option to the generated SQL (MySQL)
62
+ # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
63
+ # generates:
64
+ # CREATE TABLE suppliers (
65
+ # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
66
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
67
+ #
68
+ # ====== Rename the primary key column
69
+ # create_table(:objects, :primary_key => 'guid') do |t|
70
+ # t.column :name, :string, :limit => 80
71
+ # end
72
+ # generates:
73
+ # CREATE TABLE objects (
74
+ # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
75
+ # name varchar(80)
76
+ # )
77
+ #
78
+ # ====== Do not add a primary key column
79
+ # create_table(:categories_suppliers, :id => false) do |t|
80
+ # t.column :category_id, :integer
81
+ # t.column :supplier_id, :integer
82
+ # end
83
+ # generates:
84
+ # CREATE TABLE categories_suppliers_join (
85
+ # category_id int,
86
+ # supplier_id int
87
+ # )
88
+ #
89
+ # See also TableDefinition#column for details on how to create columns.
90
+ def create_table(name, options = {})
91
+ table_definition = TableDefinition.new(self)
92
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
93
+
94
+ yield table_definition
95
+
96
+ if options[:force]
97
+ drop_table(name, options) rescue nil
98
+ end
99
+
100
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
101
+ create_sql << "#{name} ("
102
+ create_sql << table_definition.to_sql
103
+ create_sql << ") #{options[:options]}"
104
+ execute create_sql
105
+ end
106
+
107
+ # Renames a table.
108
+ # ===== Example
109
+ # rename_table('octopuses', 'octopi')
110
+ def rename_table(name, new_name)
111
+ raise NotImplementedError, "rename_table is not implemented"
112
+ end
113
+
114
+ # Drops a table from the database.
115
+ def drop_table(name, options = {})
116
+ execute "DROP TABLE #{name}"
117
+ end
118
+
119
+ # Adds a new column to the named table.
120
+ # See TableDefinition#column for details of the options you can use.
121
+ def add_column(table_name, column_name, type, options = {})
122
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
123
+ add_column_options!(add_column_sql, options)
124
+ execute(add_column_sql)
125
+ end
126
+
127
+ # Removes the column from the table definition.
128
+ # ===== Examples
129
+ # remove_column(:suppliers, :qualification)
130
+ def remove_column(table_name, column_name)
131
+ execute "ALTER TABLE #{table_name} DROP #{quote_column_name(column_name)}"
132
+ end
133
+
134
+ # Changes the column's definition according to the new options.
135
+ # See TableDefinition#column for details of the options you can use.
136
+ # ===== Examples
137
+ # change_column(:suppliers, :name, :string, :limit => 80)
138
+ # change_column(:accounts, :description, :text)
139
+ def change_column(table_name, column_name, type, options = {})
140
+ raise NotImplementedError, "change_column is not implemented"
141
+ end
142
+
143
+ # Sets a new default value for a column. If you want to set the default
144
+ # value to +NULL+, you are out of luck. You need to
145
+ # DatabaseStatements#execute the apppropriate SQL statement yourself.
146
+ # ===== Examples
147
+ # change_column_default(:suppliers, :qualification, 'new')
148
+ # change_column_default(:accounts, :authorized, 1)
149
+ def change_column_default(table_name, column_name, default)
150
+ raise NotImplementedError, "change_column_default is not implemented"
151
+ end
152
+
153
+ # Renames a column.
154
+ # ===== Example
155
+ # rename_column(:suppliers, :description, :name)
156
+ def rename_column(table_name, column_name, new_column_name)
157
+ raise NotImplementedError, "rename_column is not implemented"
158
+ end
159
+
160
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
161
+ # an Array of Symbols.
162
+ #
163
+ # The index will be named after the table and the first column names,
164
+ # unless you pass +:name+ as an option.
165
+ #
166
+ # When creating an index on multiple columns, the first column is used as a name
167
+ # for the index. For example, when you specify an index on two columns
168
+ # [+:first+, +:last+], the DBMS creates an index for both columns as well as an
169
+ # index for the first colum +:first+. Using just the first name for this index
170
+ # makes sense, because you will never have to create a singular index with this
171
+ # name.
172
+ #
173
+ # ===== Examples
174
+ # ====== Creating a simple index
175
+ # add_index(:suppliers, :name)
176
+ # generates
177
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
178
+ # ====== Creating a unique index
179
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true)
180
+ # generates
181
+ # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
182
+ # ====== Creating a named index
183
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
184
+ # generates
185
+ # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
186
+ def add_index(table_name, column_name, options = {})
187
+ column_names = Array(column_name)
188
+ index_name = index_name(table_name, :column => column_names)
189
+
190
+ if Hash === options # legacy support, since this param was a string
191
+ index_type = options[:unique] ? "UNIQUE" : ""
192
+ index_name = options[:name] || index_name
193
+ else
194
+ index_type = options
195
+ end
196
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
197
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{table_name} (#{quoted_column_names})"
198
+ end
199
+
200
+ # Remove the given index from the table.
201
+ #
202
+ # Remove the suppliers_name_index in the suppliers table.
203
+ # remove_index :suppliers, :name
204
+ # Remove the index named accounts_branch_id_index in the accounts table.
205
+ # remove_index :accounts, :column => :branch_id
206
+ # Remove the index named accounts_branch_id_party_id_index in the accounts table.
207
+ # remove_index :accounts, :column => [:branch_id, :party_id]
208
+ # Remove the index named by_branch_party in the accounts table.
209
+ # remove_index :accounts, :name => :by_branch_party
210
+ def remove_index(table_name, options = {})
211
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))} ON #{table_name}"
212
+ end
213
+
214
+ def index_name(table_name, options) #:nodoc:
215
+ if Hash === options # legacy support
216
+ if options[:column]
217
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
218
+ elsif options[:name]
219
+ options[:name]
220
+ else
221
+ raise ArgumentError, "You must specify the index name"
222
+ end
223
+ else
224
+ index_name(table_name, :column => options)
225
+ end
226
+ end
227
+
228
+ # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
229
+ # entire structure of the database.
230
+ def structure_dump
231
+ end
232
+
233
+ # Should not be called normally, but this operation is non-destructive.
234
+ # The migrations module handles this automatically.
235
+ def initialize_schema_information
236
+ begin
237
+ execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (version #{type_to_sql(:integer)})"
238
+ execute "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES(0)"
239
+ rescue ActiveRecord::StatementInvalid
240
+ # Schema has been intialized
241
+ end
242
+ end
243
+
244
+ def dump_schema_information #:nodoc:
245
+ begin
246
+ if (current_schema = ActiveRecord::Migrator.current_version) > 0
247
+ return "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES (#{current_schema})"
248
+ end
249
+ rescue ActiveRecord::StatementInvalid
250
+ # No Schema Info
251
+ end
252
+ end
253
+
254
+
255
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
256
+ native = native_database_types[type]
257
+ column_type_sql = native.is_a?(Hash) ? native[:name] : native
258
+ if type == :decimal # ignore limit, use precison and scale
259
+ precision ||= native[:precision]
260
+ scale ||= native[:scale]
261
+ if precision
262
+ if scale
263
+ column_type_sql << "(#{precision},#{scale})"
264
+ else
265
+ column_type_sql << "(#{precision})"
266
+ end
267
+ else
268
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified" if scale
269
+ end
270
+ column_type_sql
271
+ else
272
+ limit ||= native[:limit]
273
+ column_type_sql << "(#{limit})" if limit
274
+ column_type_sql
275
+ end
276
+ end
277
+
278
+ def add_column_options!(sql, options) #:nodoc:
279
+ sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
280
+ # must explcitly check for :null to allow change_column to work on migrations
281
+ if options.has_key? :null
282
+ if options[:null] == false
283
+ sql << " NOT NULL"
284
+ else
285
+ sql << " NULL"
286
+ end
287
+ end
288
+ end
289
+
290
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
291
+ # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
292
+ #
293
+ # distinct("posts.id", "posts.created_at desc")
294
+ def distinct(columns, order_by)
295
+ "DISTINCT #{columns}"
296
+ end
297
+
298
+ # ORDER BY clause for the passed order option.
299
+ # PostgreSQL overrides this due to its stricter standards compliance.
300
+ def add_order_by_for_association_limiting!(sql, options)
301
+ sql << "ORDER BY #{options[:order]}"
302
+ end
303
+
304
+ protected
305
+ def options_include_default?(options)
306
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
307
+ end
308
+ end
309
+ end
310
+ end