activerecord 3.1.11 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. data/CHANGELOG.md +6294 -97
  2. data/README.rdoc +2 -2
  3. data/examples/performance.rb +55 -31
  4. data/lib/active_record/aggregations.rb +2 -2
  5. data/lib/active_record/associations/association.rb +2 -42
  6. data/lib/active_record/associations/association_scope.rb +3 -30
  7. data/lib/active_record/associations/builder/association.rb +6 -4
  8. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  9. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  10. data/lib/active_record/associations/builder/has_many.rb +4 -4
  11. data/lib/active_record/associations/builder/has_one.rb +5 -6
  12. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  13. data/lib/active_record/associations/collection_association.rb +55 -28
  14. data/lib/active_record/associations/collection_proxy.rb +1 -35
  15. data/lib/active_record/associations/has_many_association.rb +5 -1
  16. data/lib/active_record/associations/has_many_through_association.rb +11 -8
  17. data/lib/active_record/associations/join_dependency.rb +1 -1
  18. data/lib/active_record/associations/preloader/association.rb +3 -1
  19. data/lib/active_record/associations.rb +82 -69
  20. data/lib/active_record/attribute_assignment.rb +221 -0
  21. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +3 -3
  23. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  24. data/lib/active_record/attribute_methods/read.rb +72 -83
  25. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
  27. data/lib/active_record/attribute_methods/write.rb +27 -5
  28. data/lib/active_record/attribute_methods.rb +209 -30
  29. data/lib/active_record/autosave_association.rb +23 -8
  30. data/lib/active_record/base.rb +217 -1709
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
  33. data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
  34. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  35. data/lib/active_record/connection_adapters/abstract/quoting.rb +9 -12
  36. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
  37. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +43 -22
  38. data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
  39. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  40. data/lib/active_record/connection_adapters/column.rb +2 -2
  41. data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
  42. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
  43. data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
  44. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  45. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  46. data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
  47. data/lib/active_record/counter_cache.rb +4 -3
  48. data/lib/active_record/dynamic_matchers.rb +79 -0
  49. data/lib/active_record/errors.rb +11 -1
  50. data/lib/active_record/explain.rb +83 -0
  51. data/lib/active_record/explain_subscriber.rb +21 -0
  52. data/lib/active_record/fixtures/file.rb +65 -0
  53. data/lib/active_record/fixtures.rb +31 -76
  54. data/lib/active_record/identity_map.rb +4 -11
  55. data/lib/active_record/inheritance.rb +167 -0
  56. data/lib/active_record/integration.rb +49 -0
  57. data/lib/active_record/locking/optimistic.rb +30 -25
  58. data/lib/active_record/locking/pessimistic.rb +23 -1
  59. data/lib/active_record/log_subscriber.rb +3 -3
  60. data/lib/active_record/migration/command_recorder.rb +8 -8
  61. data/lib/active_record/migration.rb +47 -30
  62. data/lib/active_record/model_schema.rb +366 -0
  63. data/lib/active_record/nested_attributes.rb +3 -2
  64. data/lib/active_record/persistence.rb +51 -9
  65. data/lib/active_record/querying.rb +58 -0
  66. data/lib/active_record/railtie.rb +24 -28
  67. data/lib/active_record/railties/controller_runtime.rb +3 -1
  68. data/lib/active_record/railties/databases.rake +134 -77
  69. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  70. data/lib/active_record/readonly_attributes.rb +26 -0
  71. data/lib/active_record/reflection.rb +7 -15
  72. data/lib/active_record/relation/batches.rb +5 -2
  73. data/lib/active_record/relation/calculations.rb +27 -6
  74. data/lib/active_record/relation/delegation.rb +49 -0
  75. data/lib/active_record/relation/finder_methods.rb +6 -5
  76. data/lib/active_record/relation/predicate_builder.rb +12 -19
  77. data/lib/active_record/relation/query_methods.rb +76 -10
  78. data/lib/active_record/relation/spawn_methods.rb +11 -2
  79. data/lib/active_record/relation.rb +77 -34
  80. data/lib/active_record/result.rb +1 -1
  81. data/lib/active_record/sanitization.rb +194 -0
  82. data/lib/active_record/schema_dumper.rb +5 -2
  83. data/lib/active_record/scoping/default.rb +142 -0
  84. data/lib/active_record/scoping/named.rb +202 -0
  85. data/lib/active_record/scoping.rb +152 -0
  86. data/lib/active_record/serialization.rb +1 -43
  87. data/lib/active_record/serializers/xml_serializer.rb +2 -44
  88. data/lib/active_record/session_store.rb +15 -15
  89. data/lib/active_record/store.rb +50 -0
  90. data/lib/active_record/test_case.rb +11 -7
  91. data/lib/active_record/timestamp.rb +16 -3
  92. data/lib/active_record/transactions.rb +5 -5
  93. data/lib/active_record/translation.rb +22 -0
  94. data/lib/active_record/validations/associated.rb +5 -4
  95. data/lib/active_record/validations/uniqueness.rb +4 -4
  96. data/lib/active_record/validations.rb +1 -1
  97. data/lib/active_record/version.rb +2 -2
  98. data/lib/active_record.rb +28 -2
  99. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  100. data/lib/rails/generators/active_record/migration/templates/migration.rb +9 -3
  101. data/lib/rails/generators/active_record/model/model_generator.rb +5 -1
  102. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  103. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  104. metadata +50 -40
  105. checksums.yaml +0 -7
  106. data/lib/active_record/named_scope.rb +0 -200
