sskirby-activerecord 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,183 @@
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 <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
8
+ # and the update is ignored.
9
+ #
10
+ # Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
11
+ #
12
+ # == Usage
13
+ #
14
+ # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
15
+ # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
16
+ # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
17
+ #
18
+ # p1 = Person.find(1)
19
+ # p2 = Person.find(1)
20
+ #
21
+ # p1.first_name = "Michael"
22
+ # p1.save
23
+ #
24
+ # p2.first_name = "should fail"
25
+ # p2.save # Raises a ActiveRecord::StaleObjectError
26
+ #
27
+ # Optimistic locking will also check for stale data when objects are destroyed. Example:
28
+ #
29
+ # p1 = Person.find(1)
30
+ # p2 = Person.find(1)
31
+ #
32
+ # p1.first_name = "Michael"
33
+ # p1.save
34
+ #
35
+ # p2.destroy # Raises a ActiveRecord::StaleObjectError
36
+ #
37
+ # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
38
+ # or otherwise apply the business logic needed to resolve the conflict.
39
+ #
40
+ # This locking mechanism will function inside a single Ruby process. To make it work across all
41
+ # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
42
+ #
43
+ # You must ensure that your database schema defaults the +lock_version+ column to 0.
44
+ #
45
+ # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
46
+ # To override the name of the +lock_version+ column, invoke the <tt>set_locking_column</tt> method.
47
+ # This method uses the same syntax as <tt>set_table_name</tt>
48
+ module Optimistic
49
+ extend ActiveSupport::Concern
50
+
51
+ included do
52
+ cattr_accessor :lock_optimistically, :instance_writer => false
53
+ self.lock_optimistically = true
54
+ end
55
+
56
+ def locking_enabled? #:nodoc:
57
+ self.class.locking_enabled?
58
+ end
59
+
60
+ private
61
+ def increment_lock
62
+ lock_col = self.class.locking_column
63
+ previous_lock_value = send(lock_col).to_i
64
+ send(lock_col + '=', previous_lock_value + 1)
65
+ end
66
+
67
+ def update(attribute_names = @attributes.keys) #:nodoc:
68
+ return super unless locking_enabled?
69
+ return 0 if attribute_names.empty?
70
+
71
+ lock_col = self.class.locking_column
72
+ previous_lock_value = send(lock_col).to_i
73
+ increment_lock
74
+
75
+ attribute_names += [lock_col]
76
+ attribute_names.uniq!
77
+
78
+ begin
79
+ relation = self.class.unscoped
80
+
81
+ stmt = relation.where(
82
+ relation.table[self.class.primary_key].eq(id).and(
83
+ relation.table[lock_col].eq(quote_value(previous_lock_value))
84
+ )
85
+ ).arel.compile_update(arel_attributes_values(false, false, attribute_names))
86
+
87
+ affected_rows = connection.update stmt
88
+
89
+ unless affected_rows == 1
90
+ raise ActiveRecord::StaleObjectError.new(self, "update")
91
+ end
92
+
93
+ affected_rows
94
+
95
+ # If something went wrong, revert the version.
96
+ rescue Exception
97
+ send(lock_col + '=', previous_lock_value)
98
+ raise
99
+ end
100
+ end
101
+
102
+ def destroy #:nodoc:
103
+ return super unless locking_enabled?
104
+
105
+ if persisted?
106
+ table = self.class.arel_table
107
+ lock_col = self.class.locking_column
108
+ predicate = table[self.class.primary_key].eq(id).
109
+ and(table[lock_col].eq(send(lock_col).to_i))
110
+
111
+ affected_rows = self.class.unscoped.where(predicate).delete_all
112
+
113
+ unless affected_rows == 1
114
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
115
+ end
116
+ end
117
+
118
+ @destroyed = true
119
+ freeze
120
+ end
121
+
122
+ module ClassMethods
123
+ DEFAULT_LOCKING_COLUMN = 'lock_version'
124
+
125
+ # Returns true if the +lock_optimistically+ flag is set to true
126
+ # (which it is, by default) and the table includes the
127
+ # +locking_column+ column (defaults to +lock_version+).
128
+ def locking_enabled?
129
+ lock_optimistically && columns_hash[locking_column]
130
+ end
131
+
132
+ def locking_column=(value)
133
+ @original_locking_column = @locking_column if defined?(@locking_column)
134
+ @locking_column = value.to_s
135
+ end
136
+
137
+ # Set the column to use for optimistic locking. Defaults to +lock_version+.
138
+ def set_locking_column(value = nil, &block)
139
+ deprecated_property_setter :locking_column, value, block
140
+ end
141
+
142
+ # The version column used for optimistic locking. Defaults to +lock_version+.
143
+ def locking_column
144
+ reset_locking_column unless defined?(@locking_column)
145
+ @locking_column
146
+ end
147
+
148
+ def original_locking_column #:nodoc:
149
+ deprecated_original_property_getter :locking_column
150
+ end
151
+
152
+ # Quote the column name used for optimistic locking.
153
+ def quoted_locking_column
154
+ connection.quote_column_name(locking_column)
155
+ end
156
+
157
+ # Reset the column used for optimistic locking back to the +lock_version+ default.
158
+ def reset_locking_column
159
+ self.locking_column = DEFAULT_LOCKING_COLUMN
160
+ end
161
+
162
+ # Make sure the lock version column gets updated when counters are
163
+ # updated.
164
+ def update_counters(id, counters)
165
+ counters = counters.merge(locking_column => 1) if locking_enabled?
166
+ super
167
+ end
168
+
169
+ # If the locking column has no default value set,
170
+ # start the lock version at zero. Note we can't use
171
+ # <tt>locking_enabled?</tt> at this point as
172
+ # <tt>@attributes</tt> may not have been initialized yet.
173
+ def initialize_attributes(attributes) #:nodoc:
174
+ if attributes.key?(locking_column) && lock_optimistically
175
+ attributes[locking_column] ||= 0
176
+ end
177
+
178
+ attributes
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,77 @@
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 <tt>ActiveRecord::Base.find</tt> 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'. Example:
13
+ #
14
+ # Account.transaction do
15
+ # # select * from accounts where name = 'shugo' limit 1 for update
16
+ # shugo = Account.where("name = 'shugo'").lock(true).first
17
+ # yuko = Account.where("name = 'yuko'").lock(true).first
18
+ # shugo.balance -= 100
19
+ # shugo.save!
20
+ # yuko.balance += 100
21
+ # yuko.save!
22
+ # end
23
+ #
24
+ # You can also use <tt>ActiveRecord::Base#lock!</tt> method to lock one record by id.
25
+ # This may be better if you don't need to lock every row. Example:
26
+ #
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
+ # You can start a transaction and acquire the lock in one go by calling
42
+ # <tt>with_lock</tt> with a block. The block is called from within
43
+ # a transaction, the object is already locked. Example:
44
+ #
45
+ # account = Account.first
46
+ # account.with_lock do
47
+ # # This block is called within a transaction,
48
+ # # account is already locked.
49
+ # account.balance -= 100
50
+ # account.save!
51
+ # end
52
+ #
53
+ # Database-specific information on row locking:
54
+ # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
55
+ # PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
56
+ module Pessimistic
57
+ # Obtain a row lock on this record. Reloads the record to obtain the requested
58
+ # lock. Pass an SQL locking clause to append the end of the SELECT statement
59
+ # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
60
+ # the locked record.
61
+ def lock!(lock = true)
62
+ reload(:lock => lock) if persisted?
63
+ self
64
+ end
65
+
66
+ # Wraps the passed block in a transaction, locking the object
67
+ # before yielding. You pass can the SQL locking clause
68
+ # as argument (see <tt>lock!</tt>).
69
+ def with_lock(lock = true)
70
+ transaction do
71
+ lock!(lock)
72
+ yield
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,68 @@
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
+ payload = event.payload
26
+
27
+ return if 'SCHEMA' == payload[:name]
28
+
29
+ name = '%s (%.1fms)' % [payload[:name], event.duration]
30
+ sql = payload[:sql].squeeze(' ')
31
+ binds = nil
32
+
33
+ unless (payload[:binds] || []).empty?
34
+ binds = " " + payload[:binds].map { |col,v|
35
+ [col.name, v]
36
+ }.inspect
37
+ end
38
+
39
+ if odd?
40
+ name = color(name, CYAN, true)
41
+ sql = color(sql, nil, true)
42
+ else
43
+ name = color(name, MAGENTA, true)
44
+ end
45
+
46
+ debug " #{name} #{sql}#{binds}"
47
+ end
48
+
49
+ def identity(event)
50
+ return unless logger.debug?
51
+
52
+ name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
53
+ line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
54
+
55
+ debug " #{name} #{line}"
56
+ end
57
+
58
+ def odd?
59
+ @odd_or_even = !@odd_or_even
60
+ end
61
+
62
+ def logger
63
+ ActiveRecord::Base.logger
64
+ end
65
+ end
66
+ end
67
+
68
+ ActiveRecord::LogSubscriber.attach_to :active_record
@@ -0,0 +1,765 @@
1
+ require "active_support/core_ext/module/delegation"
2
+ require "active_support/core_ext/class/attribute_accessors"
3
+ require "active_support/core_ext/array/wrap"
4
+ require 'active_support/deprecation'
5
+
6
+ module ActiveRecord
7
+ # Exception that can be raised to stop migrations from going backwards.
8
+ class IrreversibleMigration < ActiveRecordError
9
+ end
10
+
11
+ class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
12
+ def initialize(version)
13
+ super("Multiple migrations have the version number #{version}")
14
+ end
15
+ end
16
+
17
+ class DuplicateMigrationNameError < ActiveRecordError#:nodoc:
18
+ def initialize(name)
19
+ super("Multiple migrations have the name #{name}")
20
+ end
21
+ end
22
+
23
+ class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
24
+ def initialize(version)
25
+ super("No migration with version number #{version}")
26
+ end
27
+ end
28
+
29
+ class IllegalMigrationNameError < ActiveRecordError#:nodoc:
30
+ def initialize(name)
31
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
32
+ end
33
+ end
34
+
35
+ # = Active Record Migrations
36
+ #
37
+ # Migrations can manage the evolution of a schema used by several physical
38
+ # databases. It's a solution to the common problem of adding a field to make
39
+ # a new feature work in your local database, but being unsure of how to
40
+ # push that change to other developers and to the production server. With
41
+ # migrations, you can describe the transformations in self-contained classes
42
+ # that can be checked into version control systems and executed against
43
+ # another database that might be one, two, or five versions behind.
44
+ #
45
+ # Example of a simple migration:
46
+ #
47
+ # class AddSsl < ActiveRecord::Migration
48
+ # def up
49
+ # add_column :accounts, :ssl_enabled, :boolean, :default => 1
50
+ # end
51
+ #
52
+ # def down
53
+ # remove_column :accounts, :ssl_enabled
54
+ # end
55
+ # end
56
+ #
57
+ # This migration will add a boolean flag to the accounts table and remove it
58
+ # if you're backing out of the migration. It shows how all migrations have
59
+ # two methods +up+ and +down+ that describes the transformations
60
+ # required to implement or remove the migration. These methods can consist
61
+ # of both the migration specific methods like add_column and remove_column,
62
+ # but may also contain regular Ruby code for generating data needed for the
63
+ # transformations.
64
+ #
65
+ # Example of a more complex migration that also needs to initialize data:
66
+ #
67
+ # class AddSystemSettings < ActiveRecord::Migration
68
+ # def up
69
+ # create_table :system_settings do |t|
70
+ # t.string :name
71
+ # t.string :label
72
+ # t.text :value
73
+ # t.string :type
74
+ # t.integer :position
75
+ # end
76
+ #
77
+ # SystemSetting.create :name => "notice",
78
+ # :label => "Use notice?",
79
+ # :value => 1
80
+ # end
81
+ #
82
+ # def down
83
+ # drop_table :system_settings
84
+ # end
85
+ # end
86
+ #
87
+ # This migration first adds the system_settings table, then creates the very
88
+ # first row in it using the Active Record model that relies on the table. It
89
+ # also uses the more advanced create_table syntax where you can specify a
90
+ # complete table schema in one block call.
91
+ #
92
+ # == Available transformations
93
+ #
94
+ # * <tt>create_table(name, options)</tt> Creates a table called +name+ and
95
+ # makes the table object available to a block that can then add columns to it,
96
+ # following the same format as add_column. See example above. The options hash
97
+ # is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
98
+ # table definition.
99
+ # * <tt>drop_table(name)</tt>: Drops the table called +name+.
100
+ # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
101
+ # to +new_name+.
102
+ # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
103
+ # to the table called +table_name+
104
+ # named +column_name+ specified to be one of the following types:
105
+ # <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
106
+ # <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
107
+ # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
108
+ # specified by passing an +options+ hash like <tt>{ :default => 11 }</tt>.
109
+ # Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
110
+ # <tt>{ :limit => 50, :null => false }</tt>) -- see
111
+ # ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
112
+ # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
113
+ # a column but keeps the type and content.
114
+ # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
115
+ # the column to a different type using the same parameters as add_column.
116
+ # * <tt>remove_column(table_name, column_names)</tt>: Removes the column listed in
117
+ # +column_names+ from the table called +table_name+.
118
+ # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
119
+ # with the name of the column. Other options include
120
+ # <tt>:name</tt>, <tt>:unique</tt> (e.g.
121
+ # <tt>{ :name => "users_name_index", :unique => true }</tt>) and <tt>:order</tt>
122
+ # (e.g. { :order => {:name => :desc} }</tt>).
123
+ # * <tt>remove_index(table_name, :column => column_name)</tt>: Removes the index
124
+ # specified by +column_name+.
125
+ # * <tt>remove_index(table_name, :name => index_name)</tt>: Removes the index
126
+ # specified by +index_name+.
127
+ #
128
+ # == Irreversible transformations
129
+ #
130
+ # Some transformations are destructive in a manner that cannot be reversed.
131
+ # Migrations of that kind should raise an <tt>ActiveRecord::IrreversibleMigration</tt>
132
+ # exception in their +down+ method.
133
+ #
134
+ # == Running migrations from within Rails
135
+ #
136
+ # The Rails package has several tools to help create and apply migrations.
137
+ #
138
+ # To generate a new migration, you can use
139
+ # rails generate migration MyNewMigration
140
+ #
141
+ # where MyNewMigration is the name of your migration. The generator will
142
+ # create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
143
+ # in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
144
+ # UTC formatted date and time that the migration was generated.
145
+ #
146
+ # You may then edit the <tt>up</tt> and <tt>down</tt> methods of
147
+ # MyNewMigration.
148
+ #
149
+ # There is a special syntactic shortcut to generate migrations that add fields to a table.
150
+ #
151
+ # rails generate migration add_fieldname_to_tablename fieldname:string
152
+ #
153
+ # This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
154
+ # class AddFieldnameToTablename < ActiveRecord::Migration
155
+ # def up
156
+ # add_column :tablenames, :fieldname, :string
157
+ # end
158
+ #
159
+ # def down
160
+ # remove_column :tablenames, :fieldname
161
+ # end
162
+ # end
163
+ #
164
+ # To run migrations against the currently configured database, use
165
+ # <tt>rake db:migrate</tt>. This will update the database by running all of the
166
+ # pending migrations, creating the <tt>schema_migrations</tt> table
167
+ # (see "About the schema_migrations table" section below) if missing. It will also
168
+ # invoke the db:schema:dump task, which will update your db/schema.rb file
169
+ # to match the structure of your database.
170
+ #
171
+ # To roll the database back to a previous migration version, use
172
+ # <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
173
+ # you wish to downgrade. If any of the migrations throw an
174
+ # <tt>ActiveRecord::IrreversibleMigration</tt> exception, that step will fail and you'll
175
+ # have some manual work to do.
176
+ #
177
+ # == Database support
178
+ #
179
+ # Migrations are currently supported in MySQL, PostgreSQL, SQLite,
180
+ # SQL Server, Sybase, and Oracle (all supported databases except DB2).
181
+ #
182
+ # == More examples
183
+ #
184
+ # Not all migrations change the schema. Some just fix the data:
185
+ #
186
+ # class RemoveEmptyTags < ActiveRecord::Migration
187
+ # def up
188
+ # Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
189
+ # end
190
+ #
191
+ # def down
192
+ # # not much we can do to restore deleted data
193
+ # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
194
+ # end
195
+ # end
196
+ #
197
+ # Others remove columns when they migrate up instead of down:
198
+ #
199
+ # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
200
+ # def up
201
+ # remove_column :items, :incomplete_items_count
202
+ # remove_column :items, :completed_items_count
203
+ # end
204
+ #
205
+ # def down
206
+ # add_column :items, :incomplete_items_count
207
+ # add_column :items, :completed_items_count
208
+ # end
209
+ # end
210
+ #
211
+ # And sometimes you need to do something in SQL not abstracted directly by migrations:
212
+ #
213
+ # class MakeJoinUnique < ActiveRecord::Migration
214
+ # def up
215
+ # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
216
+ # end
217
+ #
218
+ # def down
219
+ # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
220
+ # end
221
+ # end
222
+ #
223
+ # == Using a model after changing its table
224
+ #
225
+ # Sometimes you'll want to add a column in a migration and populate it
226
+ # immediately after. In that case, you'll need to make a call to
227
+ # <tt>Base#reset_column_information</tt> in order to ensure that the model has the
228
+ # latest column data from after the new column was added. Example:
229
+ #
230
+ # class AddPeopleSalary < ActiveRecord::Migration
231
+ # def up
232
+ # add_column :people, :salary, :integer
233
+ # Person.reset_column_information
234
+ # Person.all.each do |p|
235
+ # p.update_attribute :salary, SalaryCalculator.compute(p)
236
+ # end
237
+ # end
238
+ # end
239
+ #
240
+ # == Controlling verbosity
241
+ #
242
+ # By default, migrations will describe the actions they are taking, writing
243
+ # them to the console as they happen, along with benchmarks describing how
244
+ # long each step took.
245
+ #
246
+ # You can quiet them down by setting ActiveRecord::Migration.verbose = false.
247
+ #
248
+ # You can also insert your own messages and benchmarks by using the +say_with_time+
249
+ # method:
250
+ #
251
+ # def up
252
+ # ...
253
+ # say_with_time "Updating salaries..." do
254
+ # Person.all.each do |p|
255
+ # p.update_attribute :salary, SalaryCalculator.compute(p)
256
+ # end
257
+ # end
258
+ # ...
259
+ # end
260
+ #
261
+ # The phrase "Updating salaries..." would then be printed, along with the
262
+ # benchmark for the block when the block completes.
263
+ #
264
+ # == About the schema_migrations table
265
+ #
266
+ # Rails versions 2.0 and prior used to create a table called
267
+ # <tt>schema_info</tt> when using migrations. This table contained the
268
+ # version of the schema as of the last applied migration.
269
+ #
270
+ # Starting with Rails 2.1, the <tt>schema_info</tt> table is
271
+ # (automatically) replaced by the <tt>schema_migrations</tt> table, which
272
+ # contains the version numbers of all the migrations applied.
273
+ #
274
+ # As a result, it is now possible to add migration files that are numbered
275
+ # lower than the current schema version: when migrating up, those
276
+ # never-applied "interleaved" migrations will be automatically applied, and
277
+ # when migrating down, never-applied "interleaved" migrations will be skipped.
278
+ #
279
+ # == Timestamped Migrations
280
+ #
281
+ # By default, Rails generates migrations that look like:
282
+ #
283
+ # 20080717013526_your_migration_name.rb
284
+ #
285
+ # The prefix is a generation timestamp (in UTC).
286
+ #
287
+ # If you'd prefer to use numeric prefixes, you can turn timestamped migrations
288
+ # off by setting:
289
+ #
290
+ # config.active_record.timestamped_migrations = false
291
+ #
292
+ # In application.rb.
293
+ #
294
+ # == Reversible Migrations
295
+ #
296
+ # Starting with Rails 3.1, you will be able to define reversible migrations.
297
+ # Reversible migrations are migrations that know how to go +down+ for you.
298
+ # You simply supply the +up+ logic, and the Migration system will figure out
299
+ # how to execute the down commands for you.
300
+ #
301
+ # To define a reversible migration, define the +change+ method in your
302
+ # migration like this:
303
+ #
304
+ # class TenderloveMigration < ActiveRecord::Migration
305
+ # def change
306
+ # create_table(:horses) do |t|
307
+ # t.column :content, :text
308
+ # t.column :remind_at, :datetime
309
+ # end
310
+ # end
311
+ # end
312
+ #
313
+ # This migration will create the horses table for you on the way up, and
314
+ # automatically figure out how to drop the table on the way down.
315
+ #
316
+ # Some commands like +remove_column+ cannot be reversed. If you care to
317
+ # define how to move up and down in these cases, you should define the +up+
318
+ # and +down+ methods as before.
319
+ #
320
+ # If a command cannot be reversed, an
321
+ # <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
322
+ # the migration is moving down.
323
+ #
324
+ # For a list of commands that are reversible, please see
325
+ # <tt>ActiveRecord::Migration::CommandRecorder</tt>.
326
+ class Migration
327
+ autoload :CommandRecorder, 'active_record/migration/command_recorder'
328
+
329
+ class << self
330
+ attr_accessor :delegate # :nodoc:
331
+ end
332
+
333
+ def self.method_missing(name, *args, &block) # :nodoc:
334
+ (delegate || superclass.delegate).send(name, *args, &block)
335
+ end
336
+
337
+ def self.migrate(direction)
338
+ new.migrate direction
339
+ end
340
+
341
+ cattr_accessor :verbose
342
+
343
+ attr_accessor :name, :version
344
+
345
+ def initialize
346
+ @name = self.class.name
347
+ @version = nil
348
+ @connection = nil
349
+ end
350
+
351
+ # instantiate the delegate object after initialize is defined
352
+ self.verbose = true
353
+ self.delegate = new
354
+
355
+ def up
356
+ self.class.delegate = self
357
+ return unless self.class.respond_to?(:up)
358
+ self.class.up
359
+ end
360
+
361
+ def down
362
+ self.class.delegate = self
363
+ return unless self.class.respond_to?(:down)
364
+ self.class.down
365
+ end
366
+
367
+ # Execute this migration in the named direction
368
+ def migrate(direction)
369
+ return unless respond_to?(direction)
370
+
371
+ case direction
372
+ when :up then announce "migrating"
373
+ when :down then announce "reverting"
374
+ end
375
+
376
+ time = nil
377
+ ActiveRecord::Base.connection_pool.with_connection do |conn|
378
+ @connection = conn
379
+ if respond_to?(:change)
380
+ if direction == :down
381
+ recorder = CommandRecorder.new(@connection)
382
+ suppress_messages do
383
+ @connection = recorder
384
+ change
385
+ end
386
+ @connection = conn
387
+ time = Benchmark.measure {
388
+ recorder.inverse.each do |cmd, args|
389
+ send(cmd, *args)
390
+ end
391
+ }
392
+ else
393
+ time = Benchmark.measure { change }
394
+ end
395
+ else
396
+ time = Benchmark.measure { send(direction) }
397
+ end
398
+ @connection = nil
399
+ end
400
+
401
+ case direction
402
+ when :up then announce "migrated (%.4fs)" % time.real; write
403
+ when :down then announce "reverted (%.4fs)" % time.real; write
404
+ end
405
+ end
406
+
407
+ def write(text="")
408
+ puts(text) if verbose
409
+ end
410
+
411
+ def announce(message)
412
+ text = "#{version} #{name}: #{message}"
413
+ length = [0, 75 - text.length].max
414
+ write "== %s %s" % [text, "=" * length]
415
+ end
416
+
417
+ def say(message, subitem=false)
418
+ write "#{subitem ? " ->" : "--"} #{message}"
419
+ end
420
+
421
+ def say_with_time(message)
422
+ say(message)
423
+ result = nil
424
+ time = Benchmark.measure { result = yield }
425
+ say "%.4fs" % time.real, :subitem
426
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
427
+ result
428
+ end
429
+
430
+ def suppress_messages
431
+ save, self.verbose = verbose, false
432
+ yield
433
+ ensure
434
+ self.verbose = save
435
+ end
436
+
437
+ def connection
438
+ @connection || ActiveRecord::Base.connection
439
+ end
440
+
441
+ def method_missing(method, *arguments, &block)
442
+ arg_list = arguments.map{ |a| a.inspect } * ', '
443
+
444
+ say_with_time "#{method}(#{arg_list})" do
445
+ unless arguments.empty? || method == :execute
446
+ arguments[0] = Migrator.proper_table_name(arguments.first)
447
+ arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
448
+ end
449
+ return super unless connection.respond_to?(method)
450
+ connection.send(method, *arguments, &block)
451
+ end
452
+ end
453
+
454
+ def copy(destination, sources, options = {})
455
+ copied = []
456
+
457
+ FileUtils.mkdir_p(destination) unless File.exists?(destination)
458
+
459
+ destination_migrations = ActiveRecord::Migrator.migrations(destination)
460
+ last = destination_migrations.last
461
+ sources.each do |scope, path|
462
+ source_migrations = ActiveRecord::Migrator.migrations(path)
463
+
464
+ source_migrations.each do |migration|
465
+ source = File.read(migration.filename)
466
+ source = "# This migration comes from #{scope} (originally #{migration.version})\n#{source}"
467
+
468
+ if duplicate = destination_migrations.detect { |m| m.name == migration.name }
469
+ if options[:on_skip] && duplicate.scope != scope.to_s
470
+ options[:on_skip].call(scope, migration)
471
+ end
472
+ next
473
+ end
474
+
475
+ migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
476
+ new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
477
+ old_path, migration.filename = migration.filename, new_path
478
+ last = migration
479
+
480
+ File.open(migration.filename, "w") { |f| f.write source }
481
+ copied << migration
482
+ options[:on_copy].call(scope, migration, old_path) if options[:on_copy]
483
+ destination_migrations << migration
484
+ end
485
+ end
486
+
487
+ copied
488
+ end
489
+
490
+ def next_migration_number(number)
491
+ if ActiveRecord::Base.timestamped_migrations
492
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
493
+ else
494
+ "%.3d" % number
495
+ end
496
+ end
497
+ end
498
+
499
+ # MigrationProxy is used to defer loading of the actual migration classes
500
+ # until they are needed
501
+ class MigrationProxy < Struct.new(:name, :version, :filename, :scope)
502
+
503
+ def initialize(name, version, filename, scope)
504
+ super
505
+ @migration = nil
506
+ end
507
+
508
+ def basename
509
+ File.basename(filename)
510
+ end
511
+
512
+ delegate :migrate, :announce, :write, :to => :migration
513
+
514
+ private
515
+
516
+ def migration
517
+ @migration ||= load_migration
518
+ end
519
+
520
+ def load_migration
521
+ require(File.expand_path(filename))
522
+ name.constantize.new
523
+ end
524
+
525
+ end
526
+
527
+ class Migrator#:nodoc:
528
+ class << self
529
+ attr_writer :migrations_paths
530
+ alias :migrations_path= :migrations_paths=
531
+
532
+ def migrate(migrations_paths, target_version = nil, &block)
533
+ case
534
+ when target_version.nil?
535
+ up(migrations_paths, target_version, &block)
536
+ when current_version == 0 && target_version == 0
537
+ []
538
+ when current_version > target_version
539
+ down(migrations_paths, target_version, &block)
540
+ else
541
+ up(migrations_paths, target_version, &block)
542
+ end
543
+ end
544
+
545
+ def rollback(migrations_paths, steps=1)
546
+ move(:down, migrations_paths, steps)
547
+ end
548
+
549
+ def forward(migrations_paths, steps=1)
550
+ move(:up, migrations_paths, steps)
551
+ end
552
+
553
+ def up(migrations_paths, target_version = nil, &block)
554
+ self.new(:up, migrations_paths, target_version).migrate(&block)
555
+ end
556
+
557
+ def down(migrations_paths, target_version = nil, &block)
558
+ self.new(:down, migrations_paths, target_version).migrate(&block)
559
+ end
560
+
561
+ def run(direction, migrations_paths, target_version)
562
+ self.new(direction, migrations_paths, target_version).run
563
+ end
564
+
565
+ def schema_migrations_table_name
566
+ Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
567
+ end
568
+
569
+ def get_all_versions
570
+ table = Arel::Table.new(schema_migrations_table_name)
571
+ Base.connection.select_values(table.project(table['version'])).map{ |v| v.to_i }.sort
572
+ end
573
+
574
+ def current_version
575
+ sm_table = schema_migrations_table_name
576
+ if Base.connection.table_exists?(sm_table)
577
+ get_all_versions.max || 0
578
+ else
579
+ 0
580
+ end
581
+ end
582
+
583
+ def proper_table_name(name)
584
+ # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
585
+ name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
586
+ end
587
+
588
+ def migrations_paths
589
+ @migrations_paths ||= ['db/migrate']
590
+ # just to not break things if someone uses: migration_path = some_string
591
+ Array.wrap(@migrations_paths)
592
+ end
593
+
594
+ def migrations_path
595
+ migrations_paths.first
596
+ end
597
+
598
+ def migrations(paths, *args)
599
+ if args.empty?
600
+ subdirectories = true
601
+ else
602
+ subdirectories = args.first
603
+ ActiveSupport::Deprecation.warn "The `subdirectories` argument to `migrations` is deprecated"
604
+ end
605
+
606
+ paths = Array.wrap(paths)
607
+
608
+ glob = subdirectories ? "**/" : ""
609
+ files = Dir[*paths.map { |p| "#{p}/#{glob}[0-9]*_*.rb" }]
610
+
611
+ seen = Hash.new false
612
+
613
+ migrations = files.map do |file|
614
+ version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
615
+
616
+ raise IllegalMigrationNameError.new(file) unless version
617
+ version = version.to_i
618
+ name = name.camelize
619
+
620
+ raise DuplicateMigrationVersionError.new(version) if seen[version]
621
+ raise DuplicateMigrationNameError.new(name) if seen[name]
622
+
623
+ seen[version] = seen[name] = true
624
+
625
+ MigrationProxy.new(name, version, file, scope)
626
+ end
627
+
628
+ migrations.sort_by(&:version)
629
+ end
630
+
631
+ private
632
+
633
+ def move(direction, migrations_paths, steps)
634
+ migrator = self.new(direction, migrations_paths)
635
+ start_index = migrator.migrations.index(migrator.current_migration)
636
+
637
+ if start_index
638
+ finish = migrator.migrations[start_index + steps]
639
+ version = finish ? finish.version : 0
640
+ send(direction, migrations_paths, version)
641
+ end
642
+ end
643
+ end
644
+
645
+ def initialize(direction, migrations_paths, target_version = nil)
646
+ raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
647
+ Base.connection.initialize_schema_migrations_table
648
+ @direction, @migrations_paths, @target_version = direction, migrations_paths, target_version
649
+ end
650
+
651
+ def current_version
652
+ migrated.last || 0
653
+ end
654
+
655
+ def current_migration
656
+ migrations.detect { |m| m.version == current_version }
657
+ end
658
+
659
+ def run
660
+ target = migrations.detect { |m| m.version == @target_version }
661
+ raise UnknownMigrationVersionError.new(@target_version) if target.nil?
662
+ unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
663
+ target.migrate(@direction)
664
+ record_version_state_after_migrating(target.version)
665
+ end
666
+ end
667
+
668
+ def migrate(&block)
669
+ current = migrations.detect { |m| m.version == current_version }
670
+ target = migrations.detect { |m| m.version == @target_version }
671
+
672
+ if target.nil? && @target_version && @target_version > 0
673
+ raise UnknownMigrationVersionError.new(@target_version)
674
+ end
675
+
676
+ start = up? ? 0 : (migrations.index(current) || 0)
677
+ finish = migrations.index(target) || migrations.size - 1
678
+ runnable = migrations[start..finish]
679
+
680
+ # skip the last migration if we're headed down, but not ALL the way down
681
+ runnable.pop if down? && target
682
+
683
+ ran = []
684
+ runnable.each do |migration|
685
+ if block && !block.call(migration)
686
+ next
687
+ end
688
+
689
+ Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
690
+
691
+ seen = migrated.include?(migration.version.to_i)
692
+
693
+ # On our way up, we skip migrating the ones we've already migrated
694
+ next if up? && seen
695
+
696
+ # On our way down, we skip reverting the ones we've never migrated
697
+ if down? && !seen
698
+ migration.announce 'never migrated, skipping'; migration.write
699
+ next
700
+ end
701
+
702
+ begin
703
+ ddl_transaction do
704
+ migration.migrate(@direction)
705
+ record_version_state_after_migrating(migration.version)
706
+ end
707
+ ran << migration
708
+ rescue => e
709
+ canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
710
+ raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
711
+ end
712
+ end
713
+ ran
714
+ end
715
+
716
+ def migrations
717
+ @migrations ||= begin
718
+ migrations = self.class.migrations(@migrations_paths)
719
+ down? ? migrations.reverse : migrations
720
+ end
721
+ end
722
+
723
+ def pending_migrations
724
+ already_migrated = migrated
725
+ migrations.reject { |m| already_migrated.include?(m.version.to_i) }
726
+ end
727
+
728
+ def migrated
729
+ @migrated_versions ||= self.class.get_all_versions
730
+ end
731
+
732
+ private
733
+ def record_version_state_after_migrating(version)
734
+ table = Arel::Table.new(self.class.schema_migrations_table_name)
735
+
736
+ @migrated_versions ||= []
737
+ if down?
738
+ @migrated_versions.delete(version)
739
+ stmt = table.where(table["version"].eq(version.to_s)).compile_delete
740
+ Base.connection.delete stmt
741
+ else
742
+ @migrated_versions.push(version).sort!
743
+ stmt = table.compile_insert table["version"] => version.to_s
744
+ Base.connection.insert stmt
745
+ end
746
+ end
747
+
748
+ def up?
749
+ @direction == :up
750
+ end
751
+
752
+ def down?
753
+ @direction == :down
754
+ end
755
+
756
+ # Wrap the migration in a transaction only if supported by the adapter.
757
+ def ddl_transaction(&block)
758
+ if Base.connection.supports_ddl_transactions?
759
+ Base.transaction { block.call }
760
+ else
761
+ block.call
762
+ end
763
+ end
764
+ end
765
+ end