activerecord 1.15.6 → 2.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 (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -1,15 +1,25 @@
1
1
  module ActiveRecord
2
2
  module Locking
3
+ # == What is Optimistic Locking
4
+ #
5
+ # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
6
+ # conflicts with the data. It does this by checking whether another process has made changes to a record since
7
+ # it was opened, an ActiveRecord::StaleObjectError is thrown if that has occurred and the update is ignored.
8
+ #
9
+ # Check out ActiveRecord::Locking::Pessimistic for an alternative.
10
+ #
11
+ # == Usage
12
+ #
3
13
  # Active Records support optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
4
14
  # record increments the lock_version column and the locking facilities ensure that records instantiated twice
5
15
  # will let the last one saved raise a StaleObjectError if the first was also updated. Example:
6
16
  #
7
17
  # p1 = Person.find(1)
8
18
  # p2 = Person.find(1)
9
- #
19
+ #
10
20
  # p1.first_name = "Michael"
11
21
  # p1.save
12
- #
22
+ #
13
23
  # p2.first_name = "should fail"
14
24
  # p2.save # Raises a ActiveRecord::StaleObjectError
15
25
  #
@@ -23,7 +33,6 @@ module ActiveRecord
23
33
  # This method uses the same syntax as <tt>set_table_name</tt>
24
34
  module Optimistic
25
35
  def self.included(base) #:nodoc:
26
- super
27
36
  base.extend ClassMethods
28
37
 
29
38
  base.cattr_accessor :lock_optimistically, :instance_writer => false
@@ -31,55 +40,77 @@ module ActiveRecord
31
40
 
32
41
  base.alias_method_chain :update, :lock
33
42
  base.alias_method_chain :attributes_from_column_definition, :lock
34
-
43
+
35
44
  class << base
36
45
  alias_method :locking_column=, :set_locking_column
37
46
  end
38
47
  end
39
48
 
40
49
  def locking_enabled? #:nodoc:
41
- lock_optimistically && respond_to?(self.class.locking_column)
50
+ self.class.locking_enabled?
42
51
  end
43
52
 
44
- def attributes_from_column_definition_with_lock
45
- result = attributes_from_column_definition_without_lock
46
-
47
- # If the locking column has no default value set,
48
- # start the lock version at zero. Note we can't use
49
- # locking_enabled? at this point as @attributes may
50
- # not have been initialized yet
51
-
52
- if lock_optimistically && result.include?(self.class.locking_column)
53
- result[self.class.locking_column] ||= 0
53
+ private
54
+ def attributes_from_column_definition_with_lock
55
+ result = attributes_from_column_definition_without_lock
56
+
57
+ # If the locking column has no default value set,
58
+ # start the lock version at zero. Note we can't use
59
+ # locking_enabled? at this point as @attributes may
60
+ # not have been initialized yet
61
+
62
+ if lock_optimistically && result.include?(self.class.locking_column)
63
+ result[self.class.locking_column] ||= 0
64
+ end
65
+
66
+ return result
54
67
  end
55
-
56
- return result
57
- end
58
68
 
59
- def update_with_lock #:nodoc:
60
- return update_without_lock unless locking_enabled?
69
+ def update_with_lock #:nodoc:
70
+ return update_without_lock unless locking_enabled?
61
71
 
62
- lock_col = self.class.locking_column
63
- previous_value = send(lock_col)
64
- send(lock_col + '=', previous_value + 1)
72
+ lock_col = self.class.locking_column
73
+ previous_value = send(lock_col)
74
+ send(lock_col + '=', previous_value + 1)
65
75
 
66
- affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
67
- UPDATE #{self.class.table_name}
68
- SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))}
69
- WHERE #{self.class.primary_key} = #{quote_value(id)}
70
- AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}
71
- end_sql
76
+ begin
77
+ affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
78
+ UPDATE #{self.class.table_name}
79
+ SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false))}
80
+ WHERE #{self.class.primary_key} = #{quote_value(id)}
81
+ AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}
82
+ end_sql
72
83
 
73
- unless affected_rows == 1
74
- raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
75
- end
84
+ unless affected_rows == 1
85
+ raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
86
+ end
76
87
 
77
- return true
78
- end
88
+ affected_rows
89
+
90
+ # If something went wrong, revert the version.
91
+ rescue Exception
92
+ send(lock_col + '=', previous_value)
93
+ raise
94
+ end
95
+ end
79
96
 
80
97
  module ClassMethods
81
98
  DEFAULT_LOCKING_COLUMN = 'lock_version'
82
99
 
100
+ def self.extended(base)
101
+ class <<base
102
+ alias_method_chain :update_counters, :lock
103
+ end
104
+ end
105
+
106
+ # Is optimistic locking enabled for this table? Returns true if the
107
+ # #lock_optimistically flag is set to true (which it is, by default)
108
+ # and the table includes the #locking_column column (defaults to
109
+ # lock_version).
110
+ def locking_enabled?
111
+ lock_optimistically && columns_hash[locking_column]
112
+ end
113
+
83
114
  # Set the column to use for optimistic locking. Defaults to lock_version.
84
115
  def set_locking_column(value = nil, &block)
85
116
  define_attr_method :locking_column, value, &block
@@ -100,6 +131,13 @@ module ActiveRecord
100
131
  def reset_locking_column
101
132
  set_locking_column DEFAULT_LOCKING_COLUMN
102
133
  end
134
+
135
+ # make sure the lock version column gets updated when counters are
136
+ # updated.
137
+ def update_counters_with_lock(id, counters)
138
+ counters = counters.merge(locking_column => 1) if locking_enabled?
139
+ update_counters_without_lock(id, counters)
140
+ end
103
141
  end
104
142
  end
105
143
  end
@@ -1,13 +1,19 @@
1
1
  module ActiveRecord
2
2
  class IrreversibleMigration < ActiveRecordError#:nodoc:
3
3
  end
4
-
4
+
5
5
  class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
6
6
  def initialize(version)
7
7
  super("Multiple migrations have the version number #{version}")
8
8
  end
9
9
  end
10
-
10
+
11
+ class IllegalMigrationNameError < ActiveRecordError#:nodoc:
12
+ def initialize(name)
13
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
14
+ end
15
+ end
16
+
11
17
  # Migrations can manage the evolution of a schema used by several physical databases. It's a solution
12
18
  # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
13
19
  # push that change to other developers and to the production server. With migrations, you can describe the transformations
@@ -26,9 +32,9 @@ module ActiveRecord
26
32
  # end
27
33
  # end
28
34
  #
29
- # This migration will add a boolean flag to the accounts table and remove it again, if you're backing out of the migration.
35
+ # This migration will add a boolean flag to the accounts table and remove it if you're backing out of the migration.
30
36
  # It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement
31
- # or remove the migration. These methods can consist of both the migration specific methods, like add_column and remove_column,
37
+ # or remove the migration. These methods can consist of both the migration specific methods like add_column and remove_column,
32
38
  # but may also contain regular Ruby code for generating data needed for the transformations.
33
39
  #
34
40
  # Example of a more complex migration that also needs to initialize data:
@@ -36,11 +42,11 @@ module ActiveRecord
36
42
  # class AddSystemSettings < ActiveRecord::Migration
37
43
  # def self.up
38
44
  # create_table :system_settings do |t|
39
- # t.column :name, :string
40
- # t.column :label, :string
41
- # t.column :value, :text
42
- # t.column :type, :string
43
- # t.column :position, :integer
45
+ # t.string :name
46
+ # t.string :label
47
+ # t.text :value
48
+ # t.string :type
49
+ # t.integer :position
44
50
  # end
45
51
  #
46
52
  # SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
@@ -72,13 +78,14 @@ module ActiveRecord
72
78
  # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
73
79
  # parameters as add_column.
74
80
  # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
75
- # * <tt>add_index(table_name, column_names, index_type, index_name)</tt>: Add a new index with the name of the column, or +index_name+ (if specified) on the column(s). Specify an optional +index_type+ (e.g. UNIQUE).
76
- # * <tt>remove_index(table_name, index_name)</tt>: Remove the index specified by +index_name+.
81
+ # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index with the name of the column. Other options include
82
+ # :name and :unique (e.g. { :name => "users_name_index", :unique => true }).
83
+ # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified by +index_name+.
77
84
  #
78
85
  # == Irreversible transformations
79
86
  #
80
87
  # Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
81
- # an <tt>IrreversibleMigration</tt> exception in their +down+ method.
88
+ # an <tt>ActiveRecord::IrreversibleMigration</tt> exception in their +down+ method.
82
89
  #
83
90
  # == Running migrations from within Rails
84
91
  #
@@ -87,18 +94,18 @@ module ActiveRecord
87
94
  # To generate a new migration, use <tt>script/generate migration MyNewMigration</tt>
88
95
  # where MyNewMigration is the name of your migration. The generator will
89
96
  # create a file <tt>nnn_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
90
- # directory, where <tt>nnn</tt> is the next largest migration number.
97
+ # directory where <tt>nnn</tt> is the next largest migration number.
91
98
  # You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
