data_migrator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,42 @@
1
+ Data Migrations
2
+ ===============
3
+
4
+ Every Rails developer is familiar with Rails’ invaluable schema migration tool but, as I’m sure many have experienced, it does not address the need for data migrations. Mixing data migrations into schema migrations is simply unacceptable. There is of course the seed data tool, but this is intended for the loading of initial data, much like fixtures. We needed a way to deal with ongoing data migrations to support schema changes over time. This called for a tool similar to schema migrations to be used exclusively for data migrations. The behavior is almost identical to schema migrations. They are controlled via a versioning mechanism like schema migrations. They can be run up and down and they load your Rails environment so all your models are fair game. Anything you can do with your models you can do in the migrations i.e. create, update destroy/delete.
5
+ It's easy to get started just install the plugin and your ready to go. There is a generator just like with schema migrations:
6
+
7
+ script/generate data_migration create_sample_users
8
+
9
+ It will generate the versioning for you and place the file in db/data_migrations (don't worry if the folder isn't there it will create it for you). Simply add your code to the generated file
10
+
11
+ class CreateSampleUsers
12
+ def self.up
13
+ User.create(:login => 'awesome_dude' , :email => 'awesome@dude.com')
14
+ end
15
+ def self.down
16
+ User.destroy_all(:conditions => ['login = ?','awesome_dude'])
17
+ end
18
+ end
19
+ You can even use the Migration class if you wish:
20
+ class CreateSampleUsers < ActiveRecord::Migration
21
+ def self.up
22
+ execute "UPDATE users SET name=’Fred' WHERE id=2"
23
+ end
24
+ end
25
+
26
+ As you can see it has an up and a down just like schema migrations. To run the data migrations simply run the rake task
27
+ rake db:migrate_data
28
+
29
+ All the schema migration features work here too
30
+ Migrate up to and including specified version, running all migrations that have not been run:
31
+ rake db:migrate VERSION=10101010101
32
+ Migrate only the specified migration if it has not been run:
33
+ rake db:migrate_data:up VERSION=1010101010101
34
+ Migrate down only the specified version if it has not been run:
35
+ rake db:migrate_data:down VERSION=10101010101
36
+
37
+ One added benefit that data migrations have over regular schema migrations is that if you like to develop in engines as I do, it will pick up and run any of the data migrations living in your engines as well. Just create a folder in your engine db/data_migrations and you’re all set. When you run the rake task it will copy any files located in db/data_migrations of your engines up to RAILS_ROOT/db/data_migrations and run them. When it is done it will remove them from RAILS_ROOT /db/data_migrations leaving them down in your engines. Yet another cool feature (I know this is starting to sound like an infomercial), if you don't want to run some of your migrations, say you have some test data that you want only ran from your environment you can place them in folder called db/data_migrations/ignore and as the folder suggests the rake task will ignore anything and everything in that folder. So when you commit your changes, your colleagues won't groan when your migrations won't run in their environment. It will also recursively go through folders looking for migrations so you can organize them if you like
38
+
39
+ db/data_migrations/users/010101010101_example_users.rb
40
+ db/data_migrations/roles/010101010101_example_roles.rb
41
+
42
+ That about wraps it up. Have at it my fellow developers and as always happy coding.
@@ -0,0 +1,15 @@
1
+ class CreateDataMigrationsTable < ActiveRecord::Migration
2
+ def self.up
3
+ unless table_exists?(:data_migrations)
4
+ create_table :data_migrations, {:id => false} do |t|
5
+ t.column :version, :string
6
+ end
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ if table_exists?(:data_migrations)
12
+ drop_table :data_migrations
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,303 @@
1
+ require "benchmark"
2
+
3
+ class DataMigrator
4
+ DEFAULT_MIGRATIONS_PATH = "#{Rails.root}/db/data_migrations"
5
+ REMOVE_FILES_REGEX = /^\./
6
+
7
+ def self.migrate(passed_location=nil, passed_version=nil)
8
+ self.setup
9
+
10
+ location = passed_location.nil? ? DEFAULT_MIGRATIONS_PATH : passed_location
11
+ @@current_version = get_current_version
12
+
13
+ if passed_version.nil? || @@current_version.nil?
14
+ self.run_all_non_migrated(passed_version)
15
+ elsif passed_version < @@current_version
16
+ self.handle_lower_passed_version(passed_version)
17
+ elsif passed_version > @@current_version
18
+ self.handle_higher_passed_version(passed_version)
19
+ end
20
+ end
21
+
22
+ def self.pending_migrations
23
+ versions = []
24
+ files = self.get_all_files
25
+ files.each do |file|
26
+ filename, version, klass_name = self.seperate_file_parts(file)
27
+ unless version_has_been_migrated?(version)
28
+ versions << filename
29
+ end
30
+ end
31
+
32
+ versions
33
+ end
34
+
35
+ def self.get_current_version
36
+ result = ActiveRecord::Base.connection.select_all("select max(version) as current_version from data_migrations")
37
+
38
+ current_version = result[0]['current_version'] unless result == -1
39
+
40
+ current_version = current_version.to_i unless current_version.nil?
41
+
42
+ current_version
43
+ end
44
+
45
+ def self.next_migration_number
46
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
47
+ end
48
+
49
+ def self.run_up(passed_version)
50
+ self.setup
51
+
52
+ raise "VERSION is required" unless passed_version
53
+
54
+ files = self.get_all_files
55
+ found = false
56
+
57
+ files.each do |file|
58
+ filename, version, klass_name = self.seperate_file_parts(file)
59
+ if passed_version == version
60
+ found = true
61
+ unless self.version_has_been_migrated?(version)
62
+ self.handle_action(file, klass_name, version, :up)
63
+ else
64
+ puts "** Version #{passed_version} has already been migrated"
65
+ end
66
+ end
67
+ end
68
+
69
+ unless found
70
+ puts "** Version #{passed_version} not found"
71
+ end
72
+
73
+ end
74
+
75
+ def self.run_down(passed_version)
76
+ self.setup
77
+
78
+ raise "VERSION is required" unless passed_version
79
+
80
+ files = self.get_all_files
81
+ found = false
82
+
83
+ files.each do |file|
84
+ filename, version, klass_name = self.seperate_file_parts(file)
85
+ if passed_version == version
86
+ found = true
87
+ if self.version_has_been_migrated?(version)
88
+ self.handle_action(file, klass_name, version, :down)
89
+ else
90
+ puts "** Version #{passed_version} has not been migrated"
91
+ end
92
+ end
93
+ end
94
+
95
+ unless found
96
+ puts "** Version #{passed_version} not found"
97
+ end
98
+
99
+ end
100
+
101
+ def self.prepare_migrations
102
+ target = "#{Rails.root}/db/data_migrations/"
103
+
104
+ # first copy all app data_migrations away
105
+ files = Dir["#{target}*.rb"]
106
+
107
+ unless files.empty?
108
+ FileUtils.mkdir_p "#{target}/ignore/app/"
109
+ FileUtils.cp files, "#{target}/ignore/app/"
110
+ puts "copied #{files.size} data_migrations to db/data_migrations/ignore/app"
111
+ end
112
+
113
+ dirs = Rails::Application::Railties.engines.map{|p| p.config.root.to_s}
114
+ files = Dir["{#{dirs.join(',')}}/db/data_migrations/*.rb"]
115
+
116
+ unless files.empty?
117
+ FileUtils.mkdir_p target
118
+ FileUtils.cp files, target
119
+ puts "copied #{files.size} migrations to db/data_migrations"
120
+ end
121
+ end
122
+
123
+ def self.cleanup_migrations
124
+ target = "#{Rails.root}/db/data_migrations/"
125
+
126
+ files = Dir["#{target}*.rb"]
127
+ unless files.empty?
128
+ FileUtils.rm files
129
+ puts "removed #{files.size} data_migrations from db/data_migrations"
130
+ end
131
+ files = Dir["#{target}/ignore/app/*.rb"]
132
+ unless files.empty?
133
+ FileUtils.cp files, target
134
+ puts "copied #{files.size} data_migrations back to db/data_migrations"
135
+ end
136
+ FileUtils.rm_rf "#{target}/ignore/app"
137
+ end
138
+
139
+ private
140
+
141
+ def self.setup
142
+ unless self.data_migrations_table_exists?
143
+ self.create_data_migrations_table
144
+ end
145
+
146
+ unless File.directory? DEFAULT_MIGRATIONS_PATH
147
+ FileUtils.mkdir_p(DEFAULT_MIGRATIONS_PATH)
148
+ #create ignore folder
149
+ FileUtils.mkdir_p(DEFAULT_MIGRATIONS_PATH + '/ignore/')
150
+ end
151
+ end
152
+
153
+ def self.handle_higher_passed_version(passed_version)
154
+ files = self.get_all_files
155
+
156
+ files.each do |file|
157
+ filename, version, klass_name = self.seperate_file_parts(file)
158
+ if version <= passed_version
159
+ unless self.version_has_been_migrated?(version)
160
+ self.handle_action(file, klass_name, version, :up)
161
+ end
162
+ end
163
+ end
164
+
165
+ end
166
+
167
+ def self.run_all_non_migrated(passed_version)
168
+ files = self.get_all_files
169
+
170
+ files.each do |file|
171
+ filename, version, klass_name = self.seperate_file_parts(file)
172
+ if passed_version.nil? or version <= passed_version
173
+ if !self.version_has_been_migrated?(version)
174
+ self.handle_action(file, klass_name, version, :up)
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ def self.handle_lower_passed_version(passed_version)
181
+ files = self.get_all_files
182
+
183
+ files.each do |file|
184
+ filename, version, klass_name = self.seperate_file_parts(file)
185
+ if version > passed_version
186
+ if self.version_has_been_migrated?(version)
187
+ self.handle_action(file, klass_name, version, :down)
188
+ end
189
+ end
190
+ end
191
+
192
+ end
193
+
194
+ def self.handle_action(file, klass_name, version, action)
195
+ require file
196
+ klass = klass_name.camelize.constantize
197
+ puts "=================Migrating #{klass.to_s} #{action.to_s.upcase}============"
198
+ begin
199
+ time = Benchmark.measure do
200
+ ActiveRecord::Base.transaction do
201
+ klass.send(action.to_s)
202
+ end
203
+ end
204
+ rescue Exception=>ex
205
+ self.cleanup_migrations
206
+ raise ex
207
+ end
208
+ time_str = "(%.4fs)" % time.real
209
+ puts "================Finished #{klass.to_s} in #{time_str}=="
210
+ self.insert_migration_version(version)
211
+ end
212
+
213
+ def self.insert_migration_version(version)
214
+ ActiveRecord::Base.connection.execute("insert into data_migrations (version) values ('#{version}')")
215
+ end
216
+
217
+ def self.remove_migration_version(version)
218
+ ActiveRecord::Base.connection.execute("delete from data_migrations where version = '#{version}'")
219
+ end
220
+
221
+ def self.version_has_been_migrated?(version)
222
+ result = true
223
+
224
+ db_result = ActiveRecord::Base.connection.select_all("select count(*) as num_rows from data_migrations where version = '#{version}'")
225
+
226
+ num_rows = db_result[0]['num_rows'] unless db_result == -1
227
+
228
+ if num_rows.nil? || num_rows.to_i == 0
229
+ result = false
230
+ end
231
+
232
+ result
233
+ end
234
+
235
+ def self.data_migrations_table_exists?
236
+ table_names = ActiveRecord::Base.connection.tables
237
+ table_names.include?('data_migrations')
238
+ end
239
+
240
+ def self.create_data_migrations_table
241
+ puts "** data_migrations table missing creating now...."
242
+ puts ActiveRecord::Migrator.run(:up, File.join(File.dirname(__FILE__),'../db/migrate/'), 20100819181805)
243
+ puts "** done"
244
+ end
245
+
246
+ def self.seperate_file_parts(file)
247
+ paths = file.split('/')
248
+ filename = paths[paths.length - 1]
249
+ version = filename.split('_')[0]
250
+ klass_name = filename.gsub(/#{version}/, "").gsub(/.rb/, "")[1..filename.length]
251
+
252
+ return filename, version.to_i, klass_name
253
+ end
254
+
255
+ def self.get_all_files
256
+ files = []
257
+
258
+ if File.directory? DEFAULT_MIGRATIONS_PATH
259
+
260
+ files_or_directories = Dir.entries(DEFAULT_MIGRATIONS_PATH).map{|directory| directory}
261
+
262
+ files_or_directories.delete_if{|name| name =~ REMOVE_FILES_REGEX} #remove any file leading with . or ..
263
+ files_or_directories.delete_if{|name| name == 'ignore'} #ignore the ignore folder
264
+
265
+
266
+ files_or_directories.each do |file_or_directory|
267
+ file_or_directory = DEFAULT_MIGRATIONS_PATH + "/" + file_or_directory
268
+ files = self.get_files_in_directory(file_or_directory, files)
269
+ end
270
+
271
+ files.sort! {|x,y| self.get_file_name(x) <=> self.get_file_name(y)}
272
+ end
273
+
274
+ files
275
+ end
276
+
277
+ def self.get_files_in_directory(file_or_directory, files)
278
+ unless file_or_directory =~ /\w\.rb/
279
+ files_or_directories = Dir.entries(file_or_directory).map{|directory| directory}
280
+
281
+ files_or_directories.delete_if{|name| name =~ REMOVE_FILES_REGEX} #remove any file leading with . or ..
282
+ files_or_directories.delete_if{|name| name == 'ignore'} #ignore the ignore folder
283
+
284
+ files_or_directories.each do |_file_or_directory|
285
+ _file_or_directory = file_or_directory + "/" + _file_or_directory
286
+ files = self.get_files_in_directory(_file_or_directory, files)
287
+ end
288
+ else
289
+ files << file_or_directory
290
+ end
291
+
292
+ files
293
+ end
294
+
295
+ def self.get_file_name(dir)
296
+ file_name = dir
297
+
298
+ paths = dir.split('/')
299
+ file_name = paths[paths.length - 1]
300
+
301
+ file_name
302
+ end
303
+ end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Used to generate migration to insert data into database
3
+ To run the migration db:migrate_data
4
+
5
+ Example:
6
+ rails g data_migration my_new_migration
7
+
8
+ This will create:
9
+ db/data_migrations/[timestamp]_my_new_migration.rb
@@ -0,0 +1,7 @@
1
+ class DataMigrationGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('../templates', __FILE__)
3
+
4
+ def generate_layout
5
+ template "migration_template.rb", "db/data_migrations/#{DataMigrator.next_migration_number}_#{file_name}.rb"
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ class <%=class_name %>
2
+
3
+ def self.up
4
+ #insert data here
5
+ end
6
+
7
+ def self.down
8
+ #remove data here
9
+ end
10
+
11
+ end
@@ -0,0 +1,54 @@
1
+ namespace :db do
2
+ desc 'migrates data into database'
3
+ task :migrate_data => :environment do
4
+
5
+ passed_version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
6
+ DataMigrator.prepare_migrations
7
+ DataMigrator.migrate(nil, passed_version)
8
+ DataMigrator.cleanup_migrations
9
+
10
+ end#end task
11
+
12
+ namespace :migrate_data do
13
+ task :list_pending => :environment do
14
+ DataMigrator.prepare_migrations
15
+ pending_migrations = DataMigrator.pending_migrations
16
+ puts "================Pending Data Migrations=========="
17
+ puts pending_migrations
18
+ puts "================================================="
19
+ DataMigrator.cleanup_migrations
20
+ end
21
+
22
+ task :version => :environment do
23
+ version = DataMigrator.get_current_version
24
+
25
+ unless version.nil?
26
+ puts "** Current version #{version}"
27
+ else
28
+ puts "** No migrations ran"
29
+ end
30
+ end
31
+
32
+ task :up => :environment do
33
+ passed_version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
34
+ raise "VERSION is required" unless passed_version
35
+
36
+ DataMigrator.prepare_migrations
37
+ DataMigrator.run_up(passed_version)
38
+ DataMigrator.cleanup_migrations
39
+
40
+ end#end task
41
+
42
+ task :down => :environment do
43
+ passed_version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
44
+ raise "VERSION is required" unless passed_version
45
+
46
+ DataMigrator.prepare_migrations
47
+ DataMigrator.run_down(passed_version)
48
+ DataMigrator.cleanup_migrations
49
+
50
+ end#end task
51
+ end#end namespace
52
+ end
53
+
54
+
@@ -0,0 +1,3 @@
1
+ module DataMigrator
2
+ VERSION = '0.0.1' unless defined?(::DataMigrator::VERSION)
3
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+
3
+ class DataMigratorTest < ActiveSupport::TestCase
4
+ # Replace this with your real tests.
5
+ test "the truth" do
6
+ assert true
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'active_support/test_case'
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: data_migrator
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Russell Holmes, Adam Hull
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-19 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 9
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 5
34
+ version: 2.3.5
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: Allows you to create data migrations that can be run up and down to insert data into the database.
38
+ email: russellfholmes@gmail.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - db/migrate/20100819181805_create_data_migrations_table.rb
47
+ - lib/data_migrator.rb
48
+ - lib/generators/data_migration/data_migration_generator.rb
49
+ - lib/generators/data_migration/templates/migration_template.rb
50
+ - lib/generators/data_migration/USAGE
51
+ - lib/tasks/data_migrator.rake
52
+ - lib/version.rb
53
+ - MIT-LICENSE
54
+ - README.rdoc
55
+ - test/data_migrator_test.rb
56
+ - test/test_helper.rb
57
+ has_rdoc: true
58
+ homepage: https://github.com/portablemind/data_migrator
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options: []
63
+
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.3.7
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Creates Data Migrations for data similar to schema migrations.
91
+ test_files:
92
+ - test/data_migrator_test.rb
93
+ - test/test_helper.rb