@@ -0,0 +1,49 @@
1
+ module ActiveRecord
2
+ module Integration
3
+ # Returns a String, which Action Pack uses for constructing an URL to this
4
+ # object. The default implementation returns this record's id as a String,
5
+ # or nil if this record's unsaved.
6
+ #
7
+ # For example, suppose that you have a User model, and that you have a
8
+ # <tt>resources :users</tt> route. Normally, +user_path+ will
9
+ # construct a path with the user object's 'id' in it:
10
+ #
11
+ # user = User.find_by_name('Phusion')
12
+ # user_path(user) # => "/users/1"
13
+ #
14
+ # You can override +to_param+ in your model to make +user_path+ construct
15
+ # a path using the user's name instead of the user's id:
16
+ #
17
+ # class User < ActiveRecord::Base
18
+ # def to_param # overridden
19
+ # name
20
+ # end
21
+ # end
22
+ #
23
+ # user = User.find_by_name('Phusion')
24
+ # user_path(user) # => "/users/Phusion"
25
+ def to_param
26
+ # We can't use alias_method here, because method 'id' optimizes itself on the fly.
27
+ id && id.to_s # Be sure to stringify the id for routes
28
+ end
29
+
30
+ # Returns a cache key that can be used to identify this record.
31
+ #
32
+ # ==== Examples
33
+ #
34
+ # Product.new.cache_key # => "products/new"
35
+ # Product.find(5).cache_key # => "products/5" (updated_at not available)
36
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
37
+ def cache_key
38
+ case
39
+ when new_record?
40
+ "#{self.class.model_name.cache_key}/new"
41
+ when timestamp = self[:updated_at]
42
+ timestamp = timestamp.utc.to_s(:number)
43
+ "#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
44
+ else
45
+ "#{self.class.model_name.cache_key}/#{id}"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -37,6 +37,9 @@ module ActiveRecord
37
37
  # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
38
38
  # or otherwise apply the business logic needed to resolve the conflict.
39
39
  #
40
+ # This locking mechanism will function inside a single Ruby process. To make it work across all
41
+ # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
42
+ #
40
43
  # You must ensure that your database schema defaults the +lock_version+ column to 0.
41
44
  #
42
45
  # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
@@ -48,10 +51,6 @@ module ActiveRecord
48
51
  included do
49
52
  cattr_accessor :lock_optimistically, :instance_writer => false
50
53
  self.lock_optimistically = true
51
-
52
- class << self
53
- alias_method :locking_column=, :set_locking_column
54
- end
55
54
  end
56
55
 
57
56
  def locking_enabled? #:nodoc:
@@ -65,21 +64,6 @@ module ActiveRecord
65
64
  send(lock_col + '=', previous_lock_value + 1)
66
65
  end
67
66
 