92
- # n MyNewMigration.
99
+ # MyNewMigration.
93
100
  #
94
101
  # To run migrations against the currently configured database, use
95
- # <tt>rake migrate</tt>. This will update the database by running all of the
102
+ # <tt>rake db:migrate</tt>. This will update the database by running all of the
96
103
  # pending migrations, creating the <tt>schema_info</tt> table if missing.
97
104
  #
98
105
  # To roll the database back to a previous migration version, use
99
- # <tt>rake migrate VERSION=X</tt> where <tt>X</tt> is the version to which
106
+ # <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
100
107
  # you wish to downgrade. If any of the migrations throw an
101
- # <tt>IrreversibleMigration</tt> exception, that step will fail and you'll
108
+ # <tt>ActiveRecord::IrreversibleMigration</tt> exception, that step will fail and you'll
102
109
  # have some manual work to do.
103
110
  #
104
111
  # == Database support
@@ -117,7 +124,7 @@ module ActiveRecord
117
124
  #
118
125
  # def self.down
119
126
  # # not much we can do to restore deleted data
120
- # raise IrreversibleMigration
127
+ # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
121
128
  # end
122
129
  # end
123
130
  #
@@ -191,11 +198,11 @@ module ActiveRecord
191
198
  cattr_accessor :verbose
192
199
 
193
200
  class << self
194
- def up_using_benchmarks #:nodoc:
201
+ def up_with_benchmarks #:nodoc:
195
202
  migrate(:up)
196
203
  end
197
204
 
198
- def down_using_benchmarks #:nodoc:
205
+ def down_with_benchmarks #:nodoc:
199
206
  migrate(:down)
200
207
  end
201
208
 
@@ -207,15 +214,15 @@ module ActiveRecord
207
214
  when :up then announce "migrating"
208
215
  when :down then announce "reverting"
209
216
  end
210
-
217
+
211
218
  result = nil
212
- time = Benchmark.measure { result = send("real_#{direction}") }
219
+ time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
213
220
 
214
221
  case direction
215
222
  when :up then announce "migrated (%.4fs)" % time.real; write
216
223
  when :down then announce "reverted (%.4fs)" % time.real; write
217
224
  end
218
-
225
+
219
226
  result
220
227
  end
221
228
 
@@ -224,15 +231,14 @@ module ActiveRecord
224
231
  # it is safe for the call to proceed.
225
232
  def singleton_method_added(sym) #:nodoc:
226
233
  return if @ignore_new_methods
227
-
234
+
228
235
  begin
229
236
  @ignore_new_methods = true
230
237
 
231
238
  case sym
232
239
  when :up, :down
233
240
  klass = (class << self; self; end)
234
- klass.send(:alias_method, "real_#{sym}", sym)
235
- klass.send(:alias_method, sym, "#{sym}_using_benchmarks")
241
+ klass.send(:alias_method_chain, sym, "benchmarks")
236
242
  end
237
243
  ensure
238
244
  @ignore_new_methods = false
@@ -244,7 +250,7 @@ module ActiveRecord
244
250
  end
245
251
 
246
252
  def announce(message)
247
- text = "#{name}: #{message}"
253
+ text = "#{@version} #{name}: #{message}"
248
254
  length = [0, 75 - text.length].max
249
255
  write "== %s %s" % [text, "=" * length]
250
256
  end
@@ -258,20 +264,24 @@ module ActiveRecord
258
264
  result = nil
259
265
  time = Benchmark.measure { result = yield }
260
266
  say "%.4fs" % time.real, :subitem
267
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
261
268
  result
262
269
  end
263
270
 
264
271
  def suppress_messages
265
- save = verbose
266
- self.verbose = false
272
+ save, self.verbose = verbose, false
267
273
  yield
268
274
  ensure
269
275
  self.verbose = save
270
276
  end
271
277
 
272
278
  def method_missing(method, *arguments, &block)
273
- say_with_time "#{method}(#{arguments.map { |a| a.inspect }.join(", ")})" do
274
- arguments[0] = Migrator.proper_table_name(arguments.first) unless arguments.empty? || method == :execute
279
+ arg_list = arguments.map(&:inspect) * ', '
280
+
281
+ say_with_time "#{method}(#{arg_list})" do
282
+ unless arguments.empty? || method == :execute
283
+ arguments[0] = Migrator.proper_table_name(arguments.first)
284
+ end
275
285
  ActiveRecord::Base.connection.send(method, *arguments, &block)
276
286
  end
277
287
  end
@@ -292,30 +302,29 @@ module ActiveRecord
292
302
  return # You're on the right version
293
303
  end
294
304
  end
