activerecord 3.0.0 → 4.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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,5 +1,5 @@
1
- require 'active_support/core_ext/kernel/singleton_class'
2
- require 'active_support/core_ext/module/aliasing'
1
+ require "active_support/core_ext/class/attribute_accessors"
2
+ require 'set'
3
3
 
4
4
  module ActiveRecord
5
5
  # Exception that can be raised to stop migrations from going backwards.
@@ -30,6 +30,12 @@ module ActiveRecord
30
30
  end
31
31
  end
32
32
 
33
+ class PendingMigrationError < ActiveRecordError#:nodoc:
34
+ def initialize
35
+ super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{Rails.env}' to resolve this issue.")
36
+ end
37
+ end
38
+
33
39
  # = Active Record Migrations
34
40
  #
35
41
  # Migrations can manage the evolution of a schema used by several physical
@@ -43,58 +49,61 @@ module ActiveRecord
43
49
  # Example of a simple migration:
44
50
  #
45
51
  # class AddSsl < ActiveRecord::Migration
46
- # def self.up
47
- # add_column :accounts, :ssl_enabled, :boolean, :default => 1
52
+ # def up
53
+ # add_column :accounts, :ssl_enabled, :boolean, default: true
48
54
  # end
49
55
  #
50
- # def self.down
56
+ # def down
51
57
  # remove_column :accounts, :ssl_enabled
52
58
  # end
53
59
  # end
54
60
  #
55
61
  # This migration will add a boolean flag to the accounts table and remove it
56
62
  # if you're backing out of the migration. It shows how all migrations have
57
- # two class methods +up+ and +down+ that describes the transformations
63
+ # two methods +up+ and +down+ that describes the transformations
58
64
  # required to implement or remove the migration. These methods can consist
59
- # of both the migration specific methods like add_column and remove_column,
65
+ # of both the migration specific methods like +add_column+ and +remove_column+,
60
66
  # but may also contain regular Ruby code for generating data needed for the
61
67
  # transformations.
62
68
  #
63
69
  # Example of a more complex migration that also needs to initialize data:
64
70
  #
65
71
  # class AddSystemSettings < ActiveRecord::Migration
66
- # def self.up
72
+ # def up
67
73
  # create_table :system_settings do |t|
68
74
  # t.string :name
69
75
  # t.string :label
70
- # t.text :value
76
+ # t.text :value
71
77
  # t.string :type
72
- # t.integer :position
78
+ # t.integer :position
73
79
  # end
74
80
  #
75
- # SystemSetting.create :name => "notice",
76
- # :label => "Use notice?",
77
- # :value => 1
81
+ # SystemSetting.create name: 'notice',
82
+ # label: 'Use notice?',
83
+ # value: 1
78
84
  # end
79
85
  #
80
- # def self.down
86
+ # def down
81
87
  # drop_table :system_settings
82
88
  # end
83
89
  # end
84
90
  #
85
- # This migration first adds the system_settings table, then creates the very
91
+ # This migration first adds the +system_settings+ table, then creates the very
86
92
  # first row in it using the Active Record model that relies on the table. It
87
- # also uses the more advanced create_table syntax where you can specify a
93
+ # also uses the more advanced +create_table+ syntax where you can specify a
88
94
  # complete table schema in one block call.
89
95
  #
90
96
  # == Available transformations
91
97
  #
92
- # * <tt>create_table(name, options)</tt> Creates a table called +name+ and
98
+ # * <tt>create_table(name, options)</tt>: Creates a table called +name+ and
93
99
  # makes the table object available to a block that can then add columns to it,
94
- # following the same format as add_column. See example above. The options hash
100
+ # following the same format as +add_column+. See example above. The options hash
95
101
  # is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
96
102
  # table definition.
97
103
  # * <tt>drop_table(name)</tt>: Drops the table called +name+.
104
+ # * <tt>change_table(name, options)</tt>: Allows to make column alterations to
105
+ # the table called +name+. It makes the table object available to a block that
106
+ # can then add/remove columns, indexes or foreign keys to it.
98
107
  # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
99
108
  # to +new_name+.
100
109
  # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
@@ -103,22 +112,25 @@ module ActiveRecord
103
112
  # <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
104
113
  # <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
105
114
  # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
106
- # specified by passing an +options+ hash like <tt>{ :default => 11 }</tt>.
115
+ # specified by passing an +options+ hash like <tt>{ default: 11 }</tt>.
107
116
  # Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
108
- # <tt>{ :limit => 50, :null => false }</tt>) -- see
117
+ # <tt>{ limit: 50, null: false }</tt>) -- see
109
118
  # ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
110
119
  # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
111
120
  # a column but keeps the type and content.
112
121
  # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
113
122
  # the column to a different type using the same parameters as add_column.
114
- # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named
115
- # +column_name+ from the table called +table_name+.
123
+ # * <tt>remove_column(table_name, column_names)</tt>: Removes the column listed in
124
+ # +column_names+ from the table called +table_name+.
116
125
  # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
117
126
  # with the name of the column. Other options include
118
- # <tt>:name</tt> and <tt>:unique</tt> (e.g.
119
- # <tt>{ :name => "users_name_index", :unique => true }</tt>).
120
- # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified
121
- # by +index_name+.
127
+ # <tt>:name</tt>, <tt>:unique</tt> (e.g.
128
+ # <tt>{ name: 'users_name_index', unique: true }</tt>) and <tt>:order</tt>
129
+ # (e.g. <tt>{ order: { name: :desc } }</tt>).
130
+ # * <tt>remove_index(table_name, column: column_name)</tt>: Removes the index
131
+ # specified by +column_name+.
132
+ # * <tt>remove_index(table_name, name: index_name)</tt>: Removes the index
133
+ # specified by +index_name+.
122
134
  #
123
135
  # == Irreversible transformations
124
136
  #
@@ -138,7 +150,7 @@ module ActiveRecord
138
150
  # in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
139
151
  # UTC formatted date and time that the migration was generated.
140
152
  #
141
- # You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
153
+ # You may then edit the <tt>up</tt> and <tt>down</tt> methods of
142
154
  # MyNewMigration.
143
155
  #
144
156
  # There is a special syntactic shortcut to generate migrations that add fields to a table.
@@ -147,11 +159,11 @@ module ActiveRecord
147
159
  #
148
160
  # This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
149
161
  # class AddFieldnameToTablename < ActiveRecord::Migration
150
- # def self.up
162
+ # def up
151
163
  # add_column :tablenames, :fieldname, :string
152
164
  # end
153
165
  #
154
- # def self.down
166
+ # def down
155
167
  # remove_column :tablenames, :fieldname
156
168
  # end
157
169
  # end
@@ -179,11 +191,11 @@ module ActiveRecord
179
191
  # Not all migrations change the schema. Some just fix the data:
180
192
  #
181
193
  # class RemoveEmptyTags < ActiveRecord::Migration
182
- # def self.up
183
- # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
194
+ # def up
195
+ # Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
184
196
  # end
185
197
  #
186
- # def self.down
198
+ # def down
187
199
  # # not much we can do to restore deleted data
188
200
  # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
189
201
  # end
@@ -192,12 +204,12 @@ module ActiveRecord
192
204
  # Others remove columns when they migrate up instead of down:
193
205
  #
194
206
  # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
195
- # def self.up
207
+ # def up
196
208
  # remove_column :items, :incomplete_items_count
197
209
  # remove_column :items, :completed_items_count
198
210
  # end
199
211
  #
200
- # def self.down
212
+ # def down
201
213
  # add_column :items, :incomplete_items_count
202
214
  # add_column :items, :completed_items_count
203
215
  # end
@@ -206,11 +218,11 @@ module ActiveRecord
206
218
  # And sometimes you need to do something in SQL not abstracted directly by migrations:
207
219
  #
208
220
  # class MakeJoinUnique < ActiveRecord::Migration
209
- # def self.up
221
+ # def up
210
222
  # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
211
223
  # end
212
224
  #
213
- # def self.down
225
+ # def down
214
226
  # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
215
227
  # end
216
228
  # end
@@ -223,10 +235,10 @@ module ActiveRecord
223
235
  # latest column data from after the new column was added. Example:
224
236
  #
225
237
  # class AddPeopleSalary < ActiveRecord::Migration
226
- # def self.up
238
+ # def up
227
239
  # add_column :people, :salary, :integer
228
240
  # Person.reset_column_information
229
- # Person.find(:all).each do |p|
241
+ # Person.all.each do |p|
230
242
  # p.update_attribute :salary, SalaryCalculator.compute(p)
231
243
  # end
232
244
  # end
@@ -243,10 +255,10 @@ module ActiveRecord
243
255
  # You can also insert your own messages and benchmarks by using the +say_with_time+
244
256
  # method:
245
257
  #
246
- # def self.up
258
+ # def up
247
259
  # ...
248
260
  # say_with_time "Updating salaries..." do
249
- # Person.find(:all).each do |p|
261
+ # Person.all.each do |p|
250
262
  # p.update_attribute :salary, SalaryCalculator.compute(p)
251
263
  # end
252
264
  # end
@@ -286,113 +298,415 @@ module ActiveRecord
286
298
  #
287
299
  # In application.rb.
288
300
  #
