activerecord 3.1.10 → 4.2.11

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.

Potentially problematic release.


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

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