wineskins 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|