wineskins 0.2.2
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.
- data/.gitignore +9 -0
- data/LICENSE.txt +18 -0
- data/README.markdown +235 -0
- data/Rakefile +7 -0
- data/bin/wskins +5 -0
- data/lib/wineskins.rb +269 -0
- data/lib/wineskins/record_methods.rb +25 -0
- data/lib/wineskins/runner.rb +132 -0
- data/lib/wineskins/schema_methods.rb +100 -0
- data/lib/wineskins/transcript.rb +24 -0
- data/lib/wineskins/utils.rb +19 -0
- data/lib/wineskins/version.rb +3 -0
- data/lib/wineskins_cli.rb +3 -0
- data/test/helper.rb +128 -0
- data/test/helpers/transfer_assertions.rb +102 -0
- data/test/suite.rb +3 -0
- data/test/test_transfer_callbacks.rb +52 -0
- data/test/test_transfer_run_table.rb +172 -0
- data/test/test_transfer_run_tables.rb +131 -0
- data/todo.yml +10 -0
- data/wineskins.gemspec +25 -0
- metadata +98 -0
data/.gitignore
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2012 Eric Gjertsen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
# Wineskins
|
2
|
+
|
3
|
+
A Ruby database transfer utility built on [Sequel](http://sequel.rubyforge.org/).
|
4
|
+
|
5
|
+
Sometimes your old wine needs to be poured into new skins too.
|
6
|
+
|
7
|
+
## Basic usage
|
8
|
+
|
9
|
+
From the command line, the utility runs __transfer instructions__ for a specified
|
10
|
+
__source__ and __destination__ database. By default, it looks for these instructions
|
11
|
+
in the file `./transfer.rb`. (The syntax of these instructions will be
|
12
|
+
described in a moment.) So the easiest way to execute the utility is:
|
13
|
+
|
14
|
+
wskins some-db://source/url some-other-db://dest/url
|
15
|
+
|
16
|
+
This will run the instructions in ./transfer.rb to transfer the schema and/or
|
17
|
+
data from the specified source database (as a URL recognized by
|
18
|
+
`Sequel.connect`), to the specified destination database.
|
19
|
+
|
20
|
+
You can specify another transfer instructions file via `--config`:
|
21
|
+
|
22
|
+
wskins --config path/to/transfer.rb some-db://source/url some-other-db://dest/url
|
23
|
+
|
24
|
+
If your databases can't be opened easily via a URL, instead you can manually set
|
25
|
+
the constants `SOURCE_DB` and `DEST_DB` to your Sequel databases within a ruby
|
26
|
+
script, and require that script from the command line like this:
|
27
|
+
|
28
|
+
wskins --require path/to/db/setup.rb
|
29
|
+
|
30
|
+
(This is necessary for instance if you are using ADO adapters.)
|
31
|
+
|
32
|
+
Run `wskins --help` to see complete usage options.
|
33
|
+
|
34
|
+
## How to install
|
35
|
+
|
36
|
+
[Get Ruby](http://www.ruby-lang.org/en/downloads/) if you don't have it.
|
37
|
+
|
38
|
+
Then
|
39
|
+
|
40
|
+
gem install wineskins
|
41
|
+
|
42
|
+
This will also install the sequel gem. If Sequel needs native drivers for your
|
43
|
+
database(s), install them separately. Consult the
|
44
|
+
[Sequel docs](http://sequel.rubyforge.org/) for more info.
|
45
|
+
|
46
|
+
## Transfer instructions syntax
|
47
|
+
|
48
|
+
### A very simple case:
|
49
|
+
|
50
|
+
tables :students, :classes, :enrollments
|
51
|
+
|
52
|
+
This will copy the tables, indexes, and foreign key constraints, and then insert
|
53
|
+
all the records, for the three listed tables.
|
54
|
+
|
55
|
+
### To rename tables:
|
56
|
+
|
57
|
+
tables :students, [:classes, :courses], :enrollments
|
58
|
+
|
59
|
+
The `classes` table in the source will be renamed `courses` in the destination.
|
60
|
+
All foreign keys referencing `classes` (in e.g. the `enrollments` table) will be
|
61
|
+
changed accordingly.
|
62
|
+
|
63
|
+
If you are copying records, be careful in the order you list the tables -- this
|
64
|
+
is the order that the records will be inserted. If you have foreign key
|
65
|
+
constraints between two tables, you must list the target (primary key) table
|
66
|
+
first to ensure that the keys exist before inserting the foreign key table
|
67
|
+
records.
|
68
|
+
|
69
|
+
So in this example, the `:students` and `:classes` tables are transferred _before_
|
70
|
+
the `:enrollments` table, which presumably has foreign keys to `:students` and
|
71
|
+
`:classes`.
|
72
|
+
|
73
|
+
### To rename fields:
|
74
|
+
|
75
|
+
table :classes, :rename => {:class_id => :id}
|
76
|
+
|
77
|
+
Primary keys, indexes, foreign keys will be changed accordingly in the
|
78
|
+
destination database.
|
79
|
+
|
80
|
+
### Want to copy the schema, but not import data yet?
|
81
|
+
|
82
|
+
tables :students, :classes, :enrollments, :schema_only => true
|
83
|
+
|
84
|
+
### Have the schema in place, just need to import data?
|
85
|
+
|
86
|
+
tables :students, :classes, :enrollments, :records_only => true
|
87
|
+
|
88
|
+
You have finer-grained control as well:
|
89
|
+
|
90
|
+
table :students, :create_tables => true,
|
91
|
+
:create_indexes => false,
|
92
|
+
:create_fk_constraints => false,
|
93
|
+
:insert_records => true
|
94
|
+
|
95
|
+
### Adjusting column definitions
|
96
|
+
|
97
|
+
Sometimes you need to manually adjust column types or other options in the
|
98
|
+
destination: for example due to different conventions between databases. You can
|
99
|
+
pass through column definitions to Sequel's schema generator, and they will be
|
100
|
+
used _instead of_ the source database table:
|
101
|
+
|
102
|
+
table :classes do
|
103
|
+
column :slots, :integer, :null => false, :default => 25
|
104
|
+
end
|
105
|
+
|
106
|
+
Note that in this example, all of the column definitions _except `slots`_ will
|
107
|
+
be copied from the source table, while `slots` will be defined as specified.
|
108
|
+
|
109
|
+
### Excluding and including columns
|
110
|
+
|
111
|
+
(_Note: not yet implemented._)
|
112
|
+
You can also exclude specific columns entirely, or include only specified columns:
|
113
|
+
|
114
|
+
table :enrollments, :exclude => [:final_grade, :status]
|
115
|
+
table :students, :include => [:id, :name, :grad_year]
|
116
|
+
|
117
|
+
Although it's nearly as easy to do this manually in a hook (see below).
|
118
|
+
|
119
|
+
### Limiting the imported data
|
120
|
+
|
121
|
+
(_Note: not yet implemented._)
|
122
|
+
It's also possible to specify a filter on the source records that get imported.
|
123
|
+
|
124
|
+
table :students do
|
125
|
+
insert_records :grad_year => (2010..2012)
|
126
|
+
end
|
127
|
+
|
128
|
+
Filters can be anything that Sequel accepts as arguments to `Dataset#filter`.
|
129
|
+
|
130
|
+
### Generating a transcript
|
131
|
+
|
132
|
+
If you just want a script for generating the schema later, and don't actually
|
133
|
+
want to make database changes, do something like this:
|
134
|
+
|
135
|
+
transcript 'path/to/transfer.sql'
|
136
|
+
tables :schema_only => true
|
137
|
+
|
138
|
+
and include the `--dry-run` option on the command line.
|
139
|
+
|
140
|
+
### Hooks for manual futzing
|
141
|
+
|
142
|
+
Wineskins executes a given transfer in four stages:
|
143
|
+
|
144
|
+
1. All the tables are created (`:create_table`)
|
145
|
+
2. All the indexes are created via `alter_table` (`:create_indexes`)
|
146
|
+
3. All the foreign key constraints are created via `alter_table` (`:create_fk_constraints`)
|
147
|
+
4. The records are inserted into each table from the source database (`:insert_records`)
|
148
|
+
|
149
|
+
Each of these stages has a `before_*` and `after_*` hook where you can stick
|
150
|
+
whatever custom steps you need using Sequel's incredibly wide toolset, and there
|
151
|
+
are also general `before` and `after` hooks that run before and after the entire
|
152
|
+
transfer. You can define as many of these as you want at each hook. For
|
153
|
+
instance (to turn off foreign key constraints before inserting records):
|
154
|
+
|
155
|
+
before_insert_records do
|
156
|
+
dest.pragma_set 'foreign_keys', 'off'
|
157
|
+
end
|
158
|
+
|
159
|
+
Or to take the example above of excluding columns, you could do this manually
|
160
|
+
in a callback like:
|
161
|
+
|
162
|
+
after_create_tables do
|
163
|
+
dest[:enrollments].alter_table do
|
164
|
+
drop_column :final_grade
|
165
|
+
drop_column :status
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
Within callbacks, the source database is referenced via `source`, the
|
170
|
+
destination database via `dest`.
|
171
|
+
|
172
|
+
### A note on the syntax
|
173
|
+
|
174
|
+
In the examples above I've used both a 'hash-options' style and a block syntax.
|
175
|
+
Either can be used interchangably and even in combination if you want (although
|
176
|
+
it's ugly looking). The options set in the block always override the options
|
177
|
+
hash. Also, note that custom `column` definitions must be done within a block.
|
178
|
+
|
179
|
+
## Motivations
|
180
|
+
|
181
|
+
This tool aims to simplify transferring data between databases, and is designed
|
182
|
+
around the canonical case where the destination database is completely empty,
|
183
|
+
and you want to set up everything the way it is in the source and then import
|
184
|
+
the data. Of course, many other scenarios are possible, but the point is that
|
185
|
+
the only things you should need to specify are either (1) differences from this
|
186
|
+
scenario, or (2) differences between database adapters that Sequel cannot
|
187
|
+
handle automatically.
|
188
|
+
|
189
|
+
Note that accordingly, if schema or records already exist in your destination,
|
190
|
+
you are responsible for dealing with this in whatever way makes sense for your
|
191
|
+
scenario. No tables, indexes, constraints, or records are automatically deleted
|
192
|
+
in the destination.
|
193
|
+
|
194
|
+
So, you might want to wipe out and replace what exists (via `drop_table`,
|
195
|
+
`dest[:table].delete`, etc. in callbacks); or you might want to keep what
|
196
|
+
exists (omitting changes via `:schema_only`, `:records_only` options, etc.); or
|
197
|
+
you might want to alter what exists (via custom `alter_table`,
|
198
|
+
`dest[:table].filter(some_filter).delete`, etc. in callbacks).
|
199
|
+
|
200
|
+
The principle is that _as much as possible, the source database should determine
|
201
|
+
the schema of the destination database_, thus minimizing manually-entered (and
|
202
|
+
possibly incorrect) schema definition code. Also it helps avoid, for simple but
|
203
|
+
typical cases, the great pain and knashing of the teeth involved in massaging
|
204
|
+
the source data into the right format for for importing.
|
205
|
+
|
206
|
+
## Alternatives / Similar projects
|
207
|
+
|
208
|
+
- Sequel's [schema dumper extension](http://sequel.rubyforge.org/rdoc-plugins/files/lib/sequel/extensions/schema_dumper_rb.html) lets you dump and load schema using Sequel's migration DSL.
|
209
|
+
- [DbCopier](https://github.com/santosh79/db-copier), apparently unmaintained?
|
210
|
+
- [Linkage](https://github.com/coupler/linkage) mimics joins between tables in
|
211
|
+
different databases.
|
212
|
+
|
213
|
+
## Please help
|
214
|
+
|
215
|
+
This is a young young project, don't expect it will work out of the box without
|
216
|
+
some futzing. It's only been formally tested on Sqlite to Sqlite transfers, and
|
217
|
+
ad-hoc tested on a 'real' MS Access to Sqlite transfer.
|
218
|
+
|
219
|
+
If you start using it and run into weird shit, at the very least let me know
|
220
|
+
about it. Better still if you send some informed guesses as to what's going on.
|
221
|
+
Pull requests are awesome and going the extra mile and all that... but before
|
222
|
+
you go to the trouble, unless it's a really minor fix, let me know about the
|
223
|
+
issue, I might be able to save you some time and we can have a conversation
|
224
|
+
about it you know?
|
225
|
+
|
226
|
+
There's a TODO list in the project root if you want to see where I'm thinking
|
227
|
+
of heading, comments welcome.
|
228
|
+
|
229
|
+
|
230
|
+
## Requirements
|
231
|
+
|
232
|
+
- ruby >= 1.8.7
|
233
|
+
- sequel ~> 3.0 (note >= 3.39 needed for MS Access source databases)
|
234
|
+
- progressbar (optional)
|
235
|
+
|
data/Rakefile
ADDED
data/bin/wskins
ADDED
data/lib/wineskins.rb
ADDED
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
# Modification of ADO/Access adapter to get schema info
|
4
|
+
# incorporated into Sequel itself v3.39.0
|
5
|
+
# require File.expand_path('sequel_ext/adapters/shared/access', File.dirname(__FILE__))
|
6
|
+
|
7
|
+
require File.expand_path('wineskins/version', File.dirname(__FILE__))
|
8
|
+
require File.expand_path('wineskins/utils', File.dirname(__FILE__))
|
9
|
+
require File.expand_path('wineskins/transcript', File.dirname(__FILE__))
|
10
|
+
require File.expand_path('wineskins/schema_methods', File.dirname(__FILE__))
|
11
|
+
require File.expand_path('wineskins/record_methods', File.dirname(__FILE__))
|
12
|
+
|
13
|
+
module Wineskins
|
14
|
+
|
15
|
+
def self.transfer(source, dest, opts={}, &block)
|
16
|
+
Transfer.new(source, dest, &block).run(opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
class Transfer
|
20
|
+
include SchemaMethods
|
21
|
+
include RecordMethods
|
22
|
+
|
23
|
+
attr_accessor :source, :dest
|
24
|
+
attr_reader :table_defs, :progressbar
|
25
|
+
attr_reader :before_hooks, :after_hooks
|
26
|
+
|
27
|
+
def initialize(source, dest, &block)
|
28
|
+
self.source = source
|
29
|
+
self.dest = dest
|
30
|
+
@table_defs = []
|
31
|
+
@before_hooks = Hash.new{|h,k|h[k]=[]}
|
32
|
+
@after_hooks = Hash.new{|h,k|h[k]=[]}
|
33
|
+
self.define(&block) if block_given?
|
34
|
+
end
|
35
|
+
|
36
|
+
def define(&block)
|
37
|
+
instance_eval(&block)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def run(opts={})
|
42
|
+
rollback = (opts[:dryrun] ? :always : nil)
|
43
|
+
dest.transaction(:rollback => rollback) do
|
44
|
+
trigger_before_hooks
|
45
|
+
trigger_before_hooks :create_tables
|
46
|
+
create_tables!
|
47
|
+
trigger_after_hooks :create_tables
|
48
|
+
trigger_before_hooks :create_indexes
|
49
|
+
create_indexes!
|
50
|
+
trigger_after_hooks :create_indexes
|
51
|
+
trigger_before_hooks :create_fk_constraints
|
52
|
+
create_fk_constraints!
|
53
|
+
trigger_after_hooks :create_fk_constraints
|
54
|
+
trigger_before_hooks :insert_records
|
55
|
+
insert_records!
|
56
|
+
trigger_after_hooks :insert_records
|
57
|
+
trigger_after_hooks
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def transcript(file=nil)
|
62
|
+
self.dest.loggers << Transcript.new(file)
|
63
|
+
end
|
64
|
+
|
65
|
+
def tables(*args)
|
66
|
+
opts = (Hash === args.last ? args.pop : {})
|
67
|
+
tbls = (args.empty? ? self.source.tables : args)
|
68
|
+
tbls.each do |tbl| table(tbl, opts) end
|
69
|
+
end
|
70
|
+
|
71
|
+
def table(name, opts={}, &block)
|
72
|
+
@table_defs << Table.new(name, opts, &block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def before(event=nil, &cb)
|
76
|
+
before_hooks[event] << cb
|
77
|
+
end
|
78
|
+
|
79
|
+
def after(event=nil, &cb)
|
80
|
+
after_hooks[event] << cb
|
81
|
+
end
|
82
|
+
|
83
|
+
[:before,
|
84
|
+
:after
|
85
|
+
].product([
|
86
|
+
:create_tables,
|
87
|
+
:create_indexes,
|
88
|
+
:create_fk_constraints,
|
89
|
+
:insert_records
|
90
|
+
]).each do |(hook, event)|
|
91
|
+
define_method("#{hook}_#{event}") do |&block|
|
92
|
+
send hook, event, &block
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def set_progressbar(title, total)
|
97
|
+
require 'progressbar'
|
98
|
+
@progressbar = ProgressBar.new(title, total)
|
99
|
+
rescue LoadError
|
100
|
+
@progressbar = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def create_tables!
|
106
|
+
@table_defs.select {|t| t.create_table?}.each do |table|
|
107
|
+
transfer_table table
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_indexes!
|
112
|
+
@table_defs.select {|t| t.create_indexes?}.each do |table|
|
113
|
+
transfer_indexes table
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def create_fk_constraints!
|
118
|
+
@table_defs.select {|t| t.create_fk_constraints?}.each do |table|
|
119
|
+
transfer_fk_constraints table, table_rename
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def insert_records!
|
124
|
+
@table_defs.select {|t| t.insert_records?}.each do |table|
|
125
|
+
transfer_records table
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# used in create_fk_constraints
|
130
|
+
def table_rename
|
131
|
+
@table_defs.inject({}) do |memo, table|
|
132
|
+
memo[table.source_name] = table.dest_name
|
133
|
+
memo
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def trigger_before_hooks(event=nil)
|
138
|
+
before_hooks[event].each do |cb|
|
139
|
+
cb.call
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def trigger_after_hooks(event=nil)
|
144
|
+
after_hooks[event].each do |cb|
|
145
|
+
cb.call
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
# data structure for table transfer definition
|
152
|
+
class Table
|
153
|
+
|
154
|
+
attr_accessor :source_name, :dest_name, :dest_columns
|
155
|
+
attr_accessor :include,
|
156
|
+
:exclude,
|
157
|
+
:rename,
|
158
|
+
:create_table,
|
159
|
+
:create_indexes,
|
160
|
+
:create_fk_constraints,
|
161
|
+
:insert_records
|
162
|
+
|
163
|
+
def initialize(name, opts={}, &block)
|
164
|
+
self.source_name, self.dest_name = Array(name)
|
165
|
+
self.dest_name ||= self.source_name
|
166
|
+
self.dest_columns = {}
|
167
|
+
Builder.new(self, default_opts.merge(opts), &block)
|
168
|
+
end
|
169
|
+
|
170
|
+
def create_table?
|
171
|
+
!!create_table
|
172
|
+
end
|
173
|
+
|
174
|
+
def create_indexes?
|
175
|
+
!!create_indexes
|
176
|
+
end
|
177
|
+
|
178
|
+
def create_fk_constraints?
|
179
|
+
!!create_fk_constraints
|
180
|
+
end
|
181
|
+
|
182
|
+
def insert_records?
|
183
|
+
!!insert_records
|
184
|
+
end
|
185
|
+
|
186
|
+
# todo: handle Proc or Regex === rename
|
187
|
+
def rename_map(cols)
|
188
|
+
col_map = cols.inject({}) {|m,c| m[c]=c;m}
|
189
|
+
col_map.merge(rename)
|
190
|
+
end
|
191
|
+
|
192
|
+
def default_opts
|
193
|
+
{ include: nil,
|
194
|
+
exclude: [],
|
195
|
+
rename: {},
|
196
|
+
create_table: true,
|
197
|
+
create_indexes: true,
|
198
|
+
create_fk_constraints: true,
|
199
|
+
insert_records: true
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
class Builder
|
204
|
+
|
205
|
+
def initialize(target, opts={}, &block)
|
206
|
+
@target = target
|
207
|
+
set_options opts
|
208
|
+
instance_eval(&block) if block_given?
|
209
|
+
end
|
210
|
+
|
211
|
+
def include(flds)
|
212
|
+
@target.include = flds
|
213
|
+
end
|
214
|
+
|
215
|
+
def exclude(flds)
|
216
|
+
@target.exclude = flds
|
217
|
+
end
|
218
|
+
|
219
|
+
def rename(fldmap=nil, &block)
|
220
|
+
@target.rename = fldmap || block
|
221
|
+
end
|
222
|
+
|
223
|
+
def column(name, *args)
|
224
|
+
@target.dest_columns[name] = args
|
225
|
+
end
|
226
|
+
|
227
|
+
def create_table(bool=true)
|
228
|
+
@target.create_table = bool
|
229
|
+
end
|
230
|
+
|
231
|
+
def create_indexes(bool=true)
|
232
|
+
@target.create_indexes = bool
|
233
|
+
end
|
234
|
+
|
235
|
+
def create_fk_constraints(bool=true)
|
236
|
+
@target.create_fk_constraints = bool
|
237
|
+
end
|
238
|
+
|
239
|
+
def insert_records(bool=true)
|
240
|
+
@target.insert_records = bool
|
241
|
+
end
|
242
|
+
|
243
|
+
def schema_only
|
244
|
+
create_table; create_indexes; create_fk_constraints
|
245
|
+
insert_records false
|
246
|
+
end
|
247
|
+
|
248
|
+
def records_only
|
249
|
+
create_table(false); create_indexes(false); create_fk_constraints(false)
|
250
|
+
insert_records
|
251
|
+
end
|
252
|
+
|
253
|
+
def set_options(opts)
|
254
|
+
[:include, :exclude, :rename,
|
255
|
+
:create_table, :create_indexes, :create_fk_constraints, :insert_records,
|
256
|
+
].each do |opt|
|
257
|
+
self.send(opt, opts[opt]) if opts[opt]
|
258
|
+
end
|
259
|
+
[:schema_only, :records_only].each do |opt|
|
260
|
+
self.send(opt) if opts[opt]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|