activerecord 1.0.0 → 3.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 (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. data/test/validations_test.rb +0 -126
@@ -0,0 +1,543 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters # :nodoc:
5
+ module SchemaStatements
6
+ # Returns a Hash of mappings from the abstract data types to the native
7
+ # database types. See TableDefinition#column for details on the recognized
8
+ # abstract data types.
9
+ def native_database_types
10
+ {}
11
+ end
12
+
13
+ # Truncates a table alias according to the limits of the current adapter.
14
+ def table_alias_for(table_name)
15
+ table_name[0..table_alias_length-1].gsub(/\./, '_')
16
+ end
17
+
18
+ # def tables(name = nil) end
19
+
20
+ def table_exists?(table_name)
21
+ tables.include?(table_name.to_s)
22
+ end
23
+
24
+ # Returns an array of indexes for the given table.
25
+ # def indexes(table_name, name = nil) end
26
+
27
+ # Checks to see if an index exists on a table for a given index definition
28
+ #
29
+ # === Examples
30
+ # # Check an index exists
31
+ # index_exists?(:suppliers, :company_id)
32
+ #
33
+ # # Check an index on multiple columns exists
34
+ # index_exists?(:suppliers, [:company_id, :company_type])
35
+ #
36
+ # # Check a unique index exists
37
+ # index_exists?(:suppliers, :company_id, :unique => true)
38
+ #
39
+ # # Check an index with a custom name exists
40
+ # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
41
+ def index_exists?(table_name, column_name, options = {})
42
+ column_names = Array.wrap(column_name)
43
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
44
+ if options[:unique]
45
+ indexes(table_name).any?{ |i| i.unique && i.name == index_name }
46
+ else
47
+ indexes(table_name).any?{ |i| i.name == index_name }
48
+ end
49
+ end
50
+
51
+ # Returns an array of Column objects for the table specified by +table_name+.
52
+ # See the concrete implementation for details on the expected parameter values.
53
+ def columns(table_name, name = nil) end
54
+
55
+ # Checks to see if a column exists in a given table.
56
+ #
57
+ # === Examples
58
+ # # Check a column exists
59
+ # column_exists?(:suppliers, :name)
60
+ #
61
+ # # Check a column exists of a particular type
62
+ # column_exists?(:suppliers, :name, :string)
63
+ #
64
+ # # Check a column exists with a specific definition
65
+ # column_exists?(:suppliers, :name, :string, :limit => 100)
66
+ def column_exists?(table_name, column_name, type = nil, options = {})
67
+ columns(table_name).any?{ |c| c.name == column_name.to_s &&
68
+ (!type || c.type == type) &&
69
+ (!options[:limit] || c.limit == options[:limit]) &&
70
+ (!options[:precision] || c.precision == options[:precision]) &&
71
+ (!options[:scale] || c.scale == options[:scale]) }
72
+ end
73
+
74
+ # Creates a new table with the name +table_name+. +table_name+ may either
75
+ # be a String or a Symbol.
76
+ #
77
+ # There are two ways to work with +create_table+. You can use the block
78
+ # form or the regular form, like this:
79
+ #
80
+ # === Block form
81
+ # # create_table() passes a TableDefinition object to the block.
82
+ # # This form will not only create the table, but also columns for the
83
+ # # table.
84
+ #
85
+ # create_table(:suppliers) do |t|
86
+ # t.column :name, :string, :limit => 60
87
+ # # Other fields here
88
+ # end
89
+ #
90
+ # === Block form, with shorthand
91
+ # # You can also use the column types as method calls, rather than calling the column method.
92
+ # create_table(:suppliers) do |t|
93
+ # t.string :name, :limit => 60
94
+ # # Other fields here
95
+ # end
96
+ #
97
+ # === Regular form
98
+ # # Creates a table called 'suppliers' with no columns.
99
+ # create_table(:suppliers)
100
+ # # Add a column to 'suppliers'.
101
+ # add_column(:suppliers, :name, :string, {:limit => 60})
102
+ #
103
+ # The +options+ hash can include the following keys:
104
+ # [<tt>:id</tt>]
105
+ # Whether to automatically add a primary key column. Defaults to true.
106
+ # Join tables for +has_and_belongs_to_many+ should set it to false.
107
+ # [<tt>:primary_key</tt>]
108
+ # The name of the primary key, if one is to be added automatically.
109
+ # Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
110
+ #
111
+ # Also note that this just sets the primary key in the table. You additionally
112
+ # need to configure the primary key in the model via the +set_primary_key+ macro.
113
+ # Models do NOT auto-detect the primary key from their table definition.
114
+ #
115
+ # [<tt>:options</tt>]
116
+ # Any extra options you want appended to the table definition.
117
+ # [<tt>:temporary</tt>]
118
+ # Make a temporary table.
119
+ # [<tt>:force</tt>]
120
+ # Set to true to drop the table before creating it.
121
+ # Defaults to false.
122
+ #
123
+ # ===== Examples
124
+ # ====== Add a backend specific option to the generated SQL (MySQL)
125
+ # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
126
+ # generates:
127
+ # CREATE TABLE suppliers (
128
+ # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
129
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
130
+ #
131
+ # ====== Rename the primary key column
132
+ # create_table(:objects, :primary_key => 'guid') do |t|
133
+ # t.column :name, :string, :limit => 80
134
+ # end
135
+ # generates:
136
+ # CREATE TABLE objects (
137
+ # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
138
+ # name varchar(80)
139
+ # )
140
+ #
141
+ # ====== Do not add a primary key column
142
+ # create_table(:categories_suppliers, :id => false) do |t|
143
+ # t.column :category_id, :integer
144
+ # t.column :supplier_id, :integer
145
+ # end
146
+ # generates:
147
+ # CREATE TABLE categories_suppliers (
148
+ # category_id int,
149
+ # supplier_id int
150
+ # )
151
+ #
152
+ # See also TableDefinition#column for details on how to create columns.
153
+ def create_table(table_name, options = {})
154
+ table_definition = TableDefinition.new(self)
155
+ table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
156
+
157
+ yield table_definition if block_given?
158
+
159
+ if options[:force] && table_exists?(table_name)
160
+ drop_table(table_name, options)
161
+ end
162
+
163
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
164
+ create_sql << "#{quote_table_name(table_name)} ("
165
+ create_sql << table_definition.to_sql
166
+ create_sql << ") #{options[:options]}"
167
+ execute create_sql
168
+ end
169
+
170
+ # A block for changing columns in +table+.
171
+ #
172
+ # === Example
173
+ # # change_table() yields a Table instance
174
+ # change_table(:suppliers) do |t|
175
+ # t.column :name, :string, :limit => 60
176
+ # # Other column alterations here
177
+ # end
178
+ #
179
+ # ===== Examples
180
+ # ====== Add a column
181
+ # change_table(:suppliers) do |t|
182
+ # t.column :name, :string, :limit => 60
183
+ # end
184
+ #
185
+ # ====== Add 2 integer columns
186
+ # change_table(:suppliers) do |t|
187
+ # t.integer :width, :height, :null => false, :default => 0
188
+ # end
189
+ #
190
+ # ====== Add created_at/updated_at columns
191
+ # change_table(:suppliers) do |t|
192
+ # t.timestamps
193
+ # end
194
+ #
195
+ # ====== Add a foreign key column
196
+ # change_table(:suppliers) do |t|
197
+ # t.references :company
198
+ # end
199
+ #
200
+ # Creates a <tt>company_id(integer)</tt> column
201
+ #
202
+ # ====== Add a polymorphic foreign key column
203
+ # change_table(:suppliers) do |t|
204
+ # t.belongs_to :company, :polymorphic => true
205
+ # end
206
+ #
207
+ # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
208
+ #
209
+ # ====== Remove a column
210
+ # change_table(:suppliers) do |t|
211
+ # t.remove :company
212
+ # end
213
+ #
214
+ # ====== Remove several columns
215
+ # change_table(:suppliers) do |t|
216
+ # t.remove :company_id
217
+ # t.remove :width, :height
218
+ # end
219
+ #
220
+ # ====== Remove an index
221
+ # change_table(:suppliers) do |t|
222
+ # t.remove_index :company_id
223
+ # end
224
+ #
225
+ # See also Table for details on
226
+ # all of the various column transformation
227
+ def change_table(table_name)
228
+ yield Table.new(table_name, self)
229
+ end
230
+
231
+ # Renames a table.
232
+ # ===== Example
233
+ # rename_table('octopuses', 'octopi')
234
+ def rename_table(table_name, new_name)
235
+ raise NotImplementedError, "rename_table is not implemented"
236
+ end
237
+
238
+ # Drops a table from the database.
239
+ def drop_table(table_name, options = {})
240
+ execute "DROP TABLE #{quote_table_name(table_name)}"
241
+ end
242
+
243
+ # Adds a new column to the named table.
244
+ # See TableDefinition#column for details of the options you can use.
245
+ def add_column(table_name, column_name, type, options = {})
246
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
247
+ add_column_options!(add_column_sql, options)
248
+ execute(add_column_sql)
249
+ end
250
+
251
+ # Removes the column(s) from the table definition.
252
+ # ===== Examples
253
+ # remove_column(:suppliers, :qualification)
254
+ # remove_columns(:suppliers, :qualification, :experience)
255
+ def remove_column(table_name, *column_names)
256
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
257
+ column_names.flatten.each do |column_name|
258
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
259
+ end
260
+ end
261
+ alias :remove_columns :remove_column
262
+
263
+ # Changes the column's definition according to the new options.
264
+ # See TableDefinition#column for details of the options you can use.
265
+ # ===== Examples
266
+ # change_column(:suppliers, :name, :string, :limit => 80)
267
+ # change_column(:accounts, :description, :text)
268
+ def change_column(table_name, column_name, type, options = {})
269
+ raise NotImplementedError, "change_column is not implemented"
270
+ end
271
+
272
+ # Sets a new default value for a column. If you want to set the default
273
+ # value to +NULL+, you are out of luck. You need to
274
+ # DatabaseStatements#execute the appropriate SQL statement yourself.
275
+ # ===== Examples
276
+ # change_column_default(:suppliers, :qualification, 'new')
277
+ # change_column_default(:accounts, :authorized, 1)
278
+ def change_column_default(table_name, column_name, default)
279
+ raise NotImplementedError, "change_column_default is not implemented"
280
+ end
281
+
282
+ # Renames a column.
283
+ # ===== Example
284
+ # rename_column(:suppliers, :description, :name)
285
+ def rename_column(table_name, column_name, new_column_name)
286
+ raise NotImplementedError, "rename_column is not implemented"
287
+ end
288
+
289
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
290
+ # an Array of Symbols.
291
+ #
292
+ # The index will be named after the table and the first column name,
293
+ # unless you pass <tt>:name</tt> as an option.
294
+ #
295
+ # When creating an index on multiple columns, the first column is used as a name
296
+ # for the index. For example, when you specify an index on two columns
297
+ # [<tt>:first</tt>, <tt>:last</tt>], the DBMS creates an index for both columns as well as an
298
+ # index for the first column <tt>:first</tt>. Using just the first name for this index
299
+ # makes sense, because you will never have to create a singular index with this
300
+ # name.
301
+ #
302
+ # ===== Examples
303
+ #
304
+ # ====== Creating a simple index
305
+ # add_index(:suppliers, :name)
306
+ # generates
307
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
308
+ #
309
+ # ====== Creating a unique index
310
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true)
311
+ # generates
312
+ # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
313
+ #
314
+ # ====== Creating a named index
315
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
316
+ # generates
317
+ # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
318
+ #
319
+ # ====== Creating an index with specific key length
320
+ # add_index(:accounts, :name, :name => 'by_name', :length => 10)
321
+ # generates
322
+ # CREATE INDEX by_name ON accounts(name(10))
323
+ #
324
+ # add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
325
+ # generates
326
+ # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
327
+ #
328
+ # Note: SQLite doesn't support index length
329
+ def add_index(table_name, column_name, options = {})
330
+ options[:name] = options[:name].to_s if options.key?(:name)
331
+
332
+ column_names = Array.wrap(column_name)
333
+ index_name = index_name(table_name, :column => column_names)
334
+
335
+ if Hash === options # legacy support, since this param was a string
336
+ index_type = options[:unique] ? "UNIQUE" : ""
337
+ index_name = options[:name] || index_name
338
+ else
339
+ index_type = options
340
+ end
341
+
342
+ if index_name.length > index_name_length
343
+ @logger.warn("Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters. Skipping.")
344
+ return
345
+ end
346
+ if index_name_exists?(table_name, index_name, false)
347
+ @logger.warn("Index name '#{index_name}' on table '#{table_name}' already exists. Skipping.")
348
+ return
349
+ end
350
+ quoted_column_names = quoted_columns_for_index(column_names, options).join(", ")
351
+
352
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
353
+ end
354
+
355
+ # Remove the given index from the table.
356
+ #
357
+ # Remove the suppliers_name_index in the suppliers table.
358
+ # remove_index :suppliers, :name
359
+ # Remove the index named accounts_branch_id_index in the accounts table.
360
+ # remove_index :accounts, :column => :branch_id
361
+ # Remove the index named accounts_branch_id_party_id_index in the accounts table.
362
+ # remove_index :accounts, :column => [:branch_id, :party_id]
363
+ # Remove the index named by_branch_party in the accounts table.
364
+ # remove_index :accounts, :name => :by_branch_party
365
+ def remove_index(table_name, options = {})
366
+ index_name = index_name(table_name, options)
367
+ unless index_name_exists?(table_name, index_name, true)
368
+ @logger.warn("Index name '#{index_name}' on table '#{table_name}' does not exist. Skipping.")
369
+ return
370
+ end
371
+ remove_index!(table_name, index_name)
372
+ end
373
+
374
+ def remove_index!(table_name, index_name) #:nodoc:
375
+ execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
376
+ end
377
+
378
+ # Rename an index.
379
+ #
380
+ # Rename the index_people_on_last_name index to index_users_on_last_name
381
+ # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
382
+ def rename_index(table_name, old_name, new_name)
383
+ # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
384
+ old_index_def = indexes(table_name).detect { |i| i.name == old_name }
385
+ return unless old_index_def
386
+ remove_index(table_name, :name => old_name)
387
+ add_index(table_name, old_index_def.columns, :name => new_name, :unique => old_index_def.unique)
388
+ end
389
+
390
+ def index_name(table_name, options) #:nodoc:
391
+ if Hash === options # legacy support
392
+ if options[:column]
393
+ "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}"
394
+ elsif options[:name]
395
+ options[:name]
396
+ else
397
+ raise ArgumentError, "You must specify the index name"
398
+ end
399
+ else
400
+ index_name(table_name, :column => options)
401
+ end
402
+ end
403
+
404
+ # Verify the existence of an index with a given name.
405
+ #
406
+ # The default argument is returned if the underlying implementation does not define the indexes method,
407
+ # as there's no way to determine the correct answer in that case.
408
+ def index_name_exists?(table_name, index_name, default)
409
+ return default unless respond_to?(:indexes)
410
+ indexes(table_name).detect { |i| i.name == index_name }
411
+ end
412
+
413
+ # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
414
+ # entire structure of the database.
415
+ def structure_dump
416
+ end
417
+
418
+ def dump_schema_information #:nodoc:
419
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
420
+ migrated = select_values("SELECT version FROM #{sm_table}")
421
+ migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n")
422
+ end
423
+
424
+ # Should not be called normally, but this operation is non-destructive.
425
+ # The migrations module handles this automatically.
426
+ def initialize_schema_migrations_table
427
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
428
+
429
+ unless table_exists?(sm_table)
430
+ create_table(sm_table, :id => false) do |schema_migrations_table|
431
+ schema_migrations_table.column :version, :string, :null => false
432
+ end
433
+ add_index sm_table, :version, :unique => true,
434
+ :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
435
+
436
+ # Backwards-compatibility: if we find schema_info, assume we've
437
+ # migrated up to that point:
438
+ si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
439
+
440
+ if table_exists?(si_table)
441
+
442
+ old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
443
+ assume_migrated_upto_version(old_version)
444
+ drop_table(si_table)
445
+ end
446
+ end
447
+ end
448
+
449
+ def assume_migrated_upto_version(version, migrations_path = ActiveRecord::Migrator.migrations_path)
450
+ version = version.to_i
451
+ sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
452
+
453
+ migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
454
+ versions = Dir["#{migrations_path}/[0-9]*_*.rb"].map do |filename|
455
+ filename.split('/').last.split('_').first.to_i
456
+ end
457
+
458
+ unless migrated.include?(version)
459
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
460
+ end
461
+
462
+ inserted = Set.new
463
+ (versions - migrated).each do |v|
464
+ if inserted.include?(v)
465
+ raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
466
+ elsif v < version
467
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
468
+ inserted << v
469
+ end
470
+ end
471
+ end
472
+
473
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
474
+ if native = native_database_types[type]
475
+ column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
476
+
477
+ if type == :decimal # ignore limit, use precision and scale
478
+ scale ||= native[:scale]
479
+
480
+ if precision ||= native[:precision]
481
+ if scale
482
+ column_type_sql << "(#{precision},#{scale})"
483
+ else
484
+ column_type_sql << "(#{precision})"
485
+ end
486
+ elsif scale
487
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
488
+ end
489
+
490
+ elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
491
+ column_type_sql << "(#{limit})"
492
+ end
493
+
494
+ column_type_sql
495
+ else
496
+ type
497
+ end
498
+ end
499
+
500
+ def add_column_options!(sql, options) #:nodoc:
501
+ sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
502
+ # must explicitly check for :null to allow change_column to work on migrations
503
+ if options[:null] == false
504
+ sql << " NOT NULL"
505
+ end
506
+ end
507
+
508
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
509
+ # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
510
+ #
511
+ # distinct("posts.id", "posts.created_at desc")
512
+ def distinct(columns, order_by)
513
+ "DISTINCT #{columns}"
514
+ end
515
+
516
+ # Adds timestamps (created_at and updated_at) columns to the named table.
517
+ # ===== Examples
518
+ # add_timestamps(:suppliers)
519
+ def add_timestamps(table_name)
520
+ add_column table_name, :created_at, :datetime
521
+ add_column table_name, :updated_at, :datetime
522
+ end
523
+
524
+ # Removes the timestamp columns (created_at and updated_at) from the table definition.
525
+ # ===== Examples
526
+ # remove_timestamps(:suppliers)
527
+ def remove_timestamps(table_name)
528
+ remove_column table_name, :updated_at
529
+ remove_column table_name, :created_at
530
+ end
531
+
532
+ protected
533
+ # Overridden by the mysql adapter for supporting index lengths
534
+ def quoted_columns_for_index(column_names, options = {})
535
+ column_names.map {|name| quote_column_name(name) }
536
+ end
537
+
538
+ def options_include_default?(options)
539
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
540
+ end
541
+ end
542
+ end
543
+ end