sequel 3.17.0 → 3.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/CHANGELOG +22 -0
  2. data/doc/migration.rdoc +34 -0
  3. data/doc/release_notes/3.18.0.txt +121 -0
  4. data/lib/sequel/adapters/jdbc.rb +6 -1
  5. data/lib/sequel/adapters/mysql.rb +11 -1
  6. data/lib/sequel/adapters/mysql2.rb +3 -1
  7. data/lib/sequel/adapters/postgres.rb +1 -1
  8. data/lib/sequel/adapters/shared/sqlite.rb +13 -1
  9. data/lib/sequel/adapters/sqlite.rb +31 -26
  10. data/lib/sequel/connection_pool/sharded_single.rb +5 -1
  11. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  12. data/lib/sequel/dataset/sql.rb +0 -5
  13. data/lib/sequel/extensions/migration.rb +117 -1
  14. data/lib/sequel/extensions/to_dot.rb +137 -0
  15. data/lib/sequel/model/base.rb +1 -1
  16. data/lib/sequel/plugins/instance_hooks.rb +14 -9
  17. data/lib/sequel/plugins/json_serializer.rb +3 -3
  18. data/lib/sequel/sql.rb +1 -1
  19. data/lib/sequel/version.rb +1 -1
  20. data/spec/adapters/sqlite_spec.rb +15 -7
  21. data/spec/core/connection_pool_spec.rb +17 -1
  22. data/spec/core/dataset_spec.rb +9 -0
  23. data/spec/extensions/instance_hooks_spec.rb +46 -0
  24. data/spec/extensions/json_serializer_spec.rb +11 -0
  25. data/spec/extensions/migration_spec.rb +107 -0
  26. data/spec/extensions/spec_helper.rb +1 -1
  27. data/spec/extensions/to_dot_spec.rb +152 -0
  28. data/spec/files/reversible_migrations/001_reversible.rb +5 -0
  29. data/spec/files/reversible_migrations/002_reversible.rb +5 -0
  30. data/spec/files/reversible_migrations/003_reversible.rb +5 -0
  31. data/spec/files/reversible_migrations/004_reversible.rb +5 -0
  32. data/spec/files/reversible_migrations/005_reversible.rb +10 -0
  33. data/spec/integration/migrator_test.rb +54 -0
  34. data/spec/model/association_reflection_spec.rb +19 -0
  35. metadata +13 -4
data/CHANGELOG CHANGED
@@ -1,3 +1,25 @@
1
+ === 3.18.0 (2010-12-01)
2
+
3
+ * Allow the user to control how the connection pool deals with attempts to access shards that aren't configured (jeremyevans)
4
+
5
+ * Typecast columns when creating model objects from JSON in the json_serializer plugin (jeremyevans)
6
+
7
+ * When parsing the schema for a model that uses an aliased table, use the unaliased table name (jeremyevans)
8
+
9
+ * When emulating schema methods such as drop_column on SQLite, recreate applicable indexes on the recreated table (jeremyevans)
10
+
11
+ * Only remove hook pairs that have been run successfully in the instance_hooks plugin (jeremyevans)
12
+
13
+ * Add reversible migration support to the migration extension (jeremyevans)
14
+
15
+ * Add to_dot extension, for producing visualizations of Dataset abstract syntax trees with Graphviz (jeremyevans)
16
+
17
+ * Switch to using manual type translation in the SQLite adapter (jeremyevans)
18
+
19
+ * Support :read_timeout option in the native mysql adapter (tmm1)
20
+
21
+ * Support :connect_timeout option in the native mysql and mysql2 adapters (tmm1)
22
+
1
23
  === 3.17.0 (2010-11-05)
2
24
 
3
25
  * Ensure that the optimistic locking plugin increments the lock column when using Model#modified! (jfirebaugh)
data/doc/migration.rdoc CHANGED
@@ -52,6 +52,40 @@ the change made by up. However, if you never need to be able to migrate down
52
52
  the +down+ block. In this case, the +down+ block just reverses the changes made by up,
53
53
  dropping the table.
54
54
 
