sequel 3.17.0 → 3.18.0

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 (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: