database_transform 0.1.1 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +46 -30
- data/lib/database_transform/schema.rb +1 -1
- data/lib/database_transform/schema_table.rb +48 -25
- data/lib/database_transform/tasks/transform.rake +34 -35
- data/lib/database_transform/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2ae47980a59f3120da84d3ed32be29f920f491f
|
4
|
+
data.tar.gz: 44077ed24f2f94f104acddcdb581b57db714665e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1d3934645c04b7213a10edab6f2aba18c655c87a971492f8ca5b73729974b71f1e1ba07b2039c2cd3bd0c6b2c458acb03afb23f629ede4501818c9dee71e6b1
|
7
|
+
data.tar.gz: 1aad272a4278423cf5b90819011c24b948e89a9d98b790c1da31064b84d802bcdd0bcee5e7d656dcdceb04078a4557ac7e2901e6ae3437e77ae6affc140835eb
|
data/README.md
CHANGED
@@ -32,7 +32,7 @@ Or install it yourself as:
|
|
32
32
|
|
33
33
|
## Usage
|
34
34
|
|
35
|
-
Database Transform is built with ActiveRecord in mind. First, define a new database
|
35
|
+
Database Transform is built with ActiveRecord in mind. First, define a new database connection to the old database in
|
36
36
|
database.yml:
|
37
37
|
|
38
38
|
```yaml
|
@@ -67,46 +67,62 @@ end
|
|
67
67
|
|
68
68
|
A summary of methods:
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
70
|
+
- `transform_table` tells Database Transform to perform the given transform over records in the given source table.
|
71
|
+
- The first argument is the table to transform. This can be a symbol, string, or an ActiveRecord model.
|
72
|
+
- `to` specifies the new table to transform to. This can be a symbol, string, or an ActiveRecord model.
|
73
|
+
- If either argument is a symbol or string, an ActiveRecord model is generated which allows access to the
|
74
|
+
record's data.
|
75
|
+
- Source models are found in the Source namespace, and can be used as the `posts.uid` column above.
|
76
|
+
- Destination models are found in the Destination namespace.
|
77
|
+
- In all cases, the model will have extra accessory methods:
|
78
|
+
- `transform(old_primary_key)`: This takes a primary key in the source table, and returns the transformed
|
79
|
+
object. This only returns a valid result after the object has been transformed.
|
80
|
+
- `transformed?(old_primary_key)`: This checks if the object has been transformed.
|
81
|
+
- `default_scope` allows the programmer to specify the records to transform
|
82
|
+
- `primary_key` declares the name of column with the primary key. This allows later access when relations need to be
|
83
|
+
mapped.
|
84
|
+
- Use the `transform` and `transformed?` methods on the model to obtain the transformed object.
|
85
|
+
- `column` declares how to transform the contents of that column from the old database to the new one.
|
86
|
+
- If `to:` is omitted, then it is assumed that the transfer function is the identity function, and the column would
|
87
|
+
map across as the same name.
|
88
|
+
- If `null: false` is specified, the value assigned to the column (in `to`) will be checked for nullity.
|
89
|
+
- A block can be provided.
|
90
|
+
- If so, then the data from the old record is passed to the block as the first argument
|
91
|
+
- In the context of the block, `self` refers to the new record.
|
92
|
+
- `self` has an additional attribute `source_record` which refers to the old record.
|
93
|
+
- `self` has an additional attribute `schema` which refers to the transformation schema.
|
94
|
+
- `save` declares whether the new record should be saved.
|
95
|
+
- `if` and `unless` accepts a block which will be evaluated to determine if the record should be saved.
|
96
|
+
- `validate` will allow the record to be saved bypassing validations. This defaults to `true`.
|
96
97
|
|
97
98
|
Finally, execute the Rake task:
|
98
99
|
|
99
|
-
$ rake db:transform
|
100
|
+
$ rake db:transform[my_old_app]
|
100
101
|
|
101
102
|
And the schema (`MyOldAppSchema`) and database connection (via `my_old_app_production`) will be established for you. A
|
102
103
|
few variants of the schema name will be checked:
|
103
104
|
|
104
|
-
|
105
|
-
|
105
|
+
- my_old_app
|
106
|
+
- my_old_app_production
|
106
107
|
|
107
108
|
Only the *source* schema will be annotated to use the other connection. The *destination* schema will be used through
|
108
109
|
the application's normal configuration (i.e. depends on the value of `ENV['RAILS_ENV']`.)
|
109
110
|
|
111
|
+
Additional arguments can be passed to the schema. All arguments specified in the Rake command following the schema name
|
112
|
+
will be passed to the initialiser of the schema.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class MyOldAppWithArgumentsSchema < DatabaseTransform::Schema
|
116
|
+
def initialize(uploads_path)
|
117
|
+
@uploads_path = uploads_path
|
118
|
+
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
$ rake db:transform[my_old_app_with_arguments,/home/joel/server/dumps/path]
|
123
|
+
|
124
|
+
These arguments then can be used from within any transform blocks by accessing the `schema` property.
|
125
|
+
|
110
126
|
## Contributing
|
111
127
|
|
112
128
|
1. Fork it ( https://github.com/[my-github-username]/database_transform/fork )
|
@@ -66,6 +66,8 @@ class DatabaseTransform::SchemaTable
|
|
66
66
|
# together with if.
|
67
67
|
# @option options [Proc] if A proc to call. The record will be saved if the proc returns a true value. This cannot be
|
68
68
|
# used together with unless.
|
69
|
+
# @option options [Boolean] validate Whether to run the model validations when saving the transformed record. Defaults
|
70
|
+
# to true.
|
69
71
|
def save(options = {})
|
70
72
|
raise ArgumentError.new('unless and if cannot be both specified') if options[:unless] && options[:if]
|
71
73
|
raise ArgumentError.new('Cannot specify a save clause twice for the same table') if @save
|
@@ -74,21 +76,24 @@ class DatabaseTransform::SchemaTable
|
|
74
76
|
clause = options.delete(:unless)
|
75
77
|
options[:if] = ->(*callback_args) { !self.instance_exec(*callback_args, &clause) }
|
76
78
|
end
|
79
|
+
options[:validate] = true unless options.has_key?(:validate)
|
77
80
|
|
78
81
|
@save = options
|
79
82
|
end
|
80
83
|
|
81
84
|
# @api private
|
82
85
|
# To be called only by Schema#run_transform
|
83
|
-
def run_transform
|
86
|
+
def run_transform(schema = nil)
|
84
87
|
before_message =
|
85
88
|
if @destination
|
86
|
-
format("-- transforming '%s' to '%s'
|
89
|
+
format("-- transforming '%s' to '%s'", @source.table_name, @destination.table_name)
|
87
90
|
else
|
88
|
-
format("-- transforming '%s'
|
91
|
+
format("-- transforming '%s'", @source.table_name)
|
89
92
|
end
|
90
93
|
|
91
|
-
time_block(before_message, " -> %fs
|
94
|
+
time_block(before_message, " -> %fs") do
|
95
|
+
transform!(schema)
|
96
|
+
end
|
92
97
|
end
|
93
98
|
|
94
99
|
private
|
@@ -109,37 +114,39 @@ class DatabaseTransform::SchemaTable
|
|
109
114
|
# @yield The block to time.
|
110
115
|
def time_block(before, after, &proc)
|
111
116
|
start = Time.now
|
112
|
-
$stderr.
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
117
|
+
$stderr.printf("%s\n", before)
|
118
|
+
|
119
|
+
proc.call
|
120
|
+
rescue Exception
|
121
|
+
$stderr.printf("%s (error)\n", format(after, Time.now - start))
|
122
|
+
raise
|
123
|
+
else
|
124
|
+
$stderr.printf("%s\n", format(after, Time.now - start))
|
120
125
|
end
|
121
126
|
|
122
127
|
# Performs the transform with the given parameters.
|
123
128
|
#
|
129
|
+
# @param [DatabaseTransform::Schema] schema The schema being transformed.
|
124
130
|
# @return [Void]
|
125
|
-
def transform!
|
131
|
+
def transform!(schema)
|
126
132
|
# For each item in the old model
|
127
133
|
default_scope = @default_scope || @source.method(:all)
|
128
134
|
@source.instance_exec(&default_scope).each do |record|
|
129
|
-
transform_record!(record)
|
135
|
+
transform_record!(schema, record)
|
130
136
|
end
|
131
137
|
end
|
132
138
|
|
133
139
|
# Transforms one record from the source model to the destination.
|
134
140
|
#
|
141
|
+
# @param [DatabaseTransform::Schema] schema The schema being transformed.
|
135
142
|
# @param [ActiveRecord::Base] old The record to map.
|
136
143
|
# @return [Void]
|
137
|
-
def transform_record!(old)
|
144
|
+
def transform_record!(schema, old)
|
138
145
|
# Instantiate a new model record
|
139
146
|
new = @destination.new if @destination
|
140
147
|
|
141
148
|
# Map the columns over
|
142
|
-
transform_record_columns!(old, new)
|
149
|
+
transform_record_columns!(schema, old, new)
|
143
150
|
return if new.nil? || new.frozen?
|
144
151
|
|
145
152
|
save_transformed_record(old, new)
|
@@ -147,14 +154,15 @@ class DatabaseTransform::SchemaTable
|
|
147
154
|
|
148
155
|
# Applies the column transforms over the old record to the new record.
|
149
156
|
#
|
157
|
+
# @param [DatabaseTransform::Schema] schema The schema being transformed.
|
150
158
|
# @param [ActiveRecord::Base] old The record to map.
|
151
159
|
# @param [ActiveRecord::Base] new The record to map to.
|
152
160
|
# @return [Void]
|
153
|
-
def transform_record_columns!(old, new)
|
161
|
+
def transform_record_columns!(schema, old, new)
|
154
162
|
@columns.each do |column|
|
155
163
|
fail ArgumentError.new unless column.is_a?(Hash)
|
156
164
|
|
157
|
-
new_value = transform_record_field!(old, new, column[:from], column[:block])
|
165
|
+
new_value = transform_record_field!(schema, old, new, column[:from], column[:block])
|
158
166
|
|
159
167
|
unless new.nil?
|
160
168
|
break if new.frozen?
|
@@ -167,12 +175,13 @@ class DatabaseTransform::SchemaTable
|
|
167
175
|
|
168
176
|
# Transforms one record's field.
|
169
177
|
#
|
178
|
+
# @param [DatabaseTransform::Schema] schema The schema being transformed.
|
170
179
|
# @param [ActiveRecord::Base] source The source row to map the values for.
|
171
180
|
# @param [ActiveRecord::Base] destination The destination row to map the values to.
|
172
181
|
# @param [Array<Symbol>] from The source columns to be used to map to the destination column.
|
173
182
|
# @param [Proc, nil] block The block to transform the source values to the destination value.
|
174
183
|
# @return The result of applying the transform over the input values.
|
175
|
-
def transform_record_field!(source, destination, from = nil, block = nil)
|
184
|
+
def transform_record_field!(schema, source, destination, from = nil, block = nil)
|
176
185
|
# Get the old record column values (can be a block taking multiple arguments)
|
177
186
|
new_values = from.map { |k| source.send(k) }
|
178
187
|
|
@@ -181,15 +190,29 @@ class DatabaseTransform::SchemaTable
|
|
181
190
|
new_values.first
|
182
191
|
elsif destination
|
183
192
|
# Map the value if necessary.
|
184
|
-
|
185
|
-
# We should ensure that the block has a way to access the schema.
|
186
|
-
destination.instance_exec(*new_values, &block)
|
193
|
+
execute_transform_block!(schema, source, destination, block, new_values)
|
187
194
|
else
|
188
195
|
# Call the proc
|
189
|
-
block
|
196
|
+
execute_transform_block!(schema, source, Object.new, block, new_values)
|
190
197
|
end
|
191
198
|
end
|
192
199
|
|
200
|
+
# Executes the given transform block.
|
201
|
+
#
|
202
|
+
# This allows the transform to be given an appropriate value of self such that it can access the old record and the schema being used.
|
203
|
+
#
|
204
|
+
# @param [DatabaseTransform::Schema] schema The schema being transformed.
|
205
|
+
# @param [ActiveRecord::Base] source_record The old record being transformed.
|
206
|
+
# @param self_ The object to use as self in the context of the block.
|
207
|
+
# @param [Proc] block The block to execute.
|
208
|
+
# @param [Array] args The arguments to give to the block.
|
209
|
+
# @return The result of applying the block.
|
210
|
+
def execute_transform_block!(schema, source_record, self_, block, args)
|
211
|
+
self_.define_singleton_method(:source_record) { source_record } unless self_.respond_to?(:source_record)
|
212
|
+
self_.define_singleton_method(:schema) { schema } unless self_.respond_to?(:schema)
|
213
|
+
self_.instance_exec(*args, &block)
|
214
|
+
end
|
215
|
+
|
193
216
|
# Assigns the transformed value to the new record, raising an argument error if the field was declared to not be
|
194
217
|
# nullable and the value is null.
|
195
218
|
#
|
@@ -216,9 +239,9 @@ class DatabaseTransform::SchemaTable
|
|
216
239
|
def save_transformed_record(old, new)
|
217
240
|
# Save. Skip if the conditional callback is given
|
218
241
|
return if @save && @save[:if] && !new.instance_exec(&@save[:if])
|
242
|
+
validate = @save ? @save[:validate] : nil
|
219
243
|
|
220
|
-
|
221
|
-
new.save!(validate: false) unless new.destroyed?
|
244
|
+
new.save!(validate: validate != false) unless new.destroyed?
|
222
245
|
@source.memoize_transform(old.send(@primary_key), new) if @primary_key
|
223
246
|
end
|
224
247
|
end
|
@@ -1,35 +1,34 @@
|
|
1
|
-
# This class just loads the file containing the schema definition and hands off control to the schema.
|
2
|
-
class DatabaseTransform::Transform
|
3
|
-
def initialize(args)
|
4
|
-
@schema = args.schema_name
|
5
|
-
@
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
1
|
+
# This class just loads the file containing the schema definition and hands off control to the schema.
|
2
|
+
class DatabaseTransform::Transform
|
3
|
+
def initialize(args)
|
4
|
+
@schema = args.schema_name
|
5
|
+
@argv = args.extras
|
6
|
+
end
|
7
|
+
|
8
|
+
def run
|
9
|
+
require_schema
|
10
|
+
schema = @schema.camelize.constantize.new(*@argv)
|
11
|
+
|
12
|
+
ActiveRecord::Base.logger = Logger.new('log/import.log')
|
13
|
+
schema.transform!
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def require_schema
|
19
|
+
schema_file = @schema.underscore
|
20
|
+
begin
|
21
|
+
return require(File.join(Rails.root, 'db', 'transforms', schema_file))
|
22
|
+
rescue LoadError
|
23
|
+
end
|
24
|
+
|
25
|
+
require (File.join(Rails.root, 'db', 'transforms', schema_file, schema_file))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
namespace :db do
|
30
|
+
desc 'Transform old database schemas'
|
31
|
+
task :transform, [:schema_name] => :environment do |_, args|
|
32
|
+
DatabaseTransform::Transform.new(args).run
|
33
|
+
end
|
34
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: database_transform
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel Low
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|