sskirby-activerecord 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,598 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+ require 'active_support/deprecation/reporting'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters # :nodoc:
6
+ module SchemaStatements
7
+ # Returns a Hash of mappings from the abstract data types to the native
8
+ # database types. See TableDefinition#column for details on the recognized
9
+ # abstract data types.
10
+ def native_database_types
11
+ {}
12
+ end
13
+
14
+ # Truncates a table alias according to the limits of the current adapter.
15
+ def table_alias_for(table_name)
16
+ table_name[0...table_alias_length].gsub(/\./, '_')
17
+ end
18
+
19
+ # Checks to see if the table +table_name+ exists on the database.
20
+ #
21
+ # === Example
22
+ # table_exists?(:developers)
23
+ def table_exists?(table_name)
24
+ tables.include?(table_name.to_s)
25
+ end
26
+
27
+ # Returns an array of indexes for the given table.
28
+ # def indexes(table_name, name = nil) end
29
+
30
+ # Checks to see if an index exists on a table for a given index definition.
31
+ #
32
+ # === Examples
33
+ # # Check an index exists
34
+ # index_exists?(:suppliers, :company_id)
35
+ #
36
+ # # Check an index on multiple columns exists
37
+ # index_exists?(:suppliers, [:company_id, :company_type])
38
+ #
39
+ # # Check a unique index exists
40
+ # index_exists?(:suppliers, :company_id, :unique => true)
41
+ #
42
+ # # Check an index with a custom name exists
43
+ # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
44
+ def index_exists?(table_name, column_name, options = {})
45
+ column_names = Array.wrap(column_name)
46
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
47
+ if options[:unique]
48
+ indexes(table_name).any?{ |i| i.unique && i.name == index_name }
49
+ else
50
+ indexes(table_name).any?{ |i| i.name == index_name }
51
+ end
52
+ end
53
+
54
+ # Returns an array of Column objects for the table specified by +table_name+.
55
+ # See the concrete implementation for details on the expected parameter values.
56
+ def columns(table_name, name = nil) end
57
+
58
+ # Checks to see if a column exists in a given table.
59
+ #
60
+ # === Examples
61
+ # # Check a column exists
62
+ # column_exists?(:suppliers, :name)
63
+ #
64
+ # # Check a column exists of a particular type
65
+ # column_exists?(:suppliers, :name, :string)
66
+ #
67
+ # # Check a column exists with a specific definition
68
+ # column_exists?(:suppliers, :name, :string, :limit => 100)
69
+ def column_exists?(table_name, column_name, type = nil, options = {})
70
+ columns(table_name).any?{ |c| c.name == column_name.to_s &&
71
+ (!type || c.type == type) &&
72
+ (!options[:limit] || c.limit == options[:limit]) &&
73
+ (!options[:precision] || c.precision == options[:precision]) &&
74
+ (!options[:scale] || c.scale == options[:scale]) }
75
+ end
76
+
77
+ # Creates a new table with the name +table_name+. +table_name+ may either
78
+ # be a String or a Symbol.
79
+ #
80
+ # There are two ways to work with +create_table+. You can use the block
81
+ # form or the regular form, like this:
82
+ #
83
+ # === Block form
84
+ # # create_table() passes a TableDefinition object to the block.
85
+ # # This form will not only create the table, but also columns for the
86
+ # # table.
87
+ #
88
+ # create_table(:suppliers) do |t|
89
+ # t.column :name, :string, :limit => 60
90
+ # # Other fields here
91
+ # end
92
+ #
93
+ # === Block form, with shorthand
94
+ # # You can also use the column types as method calls, rather than calling the column method.
95
+ # create_table(:suppliers) do |t|
96
+ # t.string :name, :limit => 60
97
+ # # Other fields here
98
+ # end
99
+ #
100
+ # === Regular form
101
+ # # Creates a table called 'suppliers' with no columns.
102
+ # create_table(:suppliers)
103
+ # # Add a column to 'suppliers'.
104
+ # add_column(:suppliers, :name, :string, {:limit => 60})
105
+ #
106
+ # The +options+ hash can include the following keys:
107
+ # [<tt>:id</tt>]
108
+ # Whether to automatically add a primary key column. Defaults to true.
109
+ # Join tables for +has_and_belongs_to_many+ should set it to false.
110
+ # [<tt>:primary_key</tt>]
111
+ # The name of the primary key, if one is to be added automatically.
112
+ # Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
113
+ #
114
+ # Also note that this just sets the primary key in the table. You additionally
115
+ # need to configure the primary key in the model via +self.primary_key=+.
116
+ # Models do NOT auto-detect the primary key from their table definition.
117
+ #
118
+ # [<tt>:options</tt>]
119
+ # Any extra options you want appended to the table definition.
120
+ # [<tt>:temporary</tt>]
121
+ # Make a temporary table.
122
+ # [<tt>:force</tt>]
123
+ # Set to true to drop the table before creating it.
124
+ # Defaults to false.
125
+ #
126
+ # ===== Examples
127
+ # ====== Add a backend specific option to the generated SQL (MySQL)
128
+ # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
129
+ # generates:
130
+ # CREATE TABLE suppliers (
131
+ # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
132
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
133
+ #
134
+ # ====== Rename the primary key column
135
+ # create_table(:objects, :primary_key => 'guid') do |t|
136
+ # t.column :name, :string, :limit => 80
137
+ # end
138
+ # generates:
139
+ # CREATE TABLE objects (
140
+ # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
141
+ # name varchar(80)
142
+ # )
143
+ #
144
+ # ====== Do not add a primary key column
145
+ # create_table(:categories_suppliers, :id => false) do |t|
146
+ # t.column :category_id, :integer
147
+ # t.column :supplier_id, :integer
148
+ # end
149
+ # generates:
150
+ # CREATE TABLE categories_suppliers (
151
+ # category_id int,
152
+ # supplier_id int
153
+ # )
154
+ #
155
+ # See also TableDefinition#column for details on how to create columns.
156
+ def create_table(table_name, options = {})
157
+ td = table_definition
158
+ td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
159
+
160
+ yield td if block_given?
161
+
162
+ if options[:force] && table_exists?(table_name)
163
+ drop_table(table_name)
164
+ end
165
+
166
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
167
+ create_sql << "#{quote_table_name(table_name)} ("
168
+ create_sql << td.to_sql
169
+ create_sql << ") #{options[:options]}"
170
+ execute create_sql
171
+ end
172
+
173
+ # A block for changing columns in +table+.
174
+ #
175
+ # === Example
176
+ # # change_table() yields a Table instance
177
+ # change_table(:suppliers) do |t|
178
+ # t.column :name, :string, :limit => 60
179
+ # # Other column alterations here
180
+ # end
181
+ #
182
+ # The +options+ hash can include the following keys:
183
+ # [<tt>:bulk</tt>]
184
+ # Set this to true to make this a bulk alter query, such as
185
+ # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
186
+ #
187
+ # Defaults to false.
188
+ #
189
+ # ===== Examples
190
+ # ====== Add a column
191
+ # change_table(:suppliers) do |t|
192
+ # t.column :name, :string, :limit => 60
193
+ # end
194
+ #
195
+ # ====== Add 2 integer columns
196
+ # change_table(:suppliers) do |t|
197
+ # t.integer :width, :height, :null => false, :default => 0
198
+ # end
199
+ #
200
+ # ====== Add created_at/updated_at columns
201
+ # change_table(:suppliers) do |t|
202
+ # t.timestamps
203
+ # end
204
+ #
205
+ # ====== Add a foreign key column
206
+ # change_table(:suppliers) do |t|
207
+ # t.references :company
208
+ # end
209
+ #
210
+ # Creates a <tt>company_id(integer)</tt> column
211
+ #
212
+ # ====== Add a polymorphic foreign key column
213
+ # change_table(:suppliers) do |t|
214
+ # t.belongs_to :company, :polymorphic => true
215
+ # end
216
+ #
217
+ # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
218
+ #
219
+ # ====== Remove a column
220
+ # change_table(:suppliers) do |t|
221
+ # t.remove :company
222
+ # end
223
+ #
224
+ # ====== Remove several columns
225
+ # change_table(:suppliers) do |t|
226
+ # t.remove :company_id
227
+ # t.remove :width, :height
228
+ # end
229
+ #
230
+ # ====== Remove an index
231
+ # change_table(:suppliers) do |t|
232
+ # t.remove_index :company_id
233
+ # end
234
+ #
235
+ # See also Table for details on
236
+ # all of the various column transformation
237
+ def change_table(table_name, options = {})
238
+ if supports_bulk_alter? && options[:bulk]
239
+ recorder = ActiveRecord::Migration::CommandRecorder.new(self)
240
+ yield Table.new(table_name, recorder)
241
+ bulk_change_table(table_name, recorder.commands)
242
+ else
243
+ yield Table.new(table_name, self)
244
+ end
245
+ end
246
+
247
+ # Renames a table.
248
+ # ===== Example
249
+ # rename_table('octopuses', 'octopi')
250
+ def rename_table(table_name, new_name)
251
+ raise NotImplementedError, "rename_table is not implemented"
252
+ end
253
+
254
+ # Drops a table from the database.
255
+ def drop_table(table_name)
256
+ execute "DROP TABLE #{quote_table_name(table_name)}"
257
+ end
258
+
259
+ # Adds a new column to the named table.
260
+ # See TableDefinition#column for details of the options you can use.
261
+ def add_column(table_name, column_name, type, options = {})
262
+ 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])}"
263
+ add_column_options!(add_column_sql, options)
264
+ execute(add_column_sql)
265
+ end
266
+
267
+ # Removes the column(s) from the table definition.
268
+ # ===== Examples
269
+ # remove_column(:suppliers, :qualification)
270
+ # remove_columns(:suppliers, :qualification, :experience)
271
+ def remove_column(table_name, *column_names)
272
+ columns_for_remove(table_name, *column_names).each {|column_name| execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}" }
273
+ end
274
+ alias :remove_columns :remove_column
275
+
276
+ # Changes the column's definition according to the new options.
277
+ # See TableDefinition#column for details of the options you can use.
278
+ # ===== Examples
279
+ # change_column(:suppliers, :name, :string, :limit => 80)
280
+ # change_column(:accounts, :description, :text)
281
+ def change_column(table_name, column_name, type, options = {})
282
+ raise NotImplementedError, "change_column is not implemented"
283
+ end
284
+
285
+ # Sets a new default value for a column.
286
+ # ===== Examples
287
+ # change_column_default(:suppliers, :qualification, 'new')
288
+ # change_column_default(:accounts, :authorized, 1)
289
+ # change_column_default(:users, :email, nil)
290
+ def change_column_default(table_name, column_name, default)
291
+ raise NotImplementedError, "change_column_default is not implemented"
292
+ end
293
+
294
+ # Renames a column.
295
+ # ===== Example
296
+ # rename_column(:suppliers, :description, :name)
297
+ def rename_column(table_name, column_name, new_column_name)
298
+ raise NotImplementedError, "rename_column is not implemented"
299
+ end
300
+
301
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
302
+ # an Array of Symbols.
303
+ #
304
+ # The index will be named after the table and the column name(s), unless
305
+ # you pass <tt>:name</tt> as an option.
306
+ #
307
+ # ===== Examples
308
+ #
309
+ # ====== Creating a simple index
310
+ # add_index(:suppliers, :name)
311
+ # generates
312
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
313
+ #
314
+ # ====== Creating a unique index
315
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true)
316
+ # generates
317
+ # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
318
+ #
319
+ # ====== Creating a named index
320
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
321
+ # generates
322
+ # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
323
+ #
324
+ # ====== Creating an index with specific key length
325
+ # add_index(:accounts, :name, :name => 'by_name', :length => 10)
326
+ # generates
327
+ # CREATE INDEX by_name ON accounts(name(10))
328
+ #
329
+ # add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
330
+ # generates
331
+ # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
332
+ #
333
+ # Note: SQLite doesn't support index length
334
+ #
335
+ # ====== Creating an index with a sort order (desc or asc, asc is the default)
336
+ # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :part_id => :asc})
337
+ # generates
338
+ # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
339
+ #
340
+ # Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
341
+ #
342
+ def add_index(table_name, column_name, options = {})
343
+ index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
344
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})"
345
+ end
346
+
347
+ # Remove the given index from the table.
348
+ #
349
+ # Remove the index_accounts_on_column in the accounts table.
350
+ # remove_index :accounts, :column
351
+ # Remove the index named index_accounts_on_branch_id in the accounts table.
352
+ # remove_index :accounts, :column => :branch_id
353
+ # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table.
354
+ # remove_index :accounts, :column => [:branch_id, :party_id]
355
+ # Remove the index named by_branch_party in the accounts table.
356
+ # remove_index :accounts, :name => :by_branch_party
357
+ def remove_index(table_name, options = {})
358
+ remove_index!(table_name, index_name_for_remove(table_name, options))
359
+ end
360
+
361
+ def remove_index!(table_name, index_name) #:nodoc:
362
+ execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
363
+ end
364
+
365
+ # Rename an index.
366
+ #
367
+ # Rename the index_people_on_last_name index to index_users_on_last_name
368
+ # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
369
+ def rename_index(table_name, old_name, new_name)
370
+ # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
371
+ old_index_def = indexes(table_name).detect { |i| i.name == old_name }
372
+ return unless old_index_def
373
+ remove_index(table_name, :name => old_name)
374
+ add_index(table_name, old_index_def.columns, :name => new_name, :unique => old_index_def.unique)
375
+ end
376
+
377
+ def index_name(table_name, options) #:nodoc:
378
+ if Hash === options # legacy support
379
+ if options[:column]
380
+ "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}"
381
+ elsif options[:name]
382
+ options[:name]
383
+ else
384
+ raise ArgumentError, "You must specify the index name"
385
+ end
386
+ else
387
+ index_name(table_name, :column => options)
388
+ end
389
+ end
390
+
391
+ # Verify the existence of an index with a given name.
392
+ #
393
+ # The default argument is returned if the underlying implementation does not define the indexes method,
394
+ # as there's no way to determine the correct answer in that case.
395
+ def index_name_exists?(table_name, index_name, default)
396
+ return default unless respond_to?(:indexes)
397
+ index_name = index_name.to_s
398
+ indexes(table_name).detect { |i| i.name == index_name }
399
+ end
400
+
401
+ # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
402
+ # entire structure of the database.
403
+ def structure_dump
404
+ end
405
+
406
+ def dump_schema_information #:nodoc:
407
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
408
+ migrated = select_values("SELECT version FROM #{sm_table} ORDER BY version")
409
+ migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n")
410
+ end
411
+
412
+ # Should not be called normally, but this operation is non-destructive.
413
+ # The migrations module handles this automatically.
414
+ def initialize_schema_migrations_table
415
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
416
+
417
+ unless table_exists?(sm_table)
418
+ create_table(sm_table, :id => false) do |schema_migrations_table|
419
+ schema_migrations_table.column :version, :string, :null => false
420
+ end
421
+ add_index sm_table, :version, :unique => true,
422
+ :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
423
+
424
+ # Backwards-compatibility: if we find schema_info, assume we've
425
+ # migrated up to that point:
426
+ si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
427
+
428
+ if table_exists?(si_table)
429
+ ActiveRecord::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table"
430
+
431
+ old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
432
+ assume_migrated_upto_version(old_version)
433
+ drop_table(si_table)
434
+ end
435
+ end
436
+ end
437
+
438
+ def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
439
+ migrations_paths = Array.wrap(migrations_paths)
440
+ version = version.to_i
441
+ sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
442
+
443
+ migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
444
+ paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
445
+ versions = Dir[*paths].map do |filename|
446
+ filename.split('/').last.split('_').first.to_i
447
+ end
448
+
449
+ unless migrated.include?(version)
450
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
451
+ end
452
+
453
+ inserted = Set.new
454
+ (versions - migrated).each do |v|
455
+ if inserted.include?(v)
456
+ raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
457
+ elsif v < version
458
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
459
+ inserted << v
460
+ end
461
+ end
462
+ end
463
+
464
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
465
+ if native = native_database_types[type.to_sym]
466
+ column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
467
+
468
+ if type == :decimal # ignore limit, use precision and scale
469
+ scale ||= native[:scale]
470
+
471
+ if precision ||= native[:precision]
472
+ if scale
473
+ column_type_sql << "(#{precision},#{scale})"
474
+ else
475
+ column_type_sql << "(#{precision})"
476
+ end
477
+ elsif scale
478
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
479
+ end
480
+
481
+ elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
482
+ column_type_sql << "(#{limit})"
483
+ end
484
+
485
+ column_type_sql
486
+ else
487
+ type
488
+ end
489
+ end
490
+
491
+ def add_column_options!(sql, options) #:nodoc:
492
+ sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
493
+ # must explicitly check for :null to allow change_column to work on migrations
494
+ if options[:null] == false
495
+ sql << " NOT NULL"
496
+ end
497
+ end
498
+
499
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
500
+ # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
501
+ #
502
+ # distinct("posts.id", "posts.created_at desc")
503
+ def distinct(columns, order_by)
504
+ "DISTINCT #{columns}"
505
+ end
506
+
507
+ # Adds timestamps (created_at and updated_at) columns to the named table.
508
+ # ===== Examples
509
+ # add_timestamps(:suppliers)
510
+ def add_timestamps(table_name)
511
+ add_column table_name, :created_at, :datetime, :null => false
512
+ add_column table_name, :updated_at, :datetime, :null => false
513
+ end
514
+
515
+ # Removes the timestamp columns (created_at and updated_at) from the table definition.
516
+ # ===== Examples
517
+ # remove_timestamps(:suppliers)
518
+ def remove_timestamps(table_name)
519
+ remove_column table_name, :updated_at
520
+ remove_column table_name, :created_at
521
+ end
522
+
523
+ protected
524
+ def add_index_sort_order(option_strings, column_names, options = {})
525
+ if options.is_a?(Hash) && order = options[:order]
526
+ case order
527
+ when Hash
528
+ column_names.each {|name| option_strings[name] += " #{order[name].to_s.upcase}" if order.has_key?(name)}
529
+ when String
530
+ column_names.each {|name| option_strings[name] += " #{order.upcase}"}
531
+ end
532
+ end
533
+
534
+ return option_strings
535
+ end
536
+
537
+ # Overridden by the mysql adapter for supporting index lengths
538
+ def quoted_columns_for_index(column_names, options = {})
539
+ option_strings = Hash[column_names.map {|name| [name, '']}]
540
+
541
+ # add index sort order if supported
542
+ if supports_index_sort_order?
543
+ option_strings = add_index_sort_order(option_strings, column_names, options)
544
+ end
545
+
546
+ column_names.map {|name| quote_column_name(name) + option_strings[name]}
547
+ end
548
+
549
+ def options_include_default?(options)
550
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
551
+ end
552
+
553
+ def add_index_options(table_name, column_name, options = {})
554
+ column_names = Array.wrap(column_name)
555
+ index_name = index_name(table_name, :column => column_names)
556
+
557
+ if Hash === options # legacy support, since this param was a string
558
+ index_type = options[:unique] ? "UNIQUE" : ""
559
+ index_name = options[:name].to_s if options.key?(:name)
560
+ else
561
+ index_type = options
562
+ end
563
+
564
+ if index_name.length > index_name_length
565
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
566
+ end
567
+ if index_name_exists?(table_name, index_name, false)
568
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
569
+ end
570
+ index_columns = quoted_columns_for_index(column_names, options).join(", ")
571
+
572
+ [index_name, index_type, index_columns]
573
+ end
574
+
575
+ def index_name_for_remove(table_name, options = {})
576
+ index_name = index_name(table_name, options)
577
+
578
+ unless index_name_exists?(table_name, index_name, true)
579
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
580
+ end
581
+
582
+ index_name
583
+ end
584
+
585
+ def columns_for_remove(table_name, *column_names)
586
+ column_names = column_names.flatten
587
+
588
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
589
+ column_names.map {|column_name| quote_column_name(column_name) }
590
+ end
591
+
592
+ private
593
+ def table_definition
594
+ TableDefinition.new(self)
595
+ end
596
+ end
597
+ end
598
+ end