301
+ # == Reversible Migrations
302
+ #
303
+ # Starting with Rails 3.1, you will be able to define reversible migrations.
304
+ # Reversible migrations are migrations that know how to go +down+ for you.
305
+ # You simply supply the +up+ logic, and the Migration system will figure out
306
+ # how to execute the down commands for you.
307
+ #
308
+ # To define a reversible migration, define the +change+ method in your
309
+ # migration like this:
310
+ #
311
+ # class TenderloveMigration < ActiveRecord::Migration
312
+ # def change
313
+ # create_table(:horses) do |t|
314
+ # t.column :content, :text
315
+ # t.column :remind_at, :datetime
316
+ # end
317
+ # end
318
+ # end
319
+ #
320
+ # This migration will create the horses table for you on the way up, and
321
+ # automatically figure out how to drop the table on the way down.
322
+ #
323
+ # Some commands like +remove_column+ cannot be reversed. If you care to
324
+ # define how to move up and down in these cases, you should define the +up+
325
+ # and +down+ methods as before.
326
+ #
327
+ # If a command cannot be reversed, an
328
+ # <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
329
+ # the migration is moving down.
330
+ #
331
+ # For a list of commands that are reversible, please see
332
+ # <tt>ActiveRecord::Migration::CommandRecorder</tt>.
333
+ #
334
+ # == Transactional Migrations
335
+ #
336
+ # If the database adapter supports DDL transactions, all migrations will
337
+ # automatically be wrapped in a transaction. There are queries that you
338
+ # can't execute inside a transaction though, and for these situations
339
+ # you can turn the automatic transactions off.
340
+ #
341
+ # class ChangeEnum < ActiveRecord::Migration
342
+ # disable_ddl_transaction!
343
+ #
344
+ # def up
345
+ # execute "ALTER TYPE model_size ADD VALUE 'new_value'"
346
+ # end
347
+ # end
348
+ #
349
+ # Remember that you can still open your own transactions, even if you
350
+ # are in a Migration with <tt>self.disable_ddl_transaction!</tt>.
289
351
  class Migration
290
- @@verbose = true
291
- cattr_accessor :verbose
352
+ autoload :CommandRecorder, 'active_record/migration/command_recorder'
292
353
 
293
- class << self
294
- def up_with_benchmarks #:nodoc:
295
- migrate(:up)
354
+
355
+ # This class is used to verify that all migrations have been run before
356
+ # loading a web page if config.active_record.migration_error is set to :page_load
357
+ class CheckPending
358
+ def initialize(app)
359
+ @app = app
360
+ @last_check = 0
296
361
  end
297
362
 
298
- def down_with_benchmarks #:nodoc:
299
- migrate(:down)
363
+ def call(env)
364
+ mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
365
+ if @last_check < mtime
366
+ ActiveRecord::Migration.check_pending!
367
+ @last_check = mtime
368
+ end
369
+ @app.call(env)
300
370
  end
371
+ end
301
372
 
302
- # Execute this migration in the named direction
303
- def migrate(direction)
304
- return unless respond_to?(direction)
373
+ class << self
374
+ attr_accessor :delegate # :nodoc:
375
+ attr_accessor :disable_ddl_transaction # :nodoc:
376
+ end
305
377
 
306
- case direction
307
- when :up then announce "migrating"
308
- when :down then announce "reverting"
309
- end
378
+ def self.check_pending!
379
+ raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?
380
+ end
310
381
 
311
- result = nil
312
- time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
382
+ def self.method_missing(name, *args, &block) # :nodoc:
383
+ (delegate || superclass.delegate).send(name, *args, &block)
384
+ end
313
385
 
314
- case direction
315
- when :up then announce "migrated (%.4fs)" % time.real; write
316
- when :down then announce "reverted (%.4fs)" % time.real; write
317
- end
386
+ def self.migrate(direction)
387
+ new.migrate direction
388
+ end
318
389
 
319
- result
320
- end
390
+ # Disable DDL transactions for this migration.
391
+ def self.disable_ddl_transaction!
392
+ @disable_ddl_transaction = true
393
+ end
321
394
 
322
- # Because the method added may do an alias_method, it can be invoked
323
- # recursively. We use @ignore_new_methods as a guard to indicate whether
324
- # it is safe for the call to proceed.
325
- def singleton_method_added(sym) #:nodoc:
326
- return if defined?(@ignore_new_methods) && @ignore_new_methods
395
+ def disable_ddl_transaction # :nodoc:
396
+ self.class.disable_ddl_transaction
397
+ end
327
398
 
328
- begin
329
- @ignore_new_methods = true
399
+ cattr_accessor :verbose
400
+ attr_accessor :name, :version
401
+
402
+ def initialize(name = self.class.name, version = nil)
403
+ @name = name
404
+ @version = version
405
+ @connection = nil
406
+ end
330
407
 