68
- def attributes_from_column_definition
69
- result = super
70
-
71
- # If the locking column has no default value set,
72
- # start the lock version at zero. Note we can't use
73
- # <tt>locking_enabled?</tt> at this point as
74
- # <tt>@attributes</tt> may not have been initialized yet.
75
-
76
- if result.key?(self.class.locking_column) && lock_optimistically
77
- result[self.class.locking_column] ||= 0
78
- end
79
-
80
- result
81
- end
82
-
83
67
  def update(attribute_names = @attributes.keys) #:nodoc:
84
68
  return super unless locking_enabled?
85
69
  return 0 if attribute_names.empty?
@@ -103,7 +87,7 @@ module ActiveRecord
103
87
  affected_rows = connection.update stmt
104
88
 
105
89
  unless affected_rows == 1
106
- raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
90
+ raise ActiveRecord::StaleObjectError.new(self, "update")
107
91
  end
108
92
 
109
93
  affected_rows
@@ -127,7 +111,7 @@ module ActiveRecord
127
111
  affected_rows = self.class.unscoped.where(predicate).delete_all
128
112
 
129
113
  unless affected_rows == 1
130
- raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}"
114
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
131
115
  end
132
116
  end
133
117
 
@@ -145,15 +129,24 @@ module ActiveRecord
145
129
  lock_optimistically && columns_hash[locking_column]
146
130
  end
147
131
 
132
+ def locking_column=(value)
133
+ @original_locking_column = @locking_column if defined?(@locking_column)
134
+ @locking_column = value.to_s
135
+ end
136
+
148
137
  # Set the column to use for optimistic locking. Defaults to +lock_version+.
149
138
  def set_locking_column(value = nil, &block)
150
- define_attr_method :locking_column, value, &block
151
- value
139
+ deprecated_property_setter :locking_column, value, block
152
140
  end
153
141
 
154
142
  # The version column used for optimistic locking. Defaults to +lock_version+.
155
143
  def locking_column
156
- reset_locking_column
144
+ reset_locking_column unless defined?(@locking_column)
145
+ @locking_column
146
+ end
147
+
148
+ def original_locking_column #:nodoc:
149
+ deprecated_original_property_getter :locking_column
157
150
  end
158
151
 
159
152
  # Quote the column name used for optimistic locking.
@@ -163,7 +156,7 @@ module ActiveRecord
163
156
 
164
157
  # Reset the column used for optimistic locking back to the +lock_version+ default.
165
158
  def reset_locking_column
166
- set_locking_column DEFAULT_LOCKING_COLUMN
159
+ self.locking_column = DEFAULT_LOCKING_COLUMN
167
160
  end
168
161
 
169
162
  # Make sure the lock version column gets updated when counters are
@@ -172,6 +165,18 @@ module ActiveRecord
172
165
  counters = counters.merge(locking_column => 1) if locking_enabled?
173
166
  super
174
167
  end
168
+
169
+ # If the locking column has no default value set,
170
+ # start the lock version at zero. Note we can't use
171
+ # <tt>locking_enabled?</tt> at this point as
172
+ # <tt>@attributes</tt> may not have been initialized yet.
173
+ def initialize_attributes(attributes) #:nodoc:
174
+ if attributes.key?(locking_column) && lock_optimistically
175
+ attributes[locking_column] ||= 0
176
+ end
177
+
178
+ attributes
179
+ end
175
180
  end
176
181
  end
177
182
  end
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  # Account.transaction do
15
15
  # # select * from accounts where name = 'shugo' limit 1 for update
16
16
  # shugo = Account.where("name = 'shugo'").lock(true).first
17
- # yuko = Account.where("name = 'shugo'").lock(true).first
17
+ # yuko = Account.where("name = 'yuko'").lock(true).first
18
18
  # shugo.balance -= 100
19
19
  # shugo.save!
20
20
  # yuko.balance += 100
@@ -38,6 +38,18 @@ module ActiveRecord
38
38
  # account2.save!
39
39
  # end
40
40
  #
41
+ # You can start a transaction and acquire the lock in one go by calling
42
+ # <tt>with_lock</tt> with a block. The block is called from within
43
+ # a transaction, the object is already locked. Example:
44
+ #
45
+ # account = Account.first
46
+ # account.with_lock do
47
+ # # This block is called within a transaction,
48
+ # # account is already locked.
49
+ # account.balance -= 100
50
+ # account.save!
51
+ # end
52
+ #
41
53
  # Database-specific information on row locking:
