activerecord 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 (93) hide show
  1. data/CHANGELOG +6023 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +162 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +403 -0
  9. data/lib/active_record/associations.rb +2254 -0
  10. data/lib/active_record/associations/association_collection.rb +562 -0
  11. data/lib/active_record/associations/association_proxy.rb +295 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +137 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +116 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  27. data/lib/active_record/attribute_methods/write.rb +37 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1867 -0
  30. data/lib/active_record/callbacks.rb +288 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +212 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +643 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1030 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +53 -0
  46. data/lib/active_record/dynamic_scope_match.rb +32 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1008 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +417 -0
  56. data/lib/active_record/observer.rb +140 -0
  57. data/lib/active_record/persistence.rb +291 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +403 -0
  63. data/lib/active_record/relation.rb +393 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +286 -0
  66. data/lib/active_record/relation/finder_methods.rb +355 -0
  67. data/lib/active_record/relation/predicate_builder.rb +41 -0
  68. data/lib/active_record/relation/query_methods.rb +261 -0
  69. data/lib/active_record/relation/spawn_methods.rb +112 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +356 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +185 -0
  81. data/lib/active_record/version.rb +9 -0
  82. data/lib/rails/generators/active_record.rb +27 -0
  83. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  84. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  85. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  86. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  87. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  88. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  89. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  90. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  91. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  92. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  93. metadata +224 -0
