database_transform 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4a266c8fa585850029bbf5f9dc845433db966782
4
- data.tar.gz: ee2da09e5e7e473209377e3003363696e5cc113f
3
+ metadata.gz: 5260295aa827b58fa3504b901e310810dfb36a4a
4
+ data.tar.gz: f15c12e08f7b6b736af4dc86e1d97ae0b33cd394
5
5
  SHA512:
6
- metadata.gz: e33caabcd8f7c37f70e98403a9824a6a80e7f6ff3177d167a9366bbc4c412fbb468486e373499852a38cc3d39e64b2322fe24034a0219b5a045d98a66006b26a
7
- data.tar.gz: 7f1b7b15d9576c2a707a77e2857d075abd99e48d2b5ea11f42af000a7a60fb05627aa0ceb441fd5f9d18e3d117deaa781948452677318021bf17feb0359dd186
6
+ metadata.gz: 8c1d1982ff7532f8a7d21ab1c5541aa1169830bdcf07acecf14e8f5976d0a5d40ad498e4d48d17a3f0c32781a0657802ca1f937ce1c0cf79aeb6b737d657a069
7
+ data.tar.gz: 51a77ad4b478b66270af5f4e974da544a431886d49b146f154e485e1564314d37cf3c65e7dcfbb13155eb64f4a420cbfbe00f20fcf135f5f8568cf7d3aa098b0
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
- --format documentation
2
1
  --color