295
-
305
+
296
306
  def up(migrations_path, target_version = nil)
297
307
  self.new(:up, migrations_path, target_version).migrate
298
308
  end
299
-
309
+
300
310
  def down(migrations_path, target_version = nil)
301
311
  self.new(:down, migrations_path, target_version).migrate
302
312
  end
303
-
313
+
304
314
  def schema_info_table_name
305
315
  Base.table_name_prefix + "schema_info" + Base.table_name_suffix
306
316
  end
307
317
 
308
318
  def current_version
309
- (Base.connection.select_one("SELECT version FROM #{schema_info_table_name}") || {"version" => 0})["version"].to_i
319
+ Base.connection.select_value("SELECT version FROM #{schema_info_table_name}").to_i
310
320
  end
311
321
 
312
322
  def proper_table_name(name)
313
323
  # Use the ActiveRecord objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
314
324
  name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
315
325
  end
316
-
317
326
  end
318
-
327
+
319
328
  def initialize(direction, migrations_path, target_version = nil)
320
329
  raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
321
330
  @direction, @migrations_path, @target_version = direction, migrations_path, target_version
@@ -327,66 +336,80 @@ module ActiveRecord
327
336
  end
328
337
 
329
338
  def migrate
330
- migration_classes.each do |(version, migration_class)|
331
- Base.logger.info("Reached target version: #{@target_version}") and break if reached_target_version?(version)
332
- next if irrelevant_migration?(version)
339
+ migration_classes.each do |migration_class|
340
+ if reached_target_version?(migration_class.version)
341
+ Base.logger.info("Reached target version: #{@target_version}")
342
+ break
343
+ end
344
+
345
+ next if irrelevant_migration?(migration_class.version)
333
346
 
334
- Base.logger.info "Migrating to #{migration_class} (#{version})"
347
+ Base.logger.info "Migrating to #{migration_class} (#{migration_class.version})"
335
348
  migration_class.migrate(@direction)
336
- set_schema_version(version)
349
+ set_schema_version(migration_class.version)
337
350
  end
338
351
  end
339
352
 
353
+ def pending_migrations
354
+ migration_classes.select { |m| m.version > current_version }
355
+ end
356
+
340
357
  private
341
358
  def migration_classes
342
359
  migrations = migration_files.inject([]) do |migrations, migration_file|
343
360
  load(migration_file)
344
361
  version, name = migration_version_and_name(migration_file)
345
362
  assert_unique_migration_version(migrations, version.to_i)
346
- migrations << [ version.to_i, migration_class(name) ]
363
+ migrations << migration_class(name, version.to_i)
347
364
  end
348
365
 
349
- down? ? migrations.sort.reverse : migrations.sort
366
+ sorted = migrations.sort_by { |m| m.version }
367
+ down? ? sorted.reverse : sorted
350
368
  end
351
-
369
+
352
370
  def assert_unique_migration_version(migrations, version)
353
- if !migrations.empty? && migrations.transpose.first.include?(version)
371
+ if !migrations.empty? && migrations.find { |m| m.version == version }
354
372
  raise DuplicateMigrationVersionError.new(version)
355
373
  end
356
374
  end
357
-
375
+
358
376
  def migration_files
359
377
  files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort_by do |f|
360
- migration_version_and_name(f).first.to_i
378
+ m = migration_version_and_name(f)
379
+ raise IllegalMigrationNameError.new(f) unless m
380
+ m.first.to_i
361
381
  end
362
382
  down? ? files.reverse : files
363
383
  end
364
-
365
- def migration_class(migration_name)
366
- migration_name.camelize.constantize
384
+
385
+ def migration_class(migration_name, version)
386
+ klass = migration_name.camelize.constantize
387
+ class << klass; attr_accessor :version end
388
+ klass.version = version
389
+ klass
367
390
  end
368
-
391
+
369
392
  def migration_version_and_name(migration_file)
370
393
  return *migration_file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
371
394
  end
372
-
395
+
373
396
  def set_schema_version(version)
374
397
  Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}")
375
398
  end
376
-
399
+
377
400
  def up?
378
401
  @direction == :up
379
402
  end
380
-
403
+
381
404
  def down?
382
405
  @direction == :down
383
406
  end
384
-
407
+
385
408
  def reached_target_version?(version)
386
409
  return false if @target_version == nil
387
410
  (up? && version.to_i - 1 >= @target_version) || (down? && version.to_i <= @target_version)
388
411
  end
389
-
412
+
390
413
  def irrelevant_migration?(version)
391
414
  (up? && version.to_i <= current_version) || (down? && version.to_i > current_version)
392
415
  end