42
54
  # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
43
55
  # PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
@@ -50,6 +62,16 @@ module ActiveRecord
50
62
  reload(:lock => lock) if persisted?
51
63
  self
52
64
  end
65
+
66
+ # Wraps the passed block in a transaction, locking the object
67
+ # before yielding. You pass can the SQL locking clause
68
+ # as argument (see <tt>lock!</tt>).
69
+ def with_lock(lock = true)
70
+ transaction do
71
+ lock!(lock)
72
+ yield
73
+ end
74
+ end
53
75
  end
54
76
  end
55
77
  end
@@ -26,9 +26,9 @@ module ActiveRecord
26
26
 
27
27
  return if 'SCHEMA' == payload[:name]
28
28
 
29
- name = '%s (%.1fms)' % [payload[:name], event.duration]
30
- sql = payload[:sql].squeeze(' ')
31
- binds = nil
29
+ name = '%s (%.1fms)' % [payload[:name], event.duration]
30
+ sql = payload[:sql].squeeze(' ')
31
+ binds = nil
32
32
 
33
33
  unless (payload[:binds] || []).empty?
34
34
  binds = " " + payload[:binds].map { |col,v|
@@ -1,12 +1,12 @@
1
1
  module ActiveRecord
2
2
  class Migration
3
- # ActiveRecord::Migration::CommandRecorder records commands done during
4
- # a migration and knows how to reverse those commands. The CommandRecorder
3
+ # <tt>ActiveRecord::Migration::CommandRecorder</tt> records commands done during
4
+ # a migration and knows how to reverse those commands. The CommandRecorder
5
5
  # knows how to invert the following commands:
6
6
  #
7
7
  # * add_column
8
8
  # * add_index
9
- # * add_timestamp
9
+ # * add_timestamps
10
10
  # * create_table
11
11
  # * remove_timestamps
12
12
  # * rename_column
@@ -20,21 +20,21 @@ module ActiveRecord
20
20
  @delegate = delegate
21
21
  end
22
22
 
23
- # record +command+. +command+ should be a method name and arguments.
23
+ # record +command+. +command+ should be a method name and arguments.
24
24
  # For example:
25
25
  #
26
- # recorder.record(:method_name, [:arg1, arg2])
26
+ # recorder.record(:method_name, [:arg1, :arg2])
27
27
  def record(*command)
28
28
  @commands << command
29
29
  end
30
30
 
31
31
  # Returns a list that represents commands that are the inverse of the
32
- # commands stored in +commands+. For example:
32
+ # commands stored in +commands+. For example:
33
33
  #
34
34
  # recorder.record(:rename_table, [:old, :new])
35
35
  # recorder.inverse # => [:rename_table, [:new, :old]]
36
36
  #
37
- # This method will raise an IrreversibleMigration exception if it cannot
37
+ # This method will raise an +IrreversibleMigration+ exception if it cannot
38
38
  # invert the +commands+.
39
39
  def inverse
40
40
  @commands.reverse.map { |name, args|
@@ -59,7 +59,7 @@ module ActiveRecord
59
59
  private
60
60
 
61
61
  def invert_create_table(args)
62
- [:drop_table, args]
62
+ [:drop_table, [args.first]]
63
63
  end
64
64
 
65
65
  def invert_rename_table(args)
@@ -1,6 +1,7 @@
1
1
  require "active_support/core_ext/module/delegation"
2
2
  require "active_support/core_ext/class/attribute_accessors"
3
3
  require "active_support/core_ext/array/wrap"
4
+ require 'active_support/deprecation'
4
5
 
5
6
  module ActiveRecord
6
7
  # Exception that can be raised to stop migrations from going backwards.
@@ -68,9 +69,9 @@ module ActiveRecord
68
69
  # create_table :system_settings do |t|
69
70
  # t.string :name
70
71
  # t.string :label
71
- # t.text :value
72
+ # t.text :value
72
73
  # t.string :type
73
- # t.integer :position
74
+ # t.integer :position
74
75
  # end
75
76
  #
76
77
  # SystemSetting.create :name => "notice",
@@ -116,8 +117,9 @@ module ActiveRecord
116
117
  # +column_names+ from the table called +table_name+.
117
118
  # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
118
119
  # 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>).
120
+ # <tt>:name</tt>, <tt>:unique</tt> (e.g.
121
+ # <tt>{ :name => "users_name_index", :unique => true }</tt>) and <tt>:order</tt>
122
+ # (e.g. { :order => {:name => :desc} }</tt>).
121
123
  # * <tt>remove_index(table_name, :column => column_name)</tt>: Removes the index
122
124
  # specified by +column_name+.
123
125
  # * <tt>remove_index(table_name, :name => index_name)</tt>: Removes the index
@@ -183,7 +185,7 @@ module ActiveRecord
183
185
  #
184
186
  # class RemoveEmptyTags < ActiveRecord::Migration
185
187
  # def up
186
- # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
188
+ # Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
187
189
  # end
188
190
  #
189
191
  # def down
@@ -229,7 +231,7 @@ module ActiveRecord
229
231
  # def up
230
232
  # add_column :people, :salary, :integer
231
233
  # Person.reset_column_information
232
- # Person.find(:all).each do |p|
234
+ # Person.all.each do |p|
233
235
  # p.update_attribute :salary, SalaryCalculator.compute(p)
234
236
  # end
235
237
  # end
@@ -249,7 +251,7 @@ module ActiveRecord
249
251
  # def up
250
252
  # ...
251
253
  # say_with_time "Updating salaries..." do
252
- # Person.find(:all).each do |p|
254
+ # Person.all.each do |p|
253
255
  # p.update_attribute :salary, SalaryCalculator.compute(p)
254
256
  # end
255
257
  # end
@@ -301,7 +303,7 @@ module ActiveRecord
301
303
  #
302
304
  # class TenderloveMigration < ActiveRecord::Migration
303
305
  # def change
304
- # create_table(:horses) do
306
+ # create_table(:horses) do |t|
305
307
  # t.column :content, :text
306
308
  # t.column :remind_at, :datetime
307
309
  # end
@@ -442,6 +444,7 @@ module ActiveRecord
442
444
  say_with_time "#{method}(#{arg_list})" do
443
445
  unless arguments.empty? || method == :execute
444
446
  arguments[0] = Migrator.proper_table_name(arguments.first)
447
+ arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
445
448
  end
446
449
  return super unless connection.respond_to?(method)
447
450
  connection.send(method, *arguments, &block)
@@ -455,26 +458,28 @@ module ActiveRecord
455
458
 
456
459
  destination_migrations = ActiveRecord::Migrator.migrations(destination)
457
460
  last = destination_migrations.last
458
- sources.each do |name, path|
461
+ sources.each do |scope, path|
459
462
  source_migrations = ActiveRecord::Migrator.migrations(path)
460
463
 
461
464
  source_migrations.each do |migration|
462
465
  source = File.read(migration.filename)
463
- source = "# This migration comes from #{name} (originally #{migration.version})\n#{source}"
466
+ source = "# This migration comes from #{scope} (originally #{migration.version})\n#{source}"
464
467
 
465
468
  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]
469
+ if options[:on_skip] && duplicate.scope != scope.to_s
470
+ options[:on_skip].call(scope, migration)
471
+ end
467
472
  next
468
473
  end
469
474
 
470
475
  migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
471
- new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.rb")
476
+ new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
472
477
  old_path, migration.filename = migration.filename, new_path
473
478
  last = migration
474
479
 
475
- FileUtils.cp(old_path, migration.filename)
480
+ File.open(migration.filename, "w") { |f| f.write source }
476
481
  copied << migration
477
- options[:on_copy].call(name, migration, old_path) if options[:on_copy]
482
+ options[:on_copy].call(scope, migration, old_path) if options[:on_copy]
478
483
  destination_migrations << migration
479
484
  end
480
485
  end
@@ -493,9 +498,9 @@ module ActiveRecord
493
498
 
494
499
  # MigrationProxy is used to defer loading of the actual migration classes
495
500
  # until they are needed
496
- class MigrationProxy < Struct.new(:name, :version, :filename)
501
+ class MigrationProxy < Struct.new(:name, :version, :filename, :scope)
497
502
 
498
- def initialize(name, version, filename)
503
+ def initialize(name, version, filename, scope)
499
504
  super
500
505
  @migration = nil
501
506
  end
@@ -504,7 +509,7 @@ module ActiveRecord
504
509
  File.basename(filename)
505
510
  end
506
511
 
507
- delegate :migrate, :announce, :write, :to=>:migration
512
+ delegate :migrate, :announce, :write, :to => :migration
508
513
 
509
514
  private
510
515
 
@@ -524,16 +529,16 @@ module ActiveRecord
524
529
  attr_writer :migrations_paths
525
530
  alias :migrations_path= :migrations_paths=
526
531
 
527
- def migrate(migrations_paths, target_version = nil)
532
+ def migrate(migrations_paths, target_version = nil, &block)
528
533
  case
529
534
  when target_version.nil?
530
- up(migrations_paths, target_version)
535
+ up(migrations_paths, target_version, &block)
531
536
  when current_version == 0 && target_version == 0
532
537
  []
533
538
  when current_version > target_version
534
- down(migrations_paths, target_version)
539
+ down(migrations_paths, target_version, &block)
535
540
  else
536
- up(migrations_paths, target_version)
541
+ up(migrations_paths, target_version, &block)
537
542
  end
538
543
  end
539
544
 
@@ -545,12 +550,12 @@ module ActiveRecord
545
550
  move(:up, migrations_paths, steps)
546
551
  end
547
552
 
548
- def up(migrations_paths, target_version = nil)
549
- self.new(:up, migrations_paths, target_version).migrate
553
+ def up(migrations_paths, target_version = nil, &block)
554
+ self.new(:up, migrations_paths, target_version).migrate(&block)
550
555
  end
551
556
 
552
- def down(migrations_paths, target_version = nil)
553
- self.new(:down, migrations_paths, target_version).migrate
557
+ def down(migrations_paths, target_version = nil, &block)
558
+ self.new(:down, migrations_paths, target_version).migrate(&block)
554
559
  end
555
560
 
556
561
  def run(direction, migrations_paths, target_version)
@@ -590,15 +595,23 @@ module ActiveRecord
590
595
  migrations_paths.first
591
596
  end
592
597
 
593
- def migrations(paths)
598
+ def migrations(paths, *args)
599
+ if args.empty?
600
+ subdirectories = true
601
+ else
602
+ subdirectories = args.first
603
+ ActiveSupport::Deprecation.warn "The `subdirectories` argument to `migrations` is deprecated"
604
+ end
605
+
594
606
  paths = Array.wrap(paths)
595
607
 
596
- files = Dir[*paths.map { |p| "#{p}/[0-9]*_*.rb" }]
608
+ glob = subdirectories ? "**/" : ""
609
+ files = Dir[*paths.map { |p| "#{p}/#{glob}[0-9]*_*.rb" }]
597
610
 
598
611
  seen = Hash.new false
599
612
 
600
613
  migrations = files.map do |file|
601
- version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
614
+ version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
602
615
 
603
616
  raise IllegalMigrationNameError.new(file) unless version
604
617
  version = version.to_i
@@ -609,7 +622,7 @@ module ActiveRecord
609
622
 
610
623
  seen[version] = seen[name] = true
611
624
 
612
- MigrationProxy.new(name, version, file)
625
+ MigrationProxy.new(name, version, file, scope)
613
626
  end
614
627
 
615
628
  migrations.sort_by(&:version)
@@ -652,7 +665,7 @@ module ActiveRecord
652
665
  end
653
666
  end
654
667
 
655
- def migrate
668
+ def migrate(&block)
656
669
  current = migrations.detect { |m| m.version == current_version }
657
670
  target = migrations.detect { |m| m.version == @target_version }
658
671
 
@@ -669,6 +682,10 @@ module ActiveRecord
669
682
 
670
683
  ran = []
671
684
  runnable.each do |migration|
685
+ if block && !block.call(migration)
686
+ next
687
+ end
688
+
672
689
  Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
673
690
 
674
691
  seen = migrated.include?(migration.version.to_i)