55
+ You can simplify the migration given above by using a reversible migration with a +change+
56
+ block:
57
+
58
+ Sequel.migration do
59
+ change do
60
+ create_table(:artists) do
61
+ primary_key :id
62
+ String :name, :null=>false
63
+ end
64
+ end
65
+ end
66
+
67
+ The +change+ block acts exactly like an +up+ block. The only difference is that
68
+ it will attempt to create a +down+ block for you, assuming that it knows how to
69
+ reverse the given migration. The +change+ block can usually correctly reverse
70
+ the following methods:
71
+
72
+ * +create_table+
73
+ * +add_column+
74
+ * +add_index+
75
+ * +rename_column+
76
+ * +rename_table+
77
+ * +alter_table+ (supporting the following methods in the +alter_table+ block):
78
+ * +add_column+
79
+ * +add_constraint+
80
+ * +add_foreign_key+ (with a symbol, not an array)
81
+ * +add_primary_key+ (with a symbol, not an array)
82
+ * +add_index+
83
+ * +add_full_text_index+
84
+ * +add_spatial_index+
85
+ * +rename_column+
86
+
87
+ If you use any other methods, you should create your own +down+ block.
88
+
55
89
  In normal usage, when Sequel's migrator runs, it runs the +up+ blocks for all
56
90
  migrations that have not yet been applied. However, you can use the <tt>-M</tt>
57
91
  switch to specify the version to which to migrate, and if it is lower than the
@@ -0,0 +1,121 @@
1
+ = New Features
2
+
3
+ * Reversible migration support has been added:
4
+
5
+ Sequel.migration do
6
+ change do
7
+ create_table(:artists) do
8
+ primary_key :id
9
+ String :name, :null=>false
10
+ end
11
+ end
12
+ end
13
+
14
+ The change block acts the same way as an up block, except that
15
+ it automatically creates a down block that reverses the changes.
16
+ So the above is equivalent to:
17
+
18
+ Sequel.migration do
19
+ up do
20
+ create_table(:artists) do
21
+ primary_key :id
22
+ String :name, :null=>false
23
+ end
24
+ end
25
+ down do
26
+ drop_table :artists
27
+ end
28
+ end
29
+
30
+ The following methods are supported in a change block:
31
+
32
+ * create_table
33
+ * add_column
34
+ * add_index
35
+ * rename_column
36
+ * rename_table
37
+ * alter_table (supporting the following methods):
38
+ * add_column
39
+ * add_constraint
40
+ * add_foreign_key (with a symbol, not an array)
41
+ * add_primary_key (with a symbol, not an array)
42
+ * add_index
43
+ * add_full_text_index
44
+ * add_spatial_index
45
+ * rename_column
46
+
47
+ Use of an other method in a change block will result in the
48
+ creation of a down block that raises an exception.
49
+
50
+ * A to_dot extension has been added that adds a Dataset#to_dot
51
+ method, which returns a string that can be used as input to
52
+ the graphviz dot program in order to create visualizations
53
+ of the dataset's abstract syntax tree. Examples:
54
+
55
+ * http://sequel.heroku.com/images/to_dot_simple.gif
56
+ * http://sequel.heroku.com/images/to_dot_complex.gif
57
+ * http://imgpaste.com/i/lxngy.gif
58
+
59
+ Both the to_dot extension and reversible migrations support
60
+ were inspired by Aaron Patterson's recent work on ActiveRecord
61
+ and ARel.
62
+
63
+ * The user can now control how the connection pool handles attempts
64
+ to access shards that haven't been configured. The default is
65
+ still to assume the :default shard. However, you can specify a
66
+ different shard using the :servers_hash option when connecting
67
+ to the database:
68
+
69
+ DB = Sequel.connect(..., :servers_hash=>Hash.new(:some_shard))
70
+
71
+ You can also use this feature to raise an exception if an
72
+ unconfigured shard is used:
73
+
74
+ DB = Sequel.connect(..., :servers_hash=>Hash.new{raise ...})
75
+
76
+ * The mysql and mysql2 adapters now both support the :read_timeout
77
+ and :connect_timeout options. read_timeout is the timeout in
78
+ seconds for reading back results of a query, and connect_timeout
79
+ is the timeout in seconds before a connection attempt is abandoned.
80
+
81
+ = Other Improvements
82
+
83
+ * The json_serializer plugin will now typecast column values for
84
+ columns with unrestricted setter methods when parsing JSON into
85
+ model objects. It now also calls the getter method when creating
86
+ the JSON, instead of directly taking the values from the underlying
87
+ hash.
88
+
89
+ * When parsing the schema for a model with an aliased table name,
90
+ the unaliased table name is now used.
91
+
92
+ * The SQLite adapter has been updated to not rely on the native
93
+ type_translation support, since that will be removed in the next
94
+ major version of sqlite3-ruby. Sequel now implements it's own
95
+ type translation in the sqlite adapter, similarly to how the mysql
96
+ and postgres adapters handle type translation.
97
+
98
+ * On SQLite, when emulating natively unsupported schema methods such
99
+ as drop_column, Sequel will now attempt to recreate applicable
100
+ indexes on the underlying table.
101
+
102
+ * A more informative error message is now used when connecting fails
103
+ when using the jdbc adapter.
104
+
105
+ * method_missing is no longer removed from Sequel::BasicObject on
106
+ ruby 1.8. This should improve compatibility in some cases with
107
+ Rubinius.
108
+
109
+ = Backwards Compatibility
110
+
111
+ * On SQLite, Sequel no longer assumes that a plain integer in a
112
+ datetime or timestamp field represents a unix epoch time.
113
+
114
+ * Previously, saving a model object that used the instance_hooks
115
+ plugin removed all instance hooks. Now, only the applicable hooks
116
+ are removed. So if you save a new object, the update instance
117
+ hooks won't be removed. And if you save an existing object, delete
118
+ instance hooks won't be removed.
119
+
120
+ * The private Dataset#identifier_list method has been moved into the
121
+ SQLite adapter, since that is the only place it was used.
@@ -168,7 +168,12 @@ module Sequel
168
168
  props.setProperty("user", opts[:user])
