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.
- data/CHANGELOG +2454 -34
- data/README +1 -1
- data/RUNNING_UNIT_TESTS +3 -34
- data/Rakefile +98 -77
- data/install.rb +1 -1
- data/lib/active_record.rb +13 -22
- data/lib/active_record/aggregations.rb +38 -49
- data/lib/active_record/associations.rb +452 -333
- data/lib/active_record/associations/association_collection.rb +66 -20
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +38 -18
- data/lib/active_record/associations/has_one_association.rb +30 -14
- data/lib/active_record/attribute_methods.rb +253 -0
- data/lib/active_record/base.rb +719 -494
- data/lib/active_record/calculations.rb +62 -63
- data/lib/active_record/callbacks.rb +57 -83
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
- data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
- data/lib/active_record/fixtures.rb +503 -113
- data/lib/active_record/locking/optimistic.rb +72 -34
- data/lib/active_record/migration.rb +80 -57
- data/lib/active_record/observer.rb +13 -10
- data/lib/active_record/query_cache.rb +16 -57
- data/lib/active_record/reflection.rb +35 -38
- data/lib/active_record/schema.rb +5 -5
- data/lib/active_record/schema_dumper.rb +35 -13
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
- data/lib/active_record/timestamp.rb +20 -21
- data/lib/active_record/transactions.rb +39 -43
- data/lib/active_record/validations.rb +256 -107
- data/lib/active_record/version.rb +3 -3
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +15 -2
- data/test/abstract_unit.rb +24 -17
- data/test/active_schema_test_mysql.rb +20 -8
- data/test/adapter_test.rb +23 -5
- data/test/adapter_test_sqlserver.rb +15 -1
- data/test/aggregations_test.rb +16 -1
- data/test/all.sh +2 -2
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +51 -30
- data/test/associations/cascaded_eager_loading_test.rb +1 -29
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +42 -6
- data/test/associations/extension_test.rb +6 -1
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +47 -16
- data/test/associations_test.rb +449 -226
- data/test/attribute_methods_test.rb +97 -0
- data/test/base_test.rb +251 -105
- data/test/binary_test.rb +22 -27
- data/test/calculations_test.rb +37 -5
- data/test/callbacks_test.rb +23 -0
- data/test/connection_test_firebird.rb +2 -2
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_mysql/connection.rb +3 -0
- data/test/connections/native_sqlite/connection.rb +5 -14
- data/test/connections/native_sqlite3/connection.rb +5 -14
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
- data/test/datatype_test_postgresql.rb +178 -27
- data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
- data/test/defaults_test.rb +8 -1
- data/test/deprecated_finder_test.rb +7 -128
- data/test/finder_test.rb +192 -54
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author.rb +12 -5
- data/test/fixtures/binaries.yml +130 -435
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +8 -1
- data/test/fixtures/computer.rb +1 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
- data/test/fixtures/db_definitions/firebird.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase.sql +5 -0
- data/test/fixtures/db_definitions/openbase.sql +41 -25
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +5 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
- data/test/fixtures/db_definitions/postgresql.sql +87 -58
- data/test/fixtures/db_definitions/postgresql2.sql +1 -2
- data/test/fixtures/db_definitions/schema.rb +280 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +4 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
- data/test/fixtures/db_definitions/sybase.sql +4 -0
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +0 -3
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixins.yml +2 -100
- data/test/fixtures/parrot.rb +13 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +1 -0
- data/test/fixtures/project.rb +3 -2
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ship.rb +3 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/tagging.rb +4 -0
- data/test/fixtures/taggings.yml +8 -1
- data/test/fixtures/topic.rb +13 -1
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures_test.rb +205 -24
- data/test/inheritance_test.rb +7 -1
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +1 -1
- data/test/locking_test.rb +85 -2
- data/test/migration_test.rb +206 -40
- data/test/mixin_test.rb +13 -515
- data/test/pk_test.rb +3 -6
- data/test/query_cache_test.rb +104 -0
- data/test/reflection_test.rb +16 -0
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_dumper_test.rb +38 -3
- data/test/serialization_test.rb +47 -0
- data/test/transactions_test.rb +74 -23
- data/test/unconnected_test.rb +1 -1
- data/test/validations_test.rb +322 -32
- data/test/xml_serialization_test.rb +121 -44
- metadata +48 -41
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -85
- data/lib/active_record/acts/list.rb +0 -256
- data/lib/active_record/acts/nested_set.rb +0 -211
- data/lib/active_record/acts/tree.rb +0 -96
- data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
- data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
- data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
- data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
- data/lib/active_record/deprecated_associations.rb +0 -104
- data/lib/active_record/deprecated_finders.rb +0 -44
- data/lib/active_record/vendor/simple.rb +0 -693
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -58
- data/test/connections/native_sqlserver/connection.rb +0 -23
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
- data/test/deprecated_associations_test.rb +0 -396
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
- data/test/fixtures/db_definitions/mysql.sql +0 -234
- data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/mysql2.sql +0 -5
- data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
- data/test/fixtures/db_definitions/sqlserver.sql +0 -243
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
- data/test/fixtures/mixin.rb +0 -63
- 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
|
-
|
50
|
+
self.class.locking_enabled?
|
42
51
|
end
|
43
52
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
result
|
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
|
-
|
60
|
-
|
69
|
+
def update_with_lock #:nodoc:
|
70
|
+
return update_without_lock unless locking_enabled?
|
61
71
|
|
62
|
-
|
63
|
-
|
64
|
-
|
72
|
+
lock_col = self.class.locking_column
|
73
|
+
previous_value = send(lock_col)
|
74
|
+
send(lock_col + '=', previous_value + 1)
|
65
75
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
84
|
+
unless affected_rows == 1
|
85
|
+
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
|
86
|
+
end
|
76
87
|
|
77
|
-
|
78
|
-
|
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
|
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
|
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.
|
40
|
-
# t.
|
41
|
-
# t.
|
42
|
-
# t.
|
43
|
-
# t.
|
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,
|
76
|
-
#
|
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
|
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
|
-
#
|
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
|
201
|
+
def up_with_benchmarks #:nodoc:
|
195
202
|
migrate(:up)
|
196
203
|
end
|
197
204
|
|
198
|
-
def
|
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("
|
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(:
|
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
|
-
|
274
|
-
|
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
|
-
|
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 |
|
331
|
-
|
332
|
-
|
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 <<
|
363
|
+
migrations << migration_class(name, version.to_i)
|
347
364
|
end
|
348
365
|
|
349
|
-
|
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.
|
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)
|
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
|