2
+ --require spec_helper
data/README.md CHANGED
@@ -1,8 +1,18 @@
1
- # DatabaseTransform
1
+ # Database Transform
2
+ [![Build Status](https://travis-ci.org/lowjoel/database_transform.svg)](https://travis-ci.org/lowjoel/database_transform)
3
+ [![Coverage Status](https://coveralls.io/repos/lowjoel/database_transform/badge.svg)](https://coveralls.io/r/lowjoel/database_transform)
4
+ [![Code Climate](https://codeclimate.com/github/lowjoel/database_transform/badges/gpa.svg)](https://codeclimate.com/github/lowjoel/database_transform)
2
5
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/database_transform`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+ This gem will allow you to transform a database's contents across schemas with a simple DSL. This is useful when
7
+ migrating from an application written in another framework to Rails, allowing programmers to reason about how an old
8
+ schema maps to a new one.
4
9
 
5
- TODO: Delete this and the text above, and describe your gem
10
+ Similar gems exist:
11
+
12
+ - [legacy_migrations](https://github.com/btelles/legacy_migrations)
13
+ - [super_migration](https://github.com/christian/super_migration)
14
+
15
+ However, they have not been updated for a few years.
6
16
 
7
17
  ## Installation
8
18
 
@@ -22,13 +32,80 @@ Or install it yourself as:
22
32
 
23
33
  ## Usage
24
34
 
25
- TODO: Write usage instructions here
35
+ Database Transform is built with ActiveRecord in mind. First, define a new database conndtion to the old database in
36
+ database.yml:
37
+
38
+ ```yaml
39
+ my_old_app_production:
40
+ adapter: postgresql
41
+ host: old_server
42
+ database: my_old_app
43
+ ```
26
44
 
27
- ## Development
45
+ Then, define the define a transform in `db/transforms/my_old_app_schema.rb`:
28
46
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
47
+ ```ruby
48
+ class MyOldAppSchema < DatabaseTransform::Schema
49
+ transform_table :users, to: ::User, scope: proc { where('uid <> 0') } do
50
+ primary_key :uid
51
+ column :mail, to: :email
52
+ column :pass, to: :password do |password|
53
+ self.password_confirmation = password
54
+ end
55
+ end
56
+
57
+ transform_table :posts, to: ::Post do
58
+ primary_key :post_id
59
+ column :uid, to: :user, null: false do |uid|
60
+ Source::User.transform(uid)
61
+ end
62
+ column :content
63
+ save unless: proc { content.empty? }
64
+ end
65
+ end
66
+ ```
30
67
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
68
+ A summary of methods:
69
+
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 record's
74
+ 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 object.
79
+ 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
+ - `column` declares how to transform the contents of that column from the old database to the new one.
85
+ - If `to:` is omitted, then it is assumed that the transfer function is the identity function, and the column would
86
+ map across as the same name.
87
+ - If `null: false` is specified, the value assigned to the column (in `to`) will be checked for nullity.
88
+ - A block can be provided.
89
+ - If so, then the data from the old record is passed to the block as the first argument
90
+ - In the context of the block, `self` refers to the new record.
91
+ - `self` has an additional attribute `source_record` which refers to the old record. (TODO)
92
+ - `self` has an additional attribute `schema` which refers to the transformation schema. (TODO)
93
+ - `save` declares whether the new record should be saved.
94
+ - `if` and `unless` accepts a block which will be evaluated to determine if the record should be saved.
95
+ - `validate` will allow the record to be saved bypassing validations. This defaults to `true`.
96
+
97
+ Finally, execute the Rake task:
98
+
99
+ $ rake db:transform my_old_app
100
+
101
+ And the schema (`MyOldAppSchema`) and database connection (via `my_old_app_production`) will be established for you. A
102
+ few variants of the schema name will be checked:
103
+
104
+ - my_old_app
105
+ - my_old_app_production
106
+
107
+ Only the *source* schema will be annotated to use the other connection. The *destination* schema will be used through
108
+ the application's normal configuration (i.e. depends on the value of `ENV['RAILS_ENV']`.)
32
109
 
33
110
  ## Contributing
34
111
 
data/Rakefile CHANGED
@@ -1 +1,13 @@
1
1
  require "bundler/gem_tasks"
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ require "rspec/core/rake_task"
9
+
10
+ RSpec::Core::RakeTask.new
11
+
12
+ task :default => :spec
13
+ task :test => :spec
@@ -19,6 +19,14 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ['lib']
21
21
 
22
+ spec.add_dependency 'activesupport'
23
+ spec.add_dependency 'activerecord'
24
+
22
25
  spec.add_development_dependency 'bundler', '~> 1.9'
23
26
  spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rspec', '~> 3'
28
+ spec.add_development_dependency 'sqlite3', '~> 1.3'
29
+
30
+ spec.add_development_dependency 'coveralls'
31
+ spec.add_development_dependency 'codeclimate-test-reporter'
24
32
  end
@@ -1,5 +1,17 @@
1
- require "database_transform/version"
1
+ require 'database_transform/version'
2
2
 
3
3
  module DatabaseTransform
4
- # Your code goes here...
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :DuplicateError
7
+ autoload :UnsatisfiedDependencyError
8
+
9
+ autoload :SchemaTables
10
+ autoload :SchemaModelStore
11
+ autoload :Schema
12
+
13
+ autoload :SchemaTableRecordMapping
14
+ autoload :SchemaTable
15
+
16
+ require 'database_transform/railtie' if defined?(Rails)
5
17
  end
@@ -0,0 +1,10 @@
1
+ class DatabaseTransform::DuplicateError < StandardError
2
+ def initialize(column)
3
+ super
4
+ @column = column
5
+ end
6
+
7
+ def to_s
8
+ "The column #{@column} had multiple transforms."
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class DatabaseTransform::Railtie < Rails::Railtie
2
+ rake_tasks do
3
+ load 'database_transform/tasks/transform.rake'
4
+ end
5
+ end
@@ -0,0 +1,123 @@
1
+ class DatabaseTransform::Schema
2
+ extend DatabaseTransform::SchemaModelStore
3
+ extend DatabaseTransform::SchemaTables
4
+
5
+ # Transforms a table from the source database to the new database.
6
+ #
7
+ # Specify the source table to get entries from; a proc with the transform steps can be specified.
8
+ #
9
+ # @option args [String, Symbol, Class] to The name of the destination table; if this is not specified, no tables are
10
+ # copied. This can be a class, or a symbol which will be constantised; each column mapping proc will have access to
11
+ # one instance of this table when performing a transformation.
12
+ # @option args [Array<String, Symbol>] depends An array of symbols (source tables) which must be transformed before
13
+ # the specified source table can be transformed.
14
+ # @option args [Proc] default_scope The default scope of the old table to use when transforming.
15
+ def self.transform_table(source_table, args = {}, &proc)
16
+ raise ArgumentError.new if source_table.nil?
17
+ raise DatabaseTransform::DuplicateError.new(source_table) if tables.has_key?(source_table)
18
+
19
+ source_table, args[:to] = prepare_models(source_table, args[:to])
20
+
21
+ transform = DatabaseTransform::SchemaTable.new(source_table, args[:to], args[:default_scope])
22
+ tables[source_table] = { depends: args[:depends] || [], transform: transform }
23
+ transform.instance_eval(&proc) if proc
24
+ end
25
+
26
+ class << self
27
+ private
28
+
29
+ def prepare_models(source_table, destination_table)
30
+ source_table = generate_model(const_get(:Source), source_table) unless source_table.is_a?(Class)
31
+ set_connection_for_model(source_table, deduce_connection_name)
32
+
33
+ if !destination_table.nil? && !destination_table.is_a?(Class)
34
+ destination_table = generate_model(const_get(:Destination), destination_table)
35
+ end
36
+
37
+ [source_table, destination_table]
38
+ end
39
+
40
+ def generate_model(within, table_name)
41
+ class_name = ActiveSupport::Inflector.camelize(ActiveSupport::Inflector.singularize(table_name.to_s))
42
+ within.module_eval <<-EndCode, __FILE__, __LINE__ + 1
43
+ class #{class_name} < ActiveRecord::Base
44
+ self.table_name = '#{table_name.to_s}'
45
+ end
46
+ #{class_name.to_s.singularize.camelize}
47
+ EndCode
48
+ end
49
+
50
+ # Deduces the connection name from the name of the schema class.
51
+ #
52
+ # @return [String] The name of the connection to use.
53
+ def deduce_connection_name
54
+ deduced_connection_name = ActiveSupport::Inflector.underscore(name)
55
+ return deduced_connection_name if connection_name_exists?(deduced_connection_name)
56
+
57
+ deduced_connection_name << '_production'
58
+ end
59
+
60
+ # Checks if the given connection name exists
61
+ #
62
+ # @param [String] name The name of the connection to check.
63
+ # @return [Boolean] True if the connection exists.
64
+ def connection_name_exists?(name)
65
+ ActiveRecord::Base.configurations.has_key?(name)
66
+ end
67
+
68
+ # Sets the connection for the given model, using the given connection name.
69
+ #
70
+ # @param [Class] model The model to set the connection on.
71
+ # @param [String] connection_name The name of the connection to set.
72
+ # @return [Void]
73
+ def set_connection_for_model(model, connection_name)
74
+ model.establish_connection(connection_name.to_sym)
75
+ end
76
+ end
77
+
78
+ # Runs the transform.
79
+ #
80
+ # @raise [DatabaseTransform::UnsatisfiedDependencyError] When the dependencies for a table cannot be satisfied, and no
81
+ # progress can be made.
82
+ # @return [Void]
83
+ def transform!
84
+ # The tables have dependencies; we must run them in order.
85
+ transformed = Set.new
86
+ queue = Set.new(tables.keys)
87
+
88
+ # We try to run all the transforms we can until no more can be run.
89
+ # If no more can run and the input queue is empty, we are done.
90
+ # If no more can run and the input queue is not empty, we have a dependency cycle.
91
+ begin
92
+ transformed_this_pass = transform_pass(transformed, queue)
93
+ fail DatabaseTransform::UnsatisfiedDependencyError.new(queue.to_a) if transformed_this_pass.empty? && !queue.empty?
94
+
95
+ queue -= transformed_this_pass
96
+ transformed += transformed_this_pass
97
+ end until queue.empty?
98
+ end
99
+
100
+ private
101
+
102
+ # Performs a transform over all elements of the queue who has its dependencies satisfied.
103
+ #
104
+ # @param [Array<Symbol>] transformed The models which have been transformed.
105
+ # @param [Array<Symbol>] queue The models which need to be transformed.
106
+ # @return [Set<Symbol>] The set of models which were transformed this pass.
107
+ def transform_pass(transformed, queue)
108
+ transformed_this_pass = Set.new
109
+ queue.each do |table|
110
+ # Check that all dependencies are satisfied
111
+ table_config = tables[table]
112
+ unmet_dependencies = table_config[:depends].select do |s|
113
+ !transformed.include?(s) && !transformed_this_pass.include?(s)
114
+ end
115
+ next unless unmet_dependencies.empty?
116
+
117
+ table_config[:transform].run_transform
118
+ transformed_this_pass << table
119
+ end
120
+
121
+ transformed_this_pass
122
+ end
123
+ end
@@ -0,0 +1,7 @@
1
+ # Implements a model namespace for database tables without an explicit model.
2
+ module DatabaseTransform::SchemaModelStore
3
+ def inherited(class_)
4
+ class_.const_set(:Source, Module.new)
5
+ class_.const_set(:Destination, Module.new)
6
+ end
7
+ end
@@ -0,0 +1,224 @@
1
+ # Represents a transformation from a source table to a destination table.
2
+ class DatabaseTransform::SchemaTable
3
+ # Initialises the table definition for a particular schema
4
+ #
5
+ # @param [Class] source The model class to map source records from
6
+ # @param [Class] destination The model class to map destination records to
7
+ # @param [nil, Proc] default_scope The default scope for querying the source table.
8
+ def initialize(source, destination, default_scope)
9
+ @source = source
10
+ @source.extend(DatabaseTransform::SchemaTableRecordMapping)
11
+ @destination = destination
12
+ @destination.extend(DatabaseTransform::SchemaTableRecordMapping) if @destination
13
+ @default_scope = default_scope
14
+
15
+ @primary_key = nil
16
+ @save = nil
17
+ @columns = []
18
+ end
19
+
20
+ # Declare the primary key of the source table.
21
+ #
22
+ # @param [Symbol] id The name of the column in the source table which is the primary key.
23
+ def primary_key(id)
24
+ raise DatabaseTransform::DuplicateError.new(id) if @primary_key
25
+ @primary_key = id
26
+ @source.primary_key = id
27
+ end
28
+
29
+ # Declare the mapping for the source table to the new table.
30
+ #
31
+ # This function takes source columns to provide to the mapping proc as arguments.
32
+ #
33
+ # The destination column is specified as the to hash parameter.
34
+ # If no mapping block is specified, the source column is copied to the destination column without modification. This
35
+ # is not possible if more than one source column is specified.
36
+ # If the mapping block is specified, if the destination column is specified, the result of the block is used as the
37
+ # destination column's value.
38
+ # Otherwise, the result is unused. This can be used to execute the block purely for side-effects
39
+ #
40
+ # @option options [Symbol] to The column to map to.
41
+ # @option options [Boolean] null If to is specified, this can be false which enforces that nil is never added to the
42
+ # database.
43
+ def column(*args, &block)
44
+ raise ArgumentError if args.length < 1
45
+
46
+ # Get the columns
47
+ options = args.extract_options!
48
+ source_columns = args
49
+ to_column = options.delete(:to)
50
+ to_column ||= source_columns.first unless block
51
+
52
+ validate_column_options!(source_columns, to_column, options, block)
53
+
54
+ # Store the mapping
55
+ @columns << {
56
+ from: source_columns,
57
+ to: to_column,
58
+ null: options[:null].nil? ? true : options[:null],
59
+ block: block
60
+ }
61
+ end
62
+
63
+ # Specifies a save clause.
64
+ #
65
+ # @option options [Proc] unless The record will be saved if the proc returns a false value. This cannot be used
66
+ # together with if.
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
+ # used together with unless.
69
+ def save(options = {})
70
+ raise ArgumentError.new('unless and if cannot be both specified') if options[:unless] && options[:if]
71
+ raise ArgumentError.new('Cannot specify a save clause twice for the same table') if @save
72
+
73
+ if options[:unless]
74
+ clause = options.delete(:unless)
75
+ options[:if] = ->(*callback_args) { !self.instance_exec(*callback_args, &clause) }
76
+ end
77
+
78
+ @save = options
79
+ end
80
+
81
+ # @api private
82
+ # To be called only by Schema#run_transform
83
+ def run_transform
84
+ before_message =
85
+ if @destination
86
+ format("-- transforming '%s' to '%s'\n", @source.table_name, @destination.table_name)
87
+ else
88
+ format("-- transforming '%s'\n", @source.table_name)
89
+ end
90
+
91
+ time_block(before_message, " -> %fs\n", &method(:transform!))
92
+ end
93
+
94
+ private
95
+
96
+ # Validates the options given to the #column method.
97
+ def validate_column_options!(source_columns, to_column, options, block)
98
+ raise ArgumentError.new unless to_column.nil? || to_column.is_a?(Symbol)
99
+ raise ArgumentError.new if !block && source_columns.length > 1
100
+ raise ArgumentError.new if options[:null] == false && !to_column
101
+ end
102
+
103
+ # Performs the given operation, timing it and printing before and after messages for executing the block.
104
+ #
105
+ # @param [String] before The message to print before the operation.
106
+ # @param [String] after The message to print after the operation. One floating point format argument is available,
107
+ # which is the time taken for the operation.
108
+ # @return The result of executing the block.
109
+ # @yield The block to time.
110
+ def time_block(before, after, &proc)
111
+ start = Time.now
112
+ $stderr.puts(before)
113
+
114
+ result = proc.call
115
+
116
+ complete = Time.now - start
117
+ $stderr.printf(after, complete)
118
+
119
+ result
120
+ end
121
+
122
+ # Performs the transform with the given parameters.
123
+ #
124
+ # @return [Void]
125
+ def transform!
126
+ # For each item in the old model
127
+ default_scope = @default_scope || @source.method(:all)
128
+ @source.instance_exec(&default_scope).each do |record|
129
+ transform_record!(record)
130
+ end
131
+ end
132
+
133
+ # Transforms one record from the source model to the destination.
134
+ #
135
+ # @param [ActiveRecord::Base] old The record to map.
136
+ # @return [Void]
137
+ def transform_record!(old)
138
+ # Instantiate a new model record
139
+ new = @destination.new if @destination
140
+
141
+ # Map the columns over
142
+ transform_record_columns!(old, new)
143
+ return if new.nil? || new.frozen?
144
+
145
+ save_transformed_record(old, new)
146
+ end
147
+
148
+ # Applies the column transforms over the old record to the new record.
149
+ #
150
+ # @param [ActiveRecord::Base] old The record to map.
151
+ # @param [ActiveRecord::Base] new The record to map to.
152
+ # @return [Void]
153
+ def transform_record_columns!(old, new)
154
+ @columns.each do |column|
155
+ fail ArgumentError.new unless column.is_a?(Hash)
156
+
157
+ new_value = transform_record_field!(old, new, column[:from], column[:block])
158
+
159
+ unless new.nil?
160
+ break if new.frozen?
161
+ next if column[:to].nil?
162
+
163
+ assign_record_field!(old, new, column, new_value)
164
+ end
165
+ end
166
+ end
167
+
168
+ # Transforms one record's field.
169
+ #
170
+ # @param [ActiveRecord::Base] source The source row to map the values for.
171
+ # @param [ActiveRecord::Base] destination The destination row to map the values to.
172
+ # @param [Array<Symbol>] from The source columns to be used to map to the destination column.
173
+ # @param [Proc, nil] block The block to transform the source values to the destination value.
174
+ # @return The result of applying the transform over the input values.
175
+ def transform_record_field!(source, destination, from = nil, block = nil)
176
+ # Get the old record column values (can be a block taking multiple arguments)
177
+ new_values = from.map { |k| source.send(k) }
178
+
179
+ if block.nil?
180
+ # We have to ensure the value is a scalar
181
+ new_values.first
182
+ elsif destination
183
+ # Map the value if necessary.
184
+ # TODO: Provide the schema instance to the callback.
185
+ # We should ensure that the block has a way to access the schema.
186
+ destination.instance_exec(*new_values, &block)
187
+ else
188
+ # Call the proc
189
+ block.call(*new_values)
190
+ end
191
+ end
192
+
193
+ # Assigns the transformed value to the new record, raising an argument error if the field was declared to not be
194
+ # nullable and the value is null.
195
+ #
196
+ # @param [ActiveRecord::Base] old The source row to map the values for.
197
+ # @param [ActiveRecord::Base] new The destination row to map the values to.
198
+ # @param [Hash] column The column mapping definition being used.
199
+ # @param new_value The new value to assign to the field.
200
+ # @return [Void]
201
+ def assign_record_field!(old, new, column, new_value)
202
+ if new_value.nil? && column[:null] == false
203
+ old_value = @primary_key ? old.send(@primary_key) : old.inspect
204
+ raise ArgumentError.new("Key #{column[:from]} for row #{old_value} in #{@source.table_name} maps to null for "\
205
+ 'non-nullable column')
206
+ end
207
+
208
+ new.send("#{column[:to]}=", new_value)
209
+ end
210
+
211
+ # Saves the newly transformed record, and then memoizes the transformed value.
212
+ #
213
+ # @param [ActiveRecord::Base] old The source row to map the values for.
214
+ # @param [ActiveRecord::Base] new The destination row to map the values to.
215
+ # @return [Void]
216
+ def save_transformed_record(old, new)
217
+ # Save. Skip if the conditional callback is given
218
+ return if @save && @save[:if] && !new.instance_exec(&@save[:if])
219
+
220
+ # TODO: Make validation optional using the save clause.
221
+ new.save!(validate: false) unless new.destroyed?
222
+ @source.memoize_transform(old.send(@primary_key), new) if @primary_key
223
+ end
224
+ end
@@ -0,0 +1,31 @@
1
+ module DatabaseTransform::SchemaTableRecordMapping
2
+ # Obtains the result of transforming the record with the given primary key.
3
+ #
4
+ # @param old_primary_key The primary key of the record to obtain the result for.
5
+ # @raise [ActiveRecord::RecordNotFound] When the primary has not been transformed, or the primary key does not exist.
6
+ # @return The new record after transformation.
7
+ def transform(old_primary_key)
8
+ @transformed ||= {}
9
+ unless @transformed.has_key?(old_primary_key)
10
+ raise ActiveRecord::RecordNotFound.new("Key #{old_primary_key} in #{table_name}")
11
+ end
12
+
13
+ @transformed[old_primary_key]
14
+ end
15
+
16
+ # Checks if the given primary key has been transformed.
17
+ #
18
+ # @param old_primary_key The primary key of the record to obtain the result for.
19
+ # @return [Boolean] True if the record has been transformed.
20
+ def transformed?(old_primary_key)
21
+ @transformed ||= {}
22
+ @transformed.has_key?(old_primary_key)
23
+ end
24
+
25
+ # @api private
26
+ # Called by TableTransform#run_transform
27
+ def memoize_transform(old_primary_key, result)
28
+ @transformed ||= {}
29
+ @transformed[old_primary_key] = result
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ module DatabaseTransform::SchemaTables
2
+ def self.extended(class_)
3
+ class_.class_attribute :tables
4
+ class_.tables = {}.freeze
5
+ end
6
+
7
+ def inherited(class_)
8
+ def class_.tables
9
+ @tables ||= ancestors[1].tables.dup
10
+ end
11
+ super
12
+ end
13
+ end
@@ -0,0 +1,35 @@
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
+ @extra_args = args.extras
6
+ run
7
+ end
8
+
9
+ def run
10
+ import_schema
11
+ schema = @schema.constantize.new
12
+
13
+ ActiveRecord::Base.logger = Logger.new('log/import.log')
14
+ schema.transform!
15
+ end
16
+
17
+ private
18
+
19
+ def import_schema
20
+ schema_file = @schema.underscore
21
+ begin
22
+ return require(File.join(Rails.root, 'db', 'transforms', schema_file))
23
+ rescue LoadError
24
+ end
25
+
26
+ require (File.join(Rails.root, 'db', 'transforms', schema_file, schema_file))
27
+ end
28
+ end
29
+
30
+ namespace :db do
31
+ desc 'Transform old database schemas'
32
+ task :transform, [:schema_name] => :environment do |_, args|
33
+ DatabaseTransform::Transform.new(args)
34
+ end
35
+ end
@@ -0,0 +1,6 @@
1
+ class DatabaseTransform::UnsatisfiedDependencyError < StandardError
2
+ def initialize(tables)
3
+ @tables = tables
4
+ super(tables.join(', '))
5
+ end
6
+ end
@@ -1,3 +1,3 @@
1
1
  module DatabaseTransform
2
- VERSION = "0.1.0"
2
+ VERSION = '0.1.1'
3
3
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: database_transform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
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-27 00:00:00.000000000 Z
11
+ date: 2015-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -38,12 +66,67 @@ dependencies:
38
66
  - - "~>"
39
67
  - !ruby/object:Gem::Version
40
68
  version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: coveralls
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: codeclimate-test-reporter
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
41
125
  description: Transforms the data of a database from an old schema to a new one.
42
126
  email:
43
127
  - joel@joelsplace.sg
44
128
  executables:
45
129
  - console
46
- - setup
47
130
  extensions: []
48
131
  extra_rdoc_files: []
49
132
  files:
@@ -55,9 +138,17 @@ files:
55
138
  - README.md
56
139
  - Rakefile
57
140
  - bin/console
58
- - bin/setup
59
141
  - database_transform.gemspec
60
142
  - lib/database_transform.rb
143
+ - lib/database_transform/duplicate_error.rb
144
+ - lib/database_transform/railtie.rb
145
+ - lib/database_transform/schema.rb
146
+ - lib/database_transform/schema_model_store.rb
147
+ - lib/database_transform/schema_table.rb
148
+ - lib/database_transform/schema_table_record_mapping.rb
149
+ - lib/database_transform/schema_tables.rb
150
+ - lib/database_transform/tasks/transform.rake
151
+ - lib/database_transform/unsatisfied_dependency_error.rb
61
152
  - lib/database_transform/version.rb
62
153
  homepage: https://github.com/lowjoel/database_transform
63
154
  licenses:
data/bin/setup DELETED
@@ -1,7 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
-
5
- bundle install
6
-
7
- # Do any other automated setup that you need to do here