331
- case sym
332
- when :up, :down
333
- singleton_class.send(:alias_method_chain, sym, "benchmarks")
408
+ self.verbose = true
409
+ # instantiate the delegate object after initialize is defined
410
+ self.delegate = new
411
+
412
+ # Reverses the migration commands for the given block and
413
+ # the given migrations.
414
+ #
415
+ # The following migration will remove the table 'horses'
416
+ # and create the table 'apples' on the way up, and the reverse
417
+ # on the way down.
418
+ #
419
+ # class FixTLMigration < ActiveRecord::Migration
420
+ # def change
421
+ # revert do
422
+ # create_table(:horses) do |t|
423
+ # t.text :content
424
+ # t.datetime :remind_at
425
+ # end
426
+ # end
427
+ # create_table(:apples) do |t|
428
+ # t.string :variety
429
+ # end
430
+ # end
431
+ # end
432
+ #
433
+ # Or equivalently, if +TenderloveMigration+ is defined as in the
434
+ # documentation for Migration:
435
+ #
436
+ # require_relative '2012121212_tenderlove_migration'
437
+ #
438
+ # class FixupTLMigration < ActiveRecord::Migration
439
+ # def change
440
+ # revert TenderloveMigration
441
+ #
442
+ # create_table(:apples) do |t|
443
+ # t.string :variety
444
+ # end
445
+ # end
446
+ # end
447
+ #
448
+ # This command can be nested.
449
+ def revert(*migration_classes)
450
+ run(*migration_classes.reverse, revert: true) unless migration_classes.empty?
451
+ if block_given?
452
+ if @connection.respond_to? :revert
453
+ @connection.revert { yield }
454
+ else
455
+ recorder = CommandRecorder.new(@connection)
456
+ @connection = recorder
457
+ suppress_messages do
458
+ @connection.revert { yield }
459
+ end
460
+ @connection = recorder.delegate
461
+ recorder.commands.each do |cmd, args, block|
462
+ send(cmd, *args, &block)
334
463
  end
335
- ensure
336
- @ignore_new_methods = false
337
464
  end
338
465
  end
466
+ end
339
467
 
340
- def write(text="")
341
- puts(text) if verbose
468
+ def reverting?
469
+ @connection.respond_to?(:reverting) && @connection.reverting
470
+ end
471
+
472
+ class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc:
473
+ def up
474
+ yield unless reverting
342
475
  end
343
476
 
344
- def announce(message)
345
- version = defined?(@version) ? @version : nil
477
+ def down
478
+ yield if reverting
479
+ end
480
+ end
346
481
 
347
- text = "#{version} #{name}: #{message}"
348
- length = [0, 75 - text.length].max
349
- write "== %s %s" % [text, "=" * length]
482
+ # Used to specify an operation that can be run in one direction or another.
483
+ # Call the methods +up+ and +down+ of the yielded object to run a block
484
+ # only in one given direction.
485
+ # The whole block will be called in the right order within the migration.
486
+ #
487
+ # In the following example, the looping on users will always be done
488
+ # when the three columns 'first_name', 'last_name' and 'full_name' exist,
489
+ # even when migrating down:
490
+ #
491
+ # class SplitNameMigration < ActiveRecord::Migration
492
+ # def change
493
+ # add_column :users, :first_name, :string
494
+ # add_column :users, :last_name, :string
495
+ #
496
+ # reversible do |dir|
497
+ # User.reset_column_information
498
+ # User.all.each do |u|
499
+ # dir.up { u.first_name, u.last_name = u.full_name.split(' ') }
500
+ # dir.down { u.full_name = "#{u.first_name} #{u.last_name}" }
501
+ # u.save
502
+ # end
503
+ # end
504
+ #
505
+ # revert { add_column :users, :full_name, :string }
506
+ # end
507
+ # end
508
+ def reversible
509
+ helper = ReversibleBlockHelper.new(reverting?)
510
+ execute_block{ yield helper }
511
+ end
512
+
513
+ # Runs the given migration classes.
514
+ # Last argument can specify options:
515
+ # - :direction (default is :up)
516
+ # - :revert (default is false)
517
+ def run(*migration_classes)
518
+ opts = migration_classes.extract_options!
519
+ dir = opts[:direction] || :up
520
+ dir = (dir == :down ? :up : :down) if opts[:revert]
521
+ if reverting?
522
+ # If in revert and going :up, say, we want to execute :down without reverting, so
523
+ revert { run(*migration_classes, direction: dir, revert: true) }
524
+ else
525
+ migration_classes.each do |migration_class|
526
+ migration_class.new.exec_migration(@connection, dir)
527
+ end
350
528
  end
529
+ end
530
+
531
+ def up
532
+ self.class.delegate = self
533
+ return unless self.class.respond_to?(:up)
534
+ self.class.up
535
+ end
536
+
537
+ def down
538
+ self.class.delegate = self
539
+ return unless self.class.respond_to?(:down)
540
+ self.class.down
541
+ end
542
+
543
+ # Execute this migration in the named direction
544
+ def migrate(direction)
545
+ return unless respond_to?(direction)
351
546
 