169
169
  props.setProperty("password", opts[:password])
170
170
  end
171
- driver.new.connect(args[0], props) rescue (raise e)
171
+ begin
172
+ driver.new.connect(args[0], props)
173
+ rescue => e2
174
+ e.message << "\n#{e2.class.name}: #{e2.message}"
175
+ raise e
176
+ end
172
177
  end
173
178
  end
174
179
  setup_connection(conn)
@@ -83,11 +83,15 @@ module Sequel
83
83
  # the MySQL config file.
84
84
  # * :config_local_infile - If provided, sets the Mysql::OPT_LOCAL_INFILE
85
85
  # option on the connection with the given value.
86
+ # * :connect_timeout - Set the timeout in seconds before a connection
87
+ # attempt is abandoned.
86
88
  # * :encoding - Set all the related character sets for this
87
89
  # connection (connection, client, database, server, and results).
90
+ # * :read_timeout - Set the timeout in seconds for reading back results
91
+ # to a query.
88
92
  # * :socket - Use a unix socket file instead of connecting via TCP/IP.
89
93
  # * :timeout - Set the timeout in seconds before the server will
90
- # disconnect this connection.
94
+ # disconnect this connection (a.k.a @@wait_timeout).
91
95
  def connect(server)
92
96
  opts = server_opts(server)
93
97
  conn = Mysql.init
@@ -99,6 +103,12 @@ module Sequel
99
103
  # encoding we want to use, but this can be overridden by READ_DEFAULT_GROUP.
100
104
  conn.options(Mysql::SET_CHARSET_NAME, encoding)
101
105
  end
106
+ if read_timeout = opts[:read_timeout] and Mysql::const_defined(Mysql::OPT_READ_TIMEOUT)
107
+ conn.options(Mysql::OPT_READ_TIMEOUT, read_timeout)
108
+ end
109
+ if connect_timeout = opts[:connect_timeout] and Mysql::const_defined(Mysql::OPT_CONNECT_TIMEOUT)
110
+ conn.options(Mysql::OPT_CONNECT_TIMEOUT, connect_timeout)
111
+ end
102
112
  conn.real_connect(
103
113
  opts[:host] || 'localhost',
104
114
  opts[:user],
@@ -26,11 +26,13 @@ module Sequel
26
26
  # the MySQL config file.
27
27
  # * :config_local_infile - If provided, sets the Mysql::OPT_LOCAL_INFILE
28
28
  # option on the connection with the given value.
29
+ # * :connect_timeout - Set the timeout in seconds before a connection
30
+ # attempt is abandoned.
29
31
  # * :encoding - Set all the related character sets for this
30
32
  # connection (connection, client, database, server, and results).
31
33
  # * :socket - Use a unix socket file instead of connecting via TCP/IP.
32
34
  # * :timeout - Set the timeout in seconds before the server will
33
- # disconnect this connection.
35
+ # disconnect this connection (a.k.a. @@wait_timeout).
34
36
  def connect(server)
35
37
  opts = server_opts(server)
36
38
  opts[:host] ||= 'localhost'
@@ -93,7 +93,7 @@ module Sequel
93
93
 
94
94
  # Use a single proc for each type to conserve memory
95
95
  PG_TYPE_PROCS = {
96
- [16] => lambda{|s| s == 't'}, # boolean
96
+ [16] => lambda{|s| s == 't'}, # boolean
97
97
  [17] => lambda{|s| ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s))}, # bytea