@@ -0,0 +1,40 @@
1
+ en:
2
+ # Attributes names common to most models
3
+ #attributes:
4
+ #created_at: "Created at"
5
+ #updated_at: "Updated at"
6
+
7
+ # Active Record models configuration
8
+ activerecord:
9
+ errors:
10
+ messages:
11
+ taken: "has already been taken"
12
+ record_invalid: "Validation failed: %{errors}"
13
+ # Append your own errors here or at the model/attributes scope.
14
+
15
+ # You can define own errors for models or model attributes.
16
+ # The values :model, :attribute and :value are always available for interpolation.
17
+ #
18
+ # For example,
19
+ # models:
20
+ # user:
21
+ # blank: "This is a custom blank message for %{model}: %{attribute}"
22
+ # attributes:
23
+ # login:
24
+ # blank: "This is a custom blank message for User login"
25
+ # Will define custom blank validation message for User model and
26
+ # custom blank validation message for login attribute of User model.
27
+ #models:
28
+
29
+ # Translate model names. Used in Model.human_name().
30
+ #models:
31
+ # For example,
32
+ # user: "Dude"
33
+ # will translate User model name to "Dude"
34
+
35
+ # Translate model attribute names. Used in Model.human_attribute_name(attribute).
36
+ #attributes:
37
+ # For example,
38
+ # user:
39
+ # login: "Handle"
40
+ # will translate User attribute "login" as "Handle"
@@ -0,0 +1,172 @@
1
+ module ActiveRecord
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
+ #
13
+ # Active Records support optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
14
+ # record increments the lock_version column and the locking facilities ensure that records instantiated twice
15
+ # will let the last one saved raise a StaleObjectError if the first was also updated. Example:
16
+ #
17
+ # p1 = Person.find(1)
18
+ # p2 = Person.find(1)
19
+ #
20
+ # p1.first_name = "Michael"
21
+ # p1.save
22
+ #
23
+ # p2.first_name = "should fail"
24
+ # p2.save # Raises a ActiveRecord::StaleObjectError
25
+ #
26
+ # Optimistic locking will also check for stale data when objects are destroyed. Example:
27
+ #
28
+ # p1 = Person.find(1)
29
+ # p2 = Person.find(1)
30
+ #
31
+ # p1.first_name = "Michael"
32
+ # p1.save
33
+ #
34
+ # p2.destroy # Raises a ActiveRecord::StaleObjectError
35
+ #
36
+ # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
37
+ # or otherwise apply the business logic needed to resolve the conflict.
38
+ #
39
+ # You must ensure that your database schema defaults the lock_version column to 0.
40
+ #
41
+ # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
42
+ # To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
43
+ # This method uses the same syntax as <tt>set_table_name</tt>
44
+ module Optimistic
45
+ extend ActiveSupport::Concern
46
+
47
+ included do
48
+ cattr_accessor :lock_optimistically, :instance_writer => false
49
+ self.lock_optimistically = true
50
+
51
+ class << self
52
+ alias_method :locking_column=, :set_locking_column
53
+ end
54
+ end
55
+
56
+ def locking_enabled? #:nodoc:
57
+ self.class.locking_enabled?
58
+ end
59
+
60
+ private
61
+ def attributes_from_column_definition
62
+ result = super
63
+
64
+ # If the locking column has no default value set,
65
+ # start the lock version at zero. Note we can't use
66
+ # locking_enabled? at this point as @attributes may
67
+ # not have been initialized yet
68
+
69
+ if lock_optimistically && result.include?(self.class.locking_column)
70
+ result[self.class.locking_column] ||= 0
71
+ end
72
+
73
+ return result
74
+ end
75
+
76
+ def update(attribute_names = @attributes.keys) #:nodoc:
77
+ return super unless locking_enabled?
78
+ return 0 if attribute_names.empty?
79
+
80
+ lock_col = self.class.locking_column
81
+ previous_value = send(lock_col).to_i
82
+ send(lock_col + '=', previous_value + 1)
83
+
84
+ attribute_names += [lock_col]
85
+ attribute_names.uniq!
86
+
87
+ begin
88
+ relation = self.class.unscoped
89
+
90
+ affected_rows = relation.where(
91
+ relation.table[self.class.primary_key].eq(quoted_id).and(
92
+ relation.table[self.class.locking_column].eq(quote_value(previous_value))
93
+ )
94
+ ).arel.update(arel_attributes_values(false, false, attribute_names))
95
+
96
+ unless affected_rows == 1
97
+ raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
98
+ end
99
+
100
+ affected_rows
101
+
102
+ # If something went wrong, revert the version.
103
+ rescue Exception
104
+ send(lock_col + '=', previous_value)
105
+ raise
106
+ end
107
+ end
108
+
109
+ def destroy #:nodoc:
110
+ return super unless locking_enabled?
111
+
112
+ unless new_record?
113
+ lock_col = self.class.locking_column
114
+ previous_value = send(lock_col).to_i
115
+
116
+ table = self.class.arel_table
117
+ predicate = table[self.class.primary_key].eq(id)
118
+ predicate = predicate.and(table[self.class.locking_column].eq(previous_value))
119
+
120
+ affected_rows = self.class.unscoped.where(predicate).delete_all
121
+
122
+ unless affected_rows == 1
123
+ raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}"
124
+ end
125
+ end
126
+
127
+ @destroyed = true
128
+ freeze
129
+ end
130
+
131
+ module ClassMethods
132
+ DEFAULT_LOCKING_COLUMN = 'lock_version'
133
+
134
+ # Is optimistic locking enabled for this table? Returns true if the
135
+ # +lock_optimistically+ flag is set to true (which it is, by default)
136
+ # and the table includes the +locking_column+ column (defaults to
137
+ # +lock_version+).
138
+ def locking_enabled?
139
+ lock_optimistically && columns_hash[locking_column]
140
+ end
141
+
142
+ # Set the column to use for optimistic locking. Defaults to +lock_version+.
143
+ def set_locking_column(value = nil, &block)
144
+ define_attr_method :locking_column, value, &block
145
+ value
146
+ end
147
+
148
+ # The version column used for optimistic locking. Defaults to +lock_version+.
149
+ def locking_column
150
+ reset_locking_column
151
+ end
152
+
153
+ # Quote the column name used for optimistic locking.
154
+ def quoted_locking_column
155
+ connection.quote_column_name(locking_column)
156
+ end
157
+
158
+ # Reset the column used for optimistic locking back to the +lock_version+ default.
159
+ def reset_locking_column
160
+ set_locking_column DEFAULT_LOCKING_COLUMN
161
+ end
162
+
163
+ # Make sure the lock version column gets updated when counters are
164
+ # updated.
165
+ def update_counters(id, counters)
166
+ counters = counters.merge(locking_column => 1) if locking_enabled?
167
+ super
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,55 @@
1
+ module ActiveRecord
2
+ module Locking
3
+ # Locking::Pessimistic provides support for row-level locking using
4
+ # SELECT ... FOR UPDATE and other lock types.
5
+ #
6
+ # Pass <tt>:lock => true</tt> to ActiveRecord::Base.find to obtain an exclusive
7
+ # lock on the selected rows:
8
+ # # select * from accounts where id=1 for update
9
+ # Account.find(1, :lock => true)
10
+ #
11
+ # Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
12
+ # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'.
13
+ #
14
+ # Example:
15
+ # Account.transaction do
16
+ # # select * from accounts where name = 'shugo' limit 1 for update
17
+ # shugo = Account.find(:first, :conditions => "name = 'shugo'", :lock => true)
18
+ # yuko = Account.find(:first, :conditions => "name = 'yuko'", :lock => true)
19
+ # shugo.balance -= 100
20
+ # shugo.save!
21
+ # yuko.balance += 100
22
+ # yuko.save!
23
+ # end
24
+ #
25
+ # You can also use ActiveRecord::Base#lock! method to lock one record by id.
26
+ # This may be better if you don't need to lock every row. Example:
27
+ # Account.transaction do
28
+ # # select * from accounts where ...
29
+ # accounts = Account.find(:all, :conditions => ...)
30
+ # account1 = accounts.detect { |account| ... }
31
+ # account2 = accounts.detect { |account| ... }
32
+ # # select * from accounts where id=? for update
33
+ # account1.lock!
34
+ # account2.lock!
35
+ # account1.balance -= 100
36
+ # account1.save!
37
+ # account2.balance += 100
38
+ # account2.save!
39
+ # end
40
+ #
41
+ # Database-specific information on row locking:
42
+ # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
43
+ # PostgreSQL: http://www.postgresql.org/docs/8.1/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
44
+ module Pessimistic
45
+ # Obtain a row lock on this record. Reloads the record to obtain the requested
46
+ # lock. Pass an SQL locking clause to append the end of the SELECT statement
47
+ # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
48
+ # the locked record.
49
+ def lock!(lock = true)
50
+ reload(:lock => lock) unless new_record?
51
+ self
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,48 @@
1
+ module ActiveRecord
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ def self.runtime=(value)
4
+ Thread.current["active_record_sql_runtime"] = value
5
+ end
6
+
7
+ def self.runtime
8
+ Thread.current["active_record_sql_runtime"] ||= 0
9
+ end
10
+
11
+ def self.reset_runtime
12
+ rt, self.runtime = runtime, 0
13
+ rt
14
+ end
15
+
16
+ def initialize
17
+ super
18
+ @odd_or_even = false
19
+ end
20
+
21
+ def sql(event)
22
+ self.class.runtime += event.duration
23
+ return unless logger.debug?
24
+
25
+ name = '%s (%.1fms)' % [event.payload[:name], event.duration]
26
+ sql = event.payload[:sql].squeeze(' ')
27
+
28
+ if odd?
29
+ name = color(name, CYAN, true)
30
+ sql = color(sql, nil, true)
31
+ else
32
+ name = color(name, MAGENTA, true)
33
+ end
34
+
35
+ debug " #{name} #{sql}"
36
+ end
37
+
38
+ def odd?
39
+ @odd_or_even = !@odd_or_even
40
+ end
41
+
42
+ def logger
43
+ ActiveRecord::Base.logger
44
+ end
45
+ end
46
+ end
47
+
48
+ ActiveRecord::LogSubscriber.attach_to :active_record
@@ -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