352
- def say(message, subitem=false)
353
- write "#{subitem ? " ->" : "--"} #{message}"
547
+ case direction
548
+ when :up then announce "migrating"
549
+ when :down then announce "reverting"
354
550
  end
355
551
 
356
- def say_with_time(message)
357
- say(message)
358
- result = nil
359
- time = Benchmark.measure { result = yield }
360
- say "%.4fs" % time.real, :subitem
361
- say("#{result} rows", :subitem) if result.is_a?(Integer)
362
- result
552
+ time = nil
553
+ ActiveRecord::Base.connection_pool.with_connection do |conn|
554
+ time = Benchmark.measure do
555
+ exec_migration(conn, direction)
556
+ end
363
557
  end
364
558
 
365
- def suppress_messages
366
- save, self.verbose = verbose, false
367
- yield
368
- ensure
369
- self.verbose = save
559
+ case direction
560
+ when :up then announce "migrated (%.4fs)" % time.real; write
561
+ when :down then announce "reverted (%.4fs)" % time.real; write
370
562
  end
563
+ end
371
564
 
372
- def connection
373
- ActiveRecord::Base.connection
565
+ def exec_migration(conn, direction)
566
+ @connection = conn
567
+ if respond_to?(:change)
568
+ if direction == :down
569
+ revert { change }
570
+ else
571
+ change
572
+ end
573
+ else
574
+ send(direction)
374
575
  end
576
+ ensure
577
+ @connection = nil
578
+ end
579
+
580
+ def write(text="")
581
+ puts(text) if verbose
582
+ end
583
+
584
+ def announce(message)
585
+ text = "#{version} #{name}: #{message}"
586
+ length = [0, 75 - text.length].max
587
+ write "== %s %s" % [text, "=" * length]
588
+ end
589
+
590
+ def say(message, subitem=false)
591
+ write "#{subitem ? " ->" : "--"} #{message}"
592
+ end
593
+
594
+ def say_with_time(message)
595
+ say(message)
596
+ result = nil
597
+ time = Benchmark.measure { result = yield }
598
+ say "%.4fs" % time.real, :subitem
599
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
600
+ result
601
+ end
602
+
603
+ def suppress_messages
604
+ save, self.verbose = verbose, false
605
+ yield
606
+ ensure
607
+ self.verbose = save
608
+ end
609
+
610
+ def connection
611
+ @connection || ActiveRecord::Base.connection
612
+ end
375
613
 
376
- def method_missing(method, *arguments, &block)
377
- arg_list = arguments.map{ |a| a.inspect } * ', '
614
+ def method_missing(method, *arguments, &block)
615
+ arg_list = arguments.map{ |a| a.inspect } * ', '
378
616
 
379
- say_with_time "#{method}(#{arg_list})" do
617
+ say_with_time "#{method}(#{arg_list})" do
618
+ unless @connection.respond_to? :revert
380
619
  unless arguments.empty? || method == :execute
381
620
  arguments[0] = Migrator.proper_table_name(arguments.first)
621
+ arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
382
622
  end
383
- connection.send(method, *arguments, &block)
384
623
  end
624
+ return super unless connection.respond_to?(method)
625
+ connection.send(method, *arguments, &block)
626
+ end
627
+ end
628
+
629
+ def copy(destination, sources, options = {})
630
+ copied = []
631
+
632
+ FileUtils.mkdir_p(destination) unless File.exists?(destination)
633
+
634
+ destination_migrations = ActiveRecord::Migrator.migrations(destination)
635
+ last = destination_migrations.last
636
+ sources.each do |scope, path|
637
+ source_migrations = ActiveRecord::Migrator.migrations(path)
638
+
639
+ source_migrations.each do |migration|
640
+ source = File.binread(migration.filename)
641
+ inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n"
642
+ if /\A#.*\b(?:en)?coding:\s*\S+/ =~ source
643
+ # If we have a magic comment in the original migration,
644
+ # insert our comment after the first newline(end of the magic comment line)
645
+ # so the magic keep working.
646
+ # Note that magic comments must be at the first line(except sh-bang).
647
+ source[/\n/] = "\n#{inserted_comment}"
648
+ else
649
+ source = "#{inserted_comment}#{source}"
650
+ end
651
+
652
+ if duplicate = destination_migrations.detect { |m| m.name == migration.name }
653
+ if options[:on_skip] && duplicate.scope != scope.to_s
654
+ options[:on_skip].call(scope, migration)
655
+ end
656
+ next
657
+ end
658
+
659
+ migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
660
+ new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
661
+ old_path, migration.filename = migration.filename, new_path
662
+ last = migration
663
+
664
+ File.binwrite(migration.filename, source)
665
+ copied << migration
666
+ options[:on_copy].call(scope, migration, old_path) if options[:on_copy]
667
+ destination_migrations << migration
668
+ end
669
+ end
670
+
671
+ copied
672
+ end
673
+
674
+ def next_migration_number(number)
675
+ if ActiveRecord::Base.timestamped_migrations
676
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
677
+ else
678
+ "%.3d" % number
679
+ end
680
+ end
681
+
682
+ private
683
+ def execute_block
684
+ if connection.respond_to? :execute_block
685
+ super # use normal delegation to record the block
686
+ else
687
+ yield
385
688
  end
386
689
  end
387
690
  end
388
691
 
389
692
  # MigrationProxy is used to defer loading of the actual migration classes
390
693
  # until they are needed
391
- class MigrationProxy
694
+ class MigrationProxy < Struct.new(:name, :version, :filename, :scope)
695
+
696
+ def initialize(name, version, filename, scope)
697
+ super
698
+ @migration = nil
699
+ end
700
+
701
+ def basename
702
+ File.basename(filename)
703
+ end
392
704
 
393
- attr_accessor :name, :version, :filename
705
+ def mtime
706
+ File.mtime filename
707
+ end
394
708
 
395
- delegate :migrate, :announce, :write, :to=>:migration
709
+ delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
396
710
 
397
711
  private
398
712
 
@@ -402,56 +716,75 @@ module ActiveRecord
402
716
 
403
717
  def load_migration
404
718
  require(File.expand_path(filename))
405
- name.constantize
719
+ name.constantize.new
406
720
  end
407
721
 
408
722
  end
409
723
 
724
+ class NullMigration < MigrationProxy #:nodoc:
725
+ def initialize
726
+ super(nil, 0, nil, nil)
727
+ end
728
+
729
+ def mtime
730
+ 0
731
+ end
732
+ end
733
+
410
734
  class Migrator#:nodoc:
411
735
  class << self
412
- def migrate(migrations_path, target_version = nil)
736
+ attr_writer :migrations_paths
737
+ alias :migrations_path= :migrations_paths=
738
+
739
+ def migrate(migrations_paths, target_version = nil, &block)
413
740
  case
414
- when target_version.nil?
415
- up(migrations_path, target_version)
416
- when current_version == 0 && target_version == 0
417
- when current_version > target_version
418
- down(migrations_path, target_version)
419
- else
420
- up(migrations_path, target_version)
741
+ when target_version.nil?
742
+ up(migrations_paths, target_version, &block)
743
+ when current_version == 0 && target_version == 0
744
+ []
745
+ when current_version > target_version
746
+ down(migrations_paths, target_version, &block)
747
+ else
748
+ up(migrations_paths, target_version, &block)
421
749
  end
422
750
  end
423
751
 
424
- def rollback(migrations_path, steps=1)
425
- move(:down, migrations_path, steps)
752
+ def rollback(migrations_paths, steps=1)
753
+ move(:down, migrations_paths, steps)
426
754
  end
427
755
 
428
- def forward(migrations_path, steps=1)
429
- move(:up, migrations_path, steps)
756
+ def forward(migrations_paths, steps=1)
757
+ move(:up, migrations_paths, steps)
430
758
  end
431
759
 
432
- def up(migrations_path, target_version = nil)
433
- self.new(:up, migrations_path, target_version).migrate
760
+ def up(migrations_paths, target_version = nil)
761
+ migrations = migrations(migrations_paths)
762
+ migrations.select! { |m| yield m } if block_given?
763
+
764
+ self.new(:up, migrations, target_version).migrate
434
765
  end
435
766
 
436
- def down(migrations_path, target_version = nil)
437
- self.new(:down, migrations_path, target_version).migrate
767
+ def down(migrations_paths, target_version = nil, &block)
768
+ migrations = migrations(migrations_paths)
769
+ migrations.select! { |m| yield m } if block_given?
770
+
771
+ self.new(:down, migrations, target_version).migrate
438
772
  end
439
773
 
440
- def run(direction, migrations_path, target_version)
441
- self.new(direction, migrations_path, target_version).run
774
+ def run(direction, migrations_paths, target_version)
775
+ self.new(direction, migrations(migrations_paths), target_version).run
442
776
  end
443
777
 
444
- def migrations_path
445
- 'db/migrate'
778
+ def open(migrations_paths)
779
+ self.new(:up, migrations(migrations_paths), nil)
446
780
  end
447
781
 
448
782
  def schema_migrations_table_name
449
- Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
783
+ SchemaMigration.table_name
450
784
  end
451
785
 
452
786
  def get_all_versions
453
- table = Arel::Table.new(schema_migrations_table_name)
454
- Base.connection.select_values(table.project(table['version']).to_sql).map{ |v| v.to_i }.sort
787
+ SchemaMigration.all.map { |x| x.version.to_i }.sort
455
788
  end
456
789
 
457
790
  def current_version
@@ -463,155 +796,220 @@ module ActiveRecord
463
796
  end
464
797
  end
465
798
 
799
+ def needs_migration?
800
+ current_version < last_version
801
+ end
802
+
803
+ def last_version
804
+ last_migration.version
805
+ end
806
+
807
+ def last_migration #:nodoc:
808
+ migrations(migrations_paths).last || NullMigration.new
809
+ end
810
+
466
811
  def proper_table_name(name)
467
812
  # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
468
- name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
813
+ if name.respond_to? :table_name
814
+ name.table_name
815
+ else
816
+ "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
817
+ end
818
+ end
819
+
820
+ def migrations_paths
821
+ @migrations_paths ||= ['db/migrate']
822
+ # just to not break things if someone uses: migration_path = some_string
823
+ Array(@migrations_paths)
824
+ end
825
+
826
+ def migrations_path
827
+ migrations_paths.first
828
+ end
829
+
830
+ def migrations(paths)
831
+ paths = Array(paths)
832
+
833
+ files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
834
+
835
+ migrations = files.map do |file|
836
+ version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first
837
+
838
+ raise IllegalMigrationNameError.new(file) unless version
839
+ version = version.to_i
840
+ name = name.camelize
841
+
842
+ MigrationProxy.new(name, version, file, scope)
843
+ end
844
+
845
+ migrations.sort_by(&:version)
469
846
  end
470
847
 
471
848
  private
472
849
 
473
- def move(direction, migrations_path, steps)
474
- migrator = self.new(direction, migrations_path)
850
+ def move(direction, migrations_paths, steps)
851
+ migrator = self.new(direction, migrations(migrations_paths))
475
852
  start_index = migrator.migrations.index(migrator.current_migration)
476
853
 
477
854
  if start_index
478
855
  finish = migrator.migrations[start_index + steps]
479
856
  version = finish ? finish.version : 0
480
- send(direction, migrations_path, version)
857
+ send(direction, migrations_paths, version)
481
858
  end
482
859
  end
483
860
  end
484
861
 
485
- def initialize(direction, migrations_path, target_version = nil)
862
+ def initialize(direction, migrations, target_version = nil)
486
863
  raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
487
- Base.connection.initialize_schema_migrations_table
488
- @direction, @migrations_path, @target_version = direction, migrations_path, target_version
864
+
865
+ @direction = direction
866
+ @target_version = target_version
867
+ @migrated_versions = nil
868
+
869
+ if Array(migrations).grep(String).empty?
870
+ @migrations = migrations
871
+ else
872
+ ActiveSupport::Deprecation.warn "instantiate this class with a list of migrations"
873
+ @migrations = self.class.migrations(migrations)
874
+ end
875
+
876
+ validate(@migrations)
877
+
878
+ ActiveRecord::SchemaMigration.create_table
489
879
  end
490
880
 
491
881
  def current_version
492
- migrated.last || 0
882
+ migrated.max || 0
493
883
  end
494
884
 
495
885
  def current_migration
496
886
  migrations.detect { |m| m.version == current_version }
497
887
  end
888
+ alias :current :current_migration
498
889
 
499
890
  def run
500
- target = migrations.detect { |m| m.version == @target_version }
501
- raise UnknownMigrationVersionError.new(@target_version) if target.nil?
502
- unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
503
- target.migrate(@direction)
504
- record_version_state_after_migrating(target.version)
891
+ migration = migrations.detect { |m| m.version == @target_version }
892
+ raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
893
+ unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
894
+ begin
895
+ execute_migration_in_transaction(migration, @direction)
896
+ rescue => e
897
+ canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
898
+ raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
899
+ end
505
900
  end
506
901
  end
507
902
 
508
903
  def migrate
509
- current = migrations.detect { |m| m.version == current_version }
510
- target = migrations.detect { |m| m.version == @target_version }
511
-
512
- if target.nil? && !@target_version.nil? && @target_version > 0
904
+ if !target && @target_version && @target_version > 0
513
905
  raise UnknownMigrationVersionError.new(@target_version)
514
906
  end
515
907
 
516
- start = up? ? 0 : (migrations.index(current) || 0)
517
- finish = migrations.index(target) || migrations.size - 1
518
- runnable = migrations[start..finish]
908
+ running = runnable
519
909
 
520
- # skip the last migration if we're headed down, but not ALL the way down
521
- runnable.pop if down? && !target.nil?
910
+ if block_given?
911
+ message = "block argument to migrate is deprecated, please filter migrations before constructing the migrator"
912
+ ActiveSupport::Deprecation.warn message
913
+ running.select! { |m| yield m }
914
+ end
522
915
 
523
- runnable.each do |migration|
916
+ running.each do |migration|
524
917
  Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
525
918
 
526
- # On our way up, we skip migrating the ones we've already migrated
527
- next if up? && migrated.include?(migration.version.to_i)
528
-
529
- # On our way down, we skip reverting the ones we've never migrated
530
- if down? && !migrated.include?(migration.version.to_i)
531
- migration.announce 'never migrated, skipping'; migration.write
532
- next
533
- end
534
-
535
919
  begin