98
98
  [20, 21, 22, 23, 26] => lambda{|s| s.to_i}, # integer
99
99
  [700, 701] => lambda{|s| s.to_f}, # float
@@ -237,12 +237,18 @@ module Sequel
237
237
 
238
238
  qt = quote_schema_table(table)
239
239
  bt = quote_identifier(backup_table_name(qt))
240
- [
240
+ a = [
241
241
  "CREATE TABLE #{bt}(#{def_columns_str})",
242
242
  "INSERT INTO #{bt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{qt}",
243
243
  "DROP TABLE #{qt}",
244
244
  "ALTER TABLE #{bt} RENAME TO #{qt}"
245
245
  ]
246
+ indexes(table).each do |name, h|
247
+ if (h[:columns].map{|x| x.to_s} - new_columns).empty?
248
+ a << alter_table_sql(table, h.merge(:op=>:add_index, :name=>name))
249
+ end
250
+ end
251
+ a
246
252
  end
247
253
 
248
254
  # SQLite folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
@@ -312,6 +318,7 @@ module Sequel
312
318
  # Instance methods for datasets that connect to an SQLite database
313
319
  module DatasetMethods
314
320
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
321
+ COMMA_SEPARATOR = ', '.freeze
315
322
  CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
316
323
 
317
324
  # SQLite does not support pattern matching via regular expressions.
@@ -388,6 +395,11 @@ module Sequel
388
395
  "#{expression} AS #{literal(aliaz.to_s)}"
389
396
  end
390
397
 
398
+ # SQL fragment specifying a list of identifiers
399
+ def identifier_list(columns)
400
+ columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
401
+ end
402
+
391
403
  # SQLite uses a preceding X for hex escaping strings
392
404
  def literal_blob(v)
393
405
  blob = ''
@@ -10,6 +10,23 @@ module Sequel
10
10
  # Top level module for holding all SQLite-related modules and classes
11
11
  # for Sequel.
12
12
  module SQLite
13
+ UNIX_EPOCH_TIME_FORMAT = /\A\d+\z/.freeze
14
+ SQLITE_TYPES = {}
15
+ FALSE_VALUES = %w'0 false f no n'.freeze
16
+ SQLITE_TYPE_PROCS = {
17
+ %w'timestamp datetime' => proc{|v| Sequel.database_to_application_timestamp(v)},
18
+ %w'date' => proc{|v| Sequel.string_to_date(v)},
19
+ %w'time' => proc{|v| Sequel.string_to_time(v)},
20
+ %w'bit bool boolean' => proc{|v| !FALSE_VALUES.include?(v.downcase)},
21
+ %w'integer smallint mediumint int bigint' => proc{|v| Integer(v) rescue v},
22
+ %w'numeric decimal money' => proc{|v| BigDecimal.new(v) rescue v},
23
+ %w'float double real dec fixed' + ['double precision'] => proc{|v| Float(v) rescue v},
24
+ %w'blob' => proc{|v| ::Sequel::SQL::Blob.new(v)}
25
+ }
26
+ SQLITE_TYPE_PROCS.each do |k,v|
27
+ k.each{|n| SQLITE_TYPES[n] = v}
28
+ end
29
+
13
30
  # Database class for SQLite databases used with Sequel and the
14
31
  # ruby-sqlite3 driver.
15
32
  class Database < Sequel::Database
@@ -35,33 +52,9 @@ module Sequel
35
52
  opts[:database] = ':memory:' if blank_object?(opts[:database])
36
53
  db = ::SQLite3::Database.new(opts[:database])
37
54
  db.busy_timeout(opts.fetch(:timeout, 5000))
38
- db.type_translation = true
39
55
 
40
56
  connection_pragmas.each{|s| log_yield(s){db.execute_batch(s)}}
41
57
 
42
- # Handle datetimes with Sequel.datetime_class
43
- prok = proc do |t,v|
44
- v = Time.at(v.to_i).iso8601 if UNIX_EPOCH_TIME_FORMAT.match(v)
45
- Sequel.database_to_application_timestamp(v)
46
- end
47
- db.translator.add_translator("timestamp", &prok)
48
- db.translator.add_translator("datetime", &prok)
49
-
50
- # Handle numeric values with BigDecimal
51
- prok = proc{|t,v| BigDecimal.new(v) rescue v}
52
- db.translator.add_translator("numeric", &prok)
53
- db.translator.add_translator("decimal", &prok)
54
- db.translator.add_translator("money", &prok)
55
-
56
- # Handle floating point values with Float
57
- prok = proc{|t,v| Float(v) rescue v}
58
- db.translator.add_translator("float", &prok)
59
- db.translator.add_translator("real", &prok)
60
- db.translator.add_translator("double precision", &prok)
61
-
62
- # Handle blob values with Sequel::SQL::Blob
63
- db.translator.add_translator("blob"){|t,v| ::Sequel::SQL::Blob.new(v)}
64
-
65
58
  class << db
66
59
  attr_reader :prepared_statements
67
60
  end
@@ -271,11 +264,18 @@ module Sequel
271
264
  def fetch_rows(sql)
272
265
  execute(sql) do |result|
273
266
  i = -1
274
- cols = result.columns.map{|c| [output_identifier(c), i+=1]}
267
+ type_procs = result.types.map{|t| SQLITE_TYPES[base_type_name(t)]}
268
+ cols = result.columns.map{|c| i+=1; [output_identifier(c), i, type_procs[i]]}
275
269
  @columns = cols.map{|c| c.first}
276
270
  result.each do |values|
277
271
  row = {}
278
- cols.each{|n,i| row[n] = values[i]}
272
+ cols.each do |name,i,type_proc|
273
+ v = values[i]
274
+ if type_proc && v.is_a?(String)
275
+ v = type_proc.call(v)
276
+ end
277
+ row[name] = v
278
+ end
279
279
  yield row
280
280
  end
281
281
  end
@@ -297,6 +297,11 @@ module Sequel
297
297
 
298
298
  private
299
299
 
300
+ # The base type name for a given type, without any parenthetical part.
301
+ def base_type_name(t)
302
+ (t =~ /^(.*?)\(/ ? $1 : t).downcase if t
303
+ end
304
+
300
305
  # Quote the string using the adapter class method.
301
306
  def literal_string(v)
302
307
  "'#{::SQLite3::Database.quote(v)}'"
@@ -8,10 +8,14 @@ class Sequel::ShardedSingleConnectionPool < Sequel::ConnectionPool
8
8
  # * :servers - A hash of servers to use. Keys should be symbols. If not
9
9
  # present, will use a single :default server. The server name symbol will
10
10
  # be passed to the connection_proc.
11
+ # * :servers_hash - The base hash to use for the servers. By default,
12
+ # Sequel uses Hash.new(:default). You can use a hash with a default proc
13
+ # that raises an error if you want to catch all cases where a nonexistent
14
+ # server is used.
11
15
  def initialize(opts={}, &block)
12
16
  super
13
17
  @conns = {}
14
- @servers = Hash.new(:default)
18
+ @servers = opts.fetch(:servers_hash, Hash.new(:default))
15
19
  add_servers([:default])
16
20
  add_servers(opts[:servers].keys) if opts[:servers]
17
21
  end
@@ -10,11 +10,15 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
10
10
  # * :servers - A hash of servers to use. Keys should be symbols. If not
11
11
  # present, will use a single :default server. The server name symbol will
12
12
  # be passed to the connection_proc.
13
+ # * :servers_hash - The base hash to use for the servers. By default,
14
+ # Sequel uses Hash.new(:default). You can use a hash with a default proc
15
+ # that raises an error if you want to catch all cases where a nonexistent
16
+ # server is used.
13
17
  def initialize(opts = {}, &block)
14
18
  super
15
19
  @available_connections = {}
16
20
  @connections_to_remove = []
17
- @servers = Hash.new(:default)
21
+ @servers = opts.fetch(:servers_hash, Hash.new(:default))
18
22
  add_servers([:default])
19
23
  add_servers(opts[:servers].keys) if opts[:servers]
20
24
  end
@@ -600,11 +600,6 @@ module Sequel
600
600
  sprintf(".%06d", usec)
601
601
  end
602
602
 
603
- # SQL fragment specifying a list of identifiers
604
- def identifier_list(columns)
605
- columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
606
- end
607
-
608
603
  # Modify the identifier returned from the database based on the
609
604
  # identifier_output_method.
610
605
  def input_identifier(v)
@@ -80,7 +80,7 @@ module Sequel
80
80
 
81
81
  # Internal class used by the Sequel.migration DSL, part of the +migration+ extension.
82
82
  class MigrationDSL < BasicObject
83
- # The underlying Migration class.
83
+ # The underlying Migration instance
84
84
  attr_reader :migration
85
85
 
86
86
  def self.create(&block)
@@ -103,6 +103,122 @@ module Sequel
103
103
  def up(&block)
104
104
  migration.up = block
105
105
  end
106
+
107
+ # Creates a reversible migration. This is the same as creating
108
+ # the same block with +up+, but it also calls the block and attempts
109
+ # to create a +down+ block that will reverse the changes made by
110
+ # the block.
111
+ #
112
+ # There are no guarantees that this will work perfectly
113
+ # in all cases, but it should work for most common cases.
114
+ def change(&block)
115
+ migration.up = block
116
+ migration.down = MigrationReverser.new.reverse(&block)
117
+ end
118
+ end
119
+
120
+ # Handles the reversing of reversible migrations. Basically records
121
+ # supported methods calls, translates them to reversed calls, and
122
+ # returns them in reverse order.
123
+ class MigrationReverser < Sequel::BasicObject
124
+ def initialize
125
+ @actions = []
126
+ end
127
+
128
+ # Reverse the actions for the given block. Takes the block given
129
+ # and returns a new block that reverses the actions taken by
130
+ # the given block.
131
+ def reverse(&block)
132
+ begin
133
+ instance_eval(&block)
134
+ rescue
135
+ just_raise = true
136
+ end
137
+ if just_raise
138
+ Proc.new{raise Sequel::Error, 'irreversible migration method used, you may need to write your own down method'}
139
+ else
140
+ actions = @actions.reverse
141
+ Proc.new do
142
+ actions.each do |a|
143
+ if a.last.is_a?(Proc)
144
+ pr = a.pop
145
+ send(*a, &pr)
146
+ else
147
+ send(*a)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ def add_column(*args)
157
+ @actions << [:drop_column, args[0], args[1]]
158
+ end
159
+
160
+ def add_index(*args)
161
+ @actions << [:drop_index, *args]
162
+ end
163
+
164
+ def alter_table(table, &block)
165
+ @actions << [:alter_table, table, MigrationAlterTableReverser.new.reverse(&block)]
166
+ end
167
+
168
+ def create_table(*args)
169
+ @actions << [:drop_table, args.first]
170
+ end
171
+
172
+ def create_view(*args)
173
+ @actions << [:drop_view, args.first]
174
+ end
175
+
176
+ def rename_column(table, name, new_name)
177
+ @actions << [:rename_column, table, new_name, name]
178
+ end
179
+
180
+ def rename_table(table, new_name)
181
+ @actions << [:rename_table, new_name, table]
182
+ end
183
+ end
184
+
185
+ # Handles reversing an alter_table block in a reversible migration.
186
+ class MigrationAlterTableReverser < Sequel::BasicObject
187
+ def initialize
188
+ @actions = []
189
+ end
190
+
191
+ def reverse(&block)
192
+ instance_eval(&block)
193
+ actions = @actions.reverse
194
+ Proc.new{actions.each{|a| send(*a)}}
195
+ end
196
+
197
+ private
198
+
199
+ def add_column(*args)
200
+ @actions << [:drop_column, args.first]
201
+ end
202
+
203
+ def add_constraint(*args)
204
+ @actions << [:drop_constraint, args.first]
205
+ end
206
+
207
+ def add_foreign_key(*args)
208
+ raise if args.first.is_a?(Array)
209
+ @actions << [:drop_column, args.first]
210
+ end
211
+ alias add_primary_key add_foreign_key
212
+
213
+ def add_index(*args)
214
+ @actions << [:drop_index, *args]
215
+ end
216
+ alias add_full_text_index add_index
217
+ alias add_spatial_index add_index
218
+
219
+ def rename_column(name, new_name)
220
+ @actions << [:rename_column, new_name, name]
221
+ end
106
222
  end
107
223
 
108
224
  # The preferred method for writing Sequel migrations, using a DSL: