activerecord 1.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. data/test/validations_test.rb +0 -126
@@ -0,0 +1,617 @@
1
+ require 'active_support/core_ext/kernel/singleton_class'
2
+ require 'active_support/core_ext/module/aliasing'
3
+
4
+ module ActiveRecord
5
+ # Exception that can be raised to stop migrations from going backwards.
6
+ class IrreversibleMigration < ActiveRecordError
7
+ end
8
+
9
+ class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
10
+ def initialize(version)
11
+ super("Multiple migrations have the version number #{version}")
12
+ end
13
+ end
14
+
15
+ class DuplicateMigrationNameError < ActiveRecordError#:nodoc:
16
+ def initialize(name)
17
+ super("Multiple migrations have the name #{name}")
18
+ end
19
+ end
20
+
21
+ class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
22
+ def initialize(version)
23
+ super("No migration with version number #{version}")
24
+ end
25
+ end
26
+
27
+ class IllegalMigrationNameError < ActiveRecordError#:nodoc:
28
+ def initialize(name)
29
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
30
+ end
31
+ end
32
+
33
+ # = Active Record Migrations
34
+ #
35
+ # Migrations can manage the evolution of a schema used by several physical
36
+ # databases. It's a solution to the common problem of adding a field to make
37
+ # a new feature work in your local database, but being unsure of how to
38
+ # push that change to other developers and to the production server. With
39
+ # migrations, you can describe the transformations in self-contained classes
40
+ # that can be checked into version control systems and executed against
41
+ # another database that might be one, two, or five versions behind.
42
+ #
43
+ # Example of a simple migration:
44
+ #
45
+ # class AddSsl < ActiveRecord::Migration
46
+ # def self.up
47
+ # add_column :accounts, :ssl_enabled, :boolean, :default => 1
48
+ # end
49
+ #
50
+ # def self.down
51
+ # remove_column :accounts, :ssl_enabled
52
+ # end
53
+ # end
54
+ #
55
+ # This migration will add a boolean flag to the accounts table and remove it
56
+ # 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
58
+ # required to implement or remove the migration. These methods can consist
59
+ # of both the migration specific methods like add_column and remove_column,
60
+ # but may also contain regular Ruby code for generating data needed for the
61
+ # transformations.
62
+ #
63
+ # Example of a more complex migration that also needs to initialize data:
64
+ #
65
+ # class AddSystemSettings < ActiveRecord::Migration
66
+ # def self.up
67
+ # create_table :system_settings do |t|
68
+ # t.string :name
69
+ # t.string :label
70
+ # t.text :value
71
+ # t.string :type
72
+ # t.integer :position
73
+ # end
74
+ #
75
+ # SystemSetting.create :name => "notice",
76
+ # :label => "Use notice?",
77
+ # :value => 1
78
+ # end
79
+ #
80
+ # def self.down
81
+ # drop_table :system_settings
82
+ # end
83
+ # end
84
+ #
85
+ # This migration first adds the system_settings table, then creates the very
86
+ # 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
88
+ # complete table schema in one block call.
89
+ #
90
+ # == Available transformations
91
+ #
92
+ # * <tt>create_table(name, options)</tt> Creates a table called +name+ and
93
+ # 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
95
+ # is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
96
+ # table definition.
97
+ # * <tt>drop_table(name)</tt>: Drops the table called +name+.
98
+ # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
99
+ # to +new_name+.
100
+ # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
101
+ # to the table called +table_name+
102
+ # named +column_name+ specified to be one of the following types:
103
+ # <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
104
+ # <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
105
+ # <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>.
107
+ # Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
108
+ # <tt>{ :limit => 50, :null => false }</tt>) -- see
109
+ # ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
110
+ # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
111
+ # a column but keeps the type and content.
112
+ # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
113
+ # 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+.
116
+ # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
117
+ # 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+.
122
+ #
123
+ # == Irreversible transformations
124
+ #
125
+ # Some transformations are destructive in a manner that cannot be reversed.
126
+ # Migrations of that kind should raise an <tt>ActiveRecord::IrreversibleMigration</tt>
127
+ # exception in their +down+ method.
128
+ #
129
+ # == Running migrations from within Rails
130
+ #
131
+ # The Rails package has several tools to help create and apply migrations.
132
+ #
133
+ # To generate a new migration, you can use
134
+ # rails generate migration MyNewMigration
135
+ #
136
+ # where MyNewMigration is the name of your migration. The generator will
137
+ # create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
138
+ # in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
139
+ # UTC formatted date and time that the migration was generated.
140
+ #
141
+ # You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
142
+ # MyNewMigration.
143
+ #
144
+ # There is a special syntactic shortcut to generate migrations that add fields to a table.
145
+ #
146
+ # rails generate migration add_fieldname_to_tablename fieldname:string
147
+ #
148
+ # This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
149
+ # class AddFieldnameToTablename < ActiveRecord::Migration
150
+ # def self.up
151
+ # add_column :tablenames, :fieldname, :string
152
+ # end
153
+ #
154
+ # def self.down
155
+ # remove_column :tablenames, :fieldname
156
+ # end
157
+ # end
158
+ #
159
+ # To run migrations against the currently configured database, use
160
+ # <tt>rake db:migrate</tt>. This will update the database by running all of the
161
+ # pending migrations, creating the <tt>schema_migrations</tt> table
162
+ # (see "About the schema_migrations table" section below) if missing. It will also
163
+ # invoke the db:schema:dump task, which will update your db/schema.rb file
164
+ # to match the structure of your database.
165
+ #
166
+ # To roll the database back to a previous migration version, use
167
+ # <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
168
+ # you wish to downgrade. If any of the migrations throw an
169
+ # <tt>ActiveRecord::IrreversibleMigration</tt> exception, that step will fail and you'll
170
+ # have some manual work to do.
171
+ #
172
+ # == Database support
173
+ #
174
+ # Migrations are currently supported in MySQL, PostgreSQL, SQLite,
175
+ # SQL Server, Sybase, and Oracle (all supported databases except DB2).
176
+ #
177
+ # == More examples
178
+ #
179
+ # Not all migrations change the schema. Some just fix the data:
180
+ #
181
+ # class RemoveEmptyTags < ActiveRecord::Migration
182
+ # def self.up
183
+ # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
184
+ # end
185
+ #
186
+ # def self.down
187
+ # # not much we can do to restore deleted data
188
+ # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
189
+ # end
190
+ # end
191
+ #
192
+ # Others remove columns when they migrate up instead of down:
193
+ #
194
+ # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
195
+ # def self.up
196
+ # remove_column :items, :incomplete_items_count
197
+ # remove_column :items, :completed_items_count
198
+ # end
199
+ #
200
+ # def self.down
201
+ # add_column :items, :incomplete_items_count
202
+ # add_column :items, :completed_items_count
203
+ # end
204
+ # end
205
+ #
206
+ # And sometimes you need to do something in SQL not abstracted directly by migrations:
207
+ #
208
+ # class MakeJoinUnique < ActiveRecord::Migration
209
+ # def self.up
210
+ # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
211
+ # end
212
+ #
213
+ # def self.down
214
+ # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
215
+ # end
216
+ # end
217
+ #
218
+ # == Using a model after changing its table
219
+ #
220
+ # Sometimes you'll want to add a column in a migration and populate it
221
+ # immediately after. In that case, you'll need to make a call to
222
+ # <tt>Base#reset_column_information</tt> in order to ensure that the model has the
223
+ # latest column data from after the new column was added. Example:
224
+ #
225
+ # class AddPeopleSalary < ActiveRecord::Migration
226
+ # def self.up
227
+ # add_column :people, :salary, :integer
228
+ # Person.reset_column_information
229
+ # Person.find(:all).each do |p|
230
+ # p.update_attribute :salary, SalaryCalculator.compute(p)
231
+ # end
232
+ # end
233
+ # end
234
+ #
235
+ # == Controlling verbosity
236
+ #
237
+ # By default, migrations will describe the actions they are taking, writing
238
+ # them to the console as they happen, along with benchmarks describing how
239
+ # long each step took.
240
+ #
241
+ # You can quiet them down by setting ActiveRecord::Migration.verbose = false.
242
+ #
243
+ # You can also insert your own messages and benchmarks by using the +say_with_time+
244
+ # method:
245
+ #
246
+ # def self.up
247
+ # ...
248
+ # say_with_time "Updating salaries..." do
249
+ # Person.find(:all).each do |p|
250
+ # p.update_attribute :salary, SalaryCalculator.compute(p)
251
+ # end
252
+ # end
253
+ # ...
254
+ # end
255
+ #
256
+ # The phrase "Updating salaries..." would then be printed, along with the
257
+ # benchmark for the block when the block completes.
258
+ #
259
+ # == About the schema_migrations table
260
+ #
261
+ # Rails versions 2.0 and prior used to create a table called
262
+ # <tt>schema_info</tt> when using migrations. This table contained the
263
+ # version of the schema as of the last applied migration.
264
+ #
265
+ # Starting with Rails 2.1, the <tt>schema_info</tt> table is
266
+ # (automatically) replaced by the <tt>schema_migrations</tt> table, which
267
+ # contains the version numbers of all the migrations applied.
268
+ #
269
+ # As a result, it is now possible to add migration files that are numbered
270
+ # lower than the current schema version: when migrating up, those
271
+ # never-applied "interleaved" migrations will be automatically applied, and
272
+ # when migrating down, never-applied "interleaved" migrations will be skipped.
273
+ #
274
+ # == Timestamped Migrations
275
+ #
276
+ # By default, Rails generates migrations that look like:
277
+ #
278
+ # 20080717013526_your_migration_name.rb
279
+ #
280
+ # The prefix is a generation timestamp (in UTC).
281
+ #
282
+ # If you'd prefer to use numeric prefixes, you can turn timestamped migrations
283
+ # off by setting:
284
+ #
285
+ # config.active_record.timestamped_migrations = false
286
+ #
287
+ # In application.rb.
288
+ #
289
+ class Migration
290
+ @@verbose = true
291
+ cattr_accessor :verbose
292
+
293
+ class << self
294
+ def up_with_benchmarks #:nodoc:
295
+ migrate(:up)
296
+ end
297
+
298
+ def down_with_benchmarks #:nodoc:
299
+ migrate(:down)
300
+ end
301
+
302
+ # Execute this migration in the named direction
303
+ def migrate(direction)
304
+ return unless respond_to?(direction)
305
+
306
+ case direction
307
+ when :up then announce "migrating"
308
+ when :down then announce "reverting"
309
+ end
310
+
311
+ result = nil
312
+ time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
313
+
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
318
+
319
+ result
320
+ end
321
+
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
327
+
328
+ begin
329
+ @ignore_new_methods = true
330
+
331
+ case sym
332
+ when :up, :down
333
+ singleton_class.send(:alias_method_chain, sym, "benchmarks")
334
+ end
335
+ ensure
336
+ @ignore_new_methods = false
337
+ end
338
+ end
339
+
340
+ def write(text="")
341
+ puts(text) if verbose
342
+ end
343
+
344
+ def announce(message)
345
+ version = defined?(@version) ? @version : nil
346
+
347
+ text = "#{version} #{name}: #{message}"
348
+ length = [0, 75 - text.length].max
349
+ write "== %s %s" % [text, "=" * length]
350
+ end
351
+
352
+ def say(message, subitem=false)
353
+ write "#{subitem ? " ->" : "--"} #{message}"
354
+ end
355
+
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
363
+ end
364
+
365
+ def suppress_messages
366
+ save, self.verbose = verbose, false
367
+ yield
368
+ ensure
369
+ self.verbose = save
370
+ end
371
+
372
+ def connection
373
+ ActiveRecord::Base.connection
374
+ end
375
+
376
+ def method_missing(method, *arguments, &block)
377
+ arg_list = arguments.map{ |a| a.inspect } * ', '
378
+
379
+ say_with_time "#{method}(#{arg_list})" do
380
+ unless arguments.empty? || method == :execute
381
+ arguments[0] = Migrator.proper_table_name(arguments.first)
382
+ end
383
+ connection.send(method, *arguments, &block)
384
+ end
385
+ end
386
+ end
387
+ end
388
+
389
+ # MigrationProxy is used to defer loading of the actual migration classes
390
+ # until they are needed
391
+ class MigrationProxy
392
+
393
+ attr_accessor :name, :version, :filename
394
+
395
+ delegate :migrate, :announce, :write, :to=>:migration
396
+
397
+ private
398
+
399
+ def migration
400
+ @migration ||= load_migration
401
+ end
402
+
403
+ def load_migration
404
+ require(File.expand_path(filename))
405
+ name.constantize
406
+ end
407
+
408
+ end
409
+
410
+ class Migrator#:nodoc:
411
+ class << self
412
+ def migrate(migrations_path, target_version = nil)
413
+ 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)
421
+ end
422
+ end
423
+
424
+ def rollback(migrations_path, steps=1)
425
+ move(:down, migrations_path, steps)
426
+ end
427
+
428
+ def forward(migrations_path, steps=1)
429
+ move(:up, migrations_path, steps)
430
+ end
431
+
432
+ def up(migrations_path, target_version = nil)
433
+ self.new(:up, migrations_path, target_version).migrate
434
+ end
435
+
436
+ def down(migrations_path, target_version = nil)
437
+ self.new(:down, migrations_path, target_version).migrate
438
+ end
439
+
440
+ def run(direction, migrations_path, target_version)
441
+ self.new(direction, migrations_path, target_version).run
442
+ end
443
+
444
+ def migrations_path
445
+ 'db/migrate'
446
+ end
447
+
448
+ def schema_migrations_table_name
449
+ Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
450
+ end
451
+
452
+ 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
455
+ end
456
+
457
+ def current_version
458
+ sm_table = schema_migrations_table_name
459
+ if Base.connection.table_exists?(sm_table)
460
+ get_all_versions.max || 0
461
+ else
462
+ 0
463
+ end
464
+ end
465
+
466
+ def proper_table_name(name)
467
+ # 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}"
469
+ end
470
+
471
+ private
472
+
473
+ def move(direction, migrations_path, steps)
474
+ migrator = self.new(direction, migrations_path)
475
+ start_index = migrator.migrations.index(migrator.current_migration)
476
+
477
+ if start_index
478
+ finish = migrator.migrations[start_index + steps]
479
+ version = finish ? finish.version : 0
480
+ send(direction, migrations_path, version)
481
+ end
482
+ end
483
+ end
484
+
485
+ def initialize(direction, migrations_path, target_version = nil)
486
+ 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
489
+ end
490
+
491
+ def current_version
492
+ migrated.last || 0
493
+ end
494
+
495
+ def current_migration
496
+ migrations.detect { |m| m.version == current_version }
497
+ end
498
+
499
+ 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)
505
+ end
506
+ end
507
+
508
+ 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
513
+ raise UnknownMigrationVersionError.new(@target_version)
514
+ end
515
+
516
+ start = up? ? 0 : (migrations.index(current) || 0)
517
+ finish = migrations.index(target) || migrations.size - 1
518
+ runnable = migrations[start..finish]
519
+
520
+ # skip the last migration if we're headed down, but not ALL the way down
521
+ runnable.pop if down? && !target.nil?
522
+
523
+ runnable.each do |migration|
524
+ Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
525
+
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
+ begin
536
+ ddl_transaction do
537
+ migration.migrate(@direction)
538
+ record_version_state_after_migrating(migration.version)
539
+ end
540
+ rescue => e
541
+ canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
542
+ raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
543
+ end
544
+ end
545
+ end
546
+
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
574
+ end
575
+ end
576
+
577
+ def pending_migrations
578
+ already_migrated = migrated
579
+ migrations.reject { |m| already_migrated.include?(m.version.to_i) }
580
+ end
581
+
582
+ def migrated
583
+ @migrated_versions ||= self.class.get_all_versions
584
+ end
585
+
586
+ private
587
+ def record_version_state_after_migrating(version)
588
+ table = Arel::Table.new(self.class.schema_migrations_table_name)
589
+
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
598
+ end
599
+
600
+ def up?
601
+ @direction == :up
602
+ end
603
+
604
+ def down?
605
+ @direction == :down
606
+ end
607
+
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
615
+ end
616
+ end
617
+ end