536
- ddl_transaction do
537
- migration.migrate(@direction)
538
- record_version_state_after_migrating(migration.version)
539
- end
920
+ execute_migration_in_transaction(migration, @direction)
540
921
  rescue => e
541
- canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
922
+ canceled_msg = use_transaction?(migration) ? "this and " : ""
542
923
  raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
543
924
  end
544
925
  end
545
926
  end
546
927
 
547
- def migrations
548
- @migrations ||= begin
549
- files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
550
-
551
- migrations = files.inject([]) do |klasses, file|
552
- version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
553
-
554
- raise IllegalMigrationNameError.new(file) unless version
555
- version = version.to_i
556
-
557
- if klasses.detect { |m| m.version == version }
558
- raise DuplicateMigrationVersionError.new(version)
559
- end
560
-
561
- if klasses.detect { |m| m.name == name.camelize }
562
- raise DuplicateMigrationNameError.new(name.camelize)
563
- end
564
-
565
- migration = MigrationProxy.new
566
- migration.name = name.camelize
567
- migration.version = version
568
- migration.filename = file
569
- klasses << migration
570
- end
571
-
572
- migrations = migrations.sort_by { |m| m.version }
573
- down? ? migrations.reverse : migrations
928
+ def runnable
929
+ runnable = migrations[start..finish]
930
+ if up?
931
+ runnable.reject { |m| ran?(m) }
932
+ else
933
+ # skip the last migration if we're headed down, but not ALL the way down
934
+ runnable.pop if target
935
+ runnable.find_all { |m| ran?(m) }
574
936
  end
575
937
  end
576
938
 
939
+ def migrations
940
+ down? ? @migrations.reverse : @migrations.sort_by(&:version)
941
+ end
942
+
577
943
  def pending_migrations
578
944
  already_migrated = migrated
579
- migrations.reject { |m| already_migrated.include?(m.version.to_i) }
945
+ migrations.reject { |m| already_migrated.include?(m.version) }
580
946
  end
581
947
 
582
948
  def migrated
583
- @migrated_versions ||= self.class.get_all_versions
949
+ @migrated_versions ||= Set.new(self.class.get_all_versions)
584
950
  end
585
951
 
586
952
  private
587
- def record_version_state_after_migrating(version)
588
- table = Arel::Table.new(self.class.schema_migrations_table_name)
953
+ def ran?(migration)
954
+ migrated.include?(migration.version.to_i)
955
+ end
589
956
 
590
- @migrated_versions ||= []
591
- if down?
592
- @migrated_versions.delete(version)
593
- table.where(table["version"].eq(version.to_s)).delete
594
- else
595
- @migrated_versions.push(version).sort!
596
- table.insert table["version"] => version.to_s
597
- end
957
+ def execute_migration_in_transaction(migration, direction)
958
+ ddl_transaction(migration) do
959
+ migration.migrate(direction)
960
+ record_version_state_after_migrating(migration.version)
598
961
  end
962
+ end
599
963
 
600
- def up?
601
- @direction == :up
602
- end
964
+ def target
965
+ migrations.detect { |m| m.version == @target_version }
966
+ end
967
+
968
+ def finish
969
+ migrations.index(target) || migrations.size - 1
970
+ end
971
+
972
+ def start
973
+ up? ? 0 : (migrations.index(current) || 0)
974
+ end
975
+
976
+ def validate(migrations)
977
+ name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 }
978
+ raise DuplicateMigrationNameError.new(name) if name
979
+
980
+ version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 }
981
+ raise DuplicateMigrationVersionError.new(version) if version
982
+ end
603
983
 
604
- def down?
605
- @direction == :down
984
+ def record_version_state_after_migrating(version)
985
+ if down?
986
+ migrated.delete(version)
987
+ ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
988
+ else
989
+ migrated << version
990
+ ActiveRecord::SchemaMigration.create!(:version => version.to_s)
606
991
  end
992
+ end
607
993
 
608
- # Wrap the migration in a transaction only if supported by the adapter.
609
- def ddl_transaction(&block)
610
- if Base.connection.supports_ddl_transactions?
611
- Base.transaction { block.call }
612
- else
613
- block.call
614
- end
994
+ def up?
995
+ @direction == :up
996
+ end
997
+
998
+ def down?
999
+ @direction == :down
1000
+ end
1001
+
1002
+ # Wrap the migration in a transaction only if supported by the adapter.
1003
+ def ddl_transaction(migration)
1004
+ if use_transaction?(migration)
1005
+ Base.transaction { yield }
1006
+ else
1007
+ yield
615
1008
  end
1009
+ end
1010
+
1011
+ def use_transaction?(migration)
1012
+ !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions?
1013
+ end
616
1014
  end
617
1015
  end