square-activerecord 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -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 +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -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 +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -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 +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -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 +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -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 +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -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 +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -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 +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -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 +359 -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 +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. metadata +223 -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.where("name = 'shugo'").lock(true).first
18
+ # yuko = Account.where("name = 'shugo'").lock(true).first
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.where(...).all
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/current/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