activerecord-conversion 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activerecord-conversion.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (C) 2012 Maximilian Schneider
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Activerecord::Conversion
2
+
3
+ Aids you in migrating legacy model data.
4
+ Saves its progress in the database and is able to resume after interruptions.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'activerecord-conversion'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install activerecord-conversion
19
+
20
+ ## Usage
21
+
22
+ class UserConversion < ActiveRecord::Conversion
23
+ source "old_users"
24
+ target "new_users"
25
+
26
+ def self.convert old_user
27
+ # create and return a new user
28
+ end
29
+ end
30
+
31
+ UserConversion.run
32
+
33
+ ## Contributing
34
+
35
+ 1. Fork it
36
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
37
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
38
+ 4. Push to the branch (`git push origin my-new-feature`)
39
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ task :default => :spec
6
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+
5
+ require "activerecord/conversion/version"
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.authors = ["Maximilian Schneider"]
9
+ gem.email = ["mail@maximilianschneider.net"]
10
+ gem.description = "Aids you in migrating legacy model data. Saves its progress in the database and is able to resume after interruptions."
11
+ gem.summary = "Simple data migration based on ActiveRecord"
12
+ gem.homepage = "https://github.com/mschneider/activerecord-conversion"
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.name = "activerecord-conversion"
18
+ gem.require_paths = ["lib"]
19
+ gem.version = ActiveRecord::Conversion::VERSION
20
+
21
+ gem.add_runtime_dependency 'activerecord', '~> 3.2.3'
22
+ gem.add_development_dependency 'rspec'
23
+ gem.add_development_dependency 'sqlite3'
24
+ end
@@ -0,0 +1,85 @@
1
+ #encoding: utf-8
2
+
3
+ require "active_record"
4
+
5
+ module ActiveRecord
6
+ class Conversion
7
+
8
+ class << self
9
+ attr_accessor :source, :target
10
+
11
+ %w[source target].each do |field|
12
+ define_method "#{field}_foreign_key" do
13
+ send(field).singularize.foreign_key
14
+ end
15
+
16
+ define_method "#{field}_class" do
17
+ send(field).classify.constantize
18
+ end
19
+ end
20
+
21
+ def record_class
22
+ this = self
23
+ @record_class ||= Class.new(ActiveRecord::Base) do
24
+ self.table_name = this.record_table_name
25
+ end
26
+ end
27
+
28
+ def record_table_name
29
+ self.to_s.tableize.singularize
30
+ end
31
+
32
+ def create_record_table
33
+ record_class.connection.execute(
34
+ "CREATE TABLE IF NOT EXISTS '#{record_table_name}'('id' INTEGER, "+
35
+ "'#{source_foreign_key}' INTEGER, '#{target_foreign_key}' INTEGER, "+
36
+ "PRIMARY KEY('#{source_foreign_key}'))")
37
+ end
38
+
39
+ def define_relation_to(model)
40
+ record_class.belongs_to(model)
41
+ record_class.validates(model, presence: true)
42
+ end
43
+
44
+ def define_relations
45
+ [source, target].each do |relation|
46
+ model_name = relation.singularize
47
+ define_relation_to(model_name)
48
+ end
49
+ end
50
+
51
+ def remaining_items
52
+ source_class.where(
53
+ ["#{source_class.primary_key} NOT IN (SELECT #{source_foreign_key} FROM :records)",
54
+ records: record_table_name])
55
+ end
56
+
57
+ def convert source_item
58
+ raise "implement self.convert in your subclass"
59
+ end
60
+
61
+ def migrate source_item
62
+ target_item = convert(source_item)
63
+ progress_record = record_class.create!({ source_foreign_key => source_item.id,
64
+ target_foreign_key => target_item.id })
65
+ end
66
+
67
+ def resume
68
+ remaining_items.each do |source_item|
69
+ ActiveRecord::Base.transaction do
70
+ migrate source_item
71
+ end
72
+ end
73
+ end
74
+
75
+ def run
76
+ define_relations
77
+ create_record_table
78
+ resume
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ class Conversion
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,126 @@
1
+ #encoding: utf-8
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
5
+ Bundler.require
6
+ require "activerecord/conversion"
7
+
8
+ #require 'logger'
9
+ #ActiveRecord::Base.logger = Logger.new(STDOUT)
10
+
11
+ class SourceRecord < ActiveRecord::Base;end
12
+ class TargetRecord < ActiveRecord::Base;end
13
+
14
+ class ExampleConversion < ActiveRecord::Conversion
15
+ self.source = "source_records"
16
+ self.target = "target_records"
17
+
18
+ def self.convert source_record
19
+ TargetRecord.create!(source_record.attributes)
20
+ end
21
+ end
22
+
23
+ class MinimalConversion < ActiveRecord::Conversion
24
+
25
+ end
26
+
27
+ RSpec.configure do |config|
28
+ config.color_enabled = true
29
+ config.formatter = :documentation
30
+
31
+ config.before :suite do
32
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
33
+ ActiveRecord::Base.connection.execute "CREATE TABLE IF NOT EXISTS source_records(id INTEGER, name STRING, PRIMARY KEY(id))"
34
+ ActiveRecord::Base.connection.execute "CREATE TABLE IF NOT EXISTS target_records(id INTEGER, name STRING, PRIMARY KEY(id))"
35
+ end
36
+
37
+ config.after :suite do
38
+ ActiveRecord::Base.connection.execute "DROP TABLE source_records"
39
+ ActiveRecord::Base.connection.execute "DROP TABLE target_records"
40
+ ActiveRecord::Base.connection.execute "DROP TABLE example_conversion"
41
+ end
42
+ end
43
+
44
+
45
+ describe ActiveRecord::Conversion do
46
+ context MinimalConversion do
47
+ it "should raise an exception when convert is not defined" do
48
+ lambda { MinimalConversion.convert(nil) }.should raise_error(RuntimeError,
49
+ "implement self.convert in your subclass")
50
+ end
51
+ end
52
+
53
+ before(:all) { ExampleConversion.create_record_table }
54
+
55
+ before do
56
+ SourceRecord.all.each(&:delete)
57
+ TargetRecord.all.each(&:delete)
58
+ ExampleConversion.record_class.all.each(&:delete)
59
+ end
60
+
61
+ it "should define all relationships, create a table and resume the migration when run" do
62
+ ExampleConversion.should_receive(:define_relations).ordered
63
+ ExampleConversion.should_receive(:create_record_table).ordered
64
+ ExampleConversion.should_receive(:resume).ordered
65
+ ExampleConversion.run
66
+ end
67
+
68
+
69
+ it "should setup the record_class when defining relations" do
70
+ ExampleConversion.record_class.should_receive(:belongs_to).with("source_record")
71
+ ExampleConversion.record_class.should_receive(:validates).with("source_record", presence: true)
72
+ ExampleConversion.record_class.should_receive(:belongs_to).with("target_record")
73
+ ExampleConversion.record_class.should_receive(:validates).with("target_record", presence: true)
74
+
75
+ ExampleConversion.define_relations
76
+ end
77
+
78
+ it "should execute a CREATE TABLE statement when creating the record table" do
79
+ @connection = double("connection")
80
+ ExampleConversion.record_class.stub(:connection).and_return(@connection)
81
+ @connection.should_receive(:execute).with(
82
+ "CREATE TABLE IF NOT EXISTS 'example_conversion'('id' INTEGER, "+
83
+ "'source_record_id' INTEGER, 'target_record_id' INTEGER, "+
84
+ "PRIMARY KEY('source_record_id'))")
85
+ ExampleConversion.create_record_table
86
+ end
87
+
88
+ it "resumes by migrating all remaining_items" do
89
+ @source_items = [double("source_record1"), double("source_record2")]
90
+ ExampleConversion.should_receive(:remaining_items).and_return(@source_items)
91
+ ExampleConversion.should_receive(:migrate).with(@source_items.first)
92
+ ExampleConversion.should_receive(:migrate).with(@source_items.second)
93
+ ExampleConversion.resume
94
+ end
95
+
96
+ it "migrates source items by converting them and saving a mapping record" do
97
+ source_item = double("source_record", id: :source_id)
98
+ target_item = double("target_record", id: :target_id)
99
+ ExampleConversion.should_receive(:convert).with(source_item).and_return(target_item)
100
+ ExampleConversion.record_class.should_receive(:create!).with({
101
+ "source_record_id" => :source_id, "target_record_id" => :target_id })
102
+ ExampleConversion.migrate(source_item)
103
+ end
104
+
105
+ context "#record_class" do
106
+ subject { MinimalConversion.record_class }
107
+
108
+ its(:superclass) { should be(ActiveRecord::Base) }
109
+ its(:table_name) { should == "minimal_conversion" }
110
+ end
111
+
112
+ context "#remaining_items" do
113
+ before do
114
+ @first = SourceRecord.create!(name: "already migrated")
115
+ ExampleConversion.migrate(@first)
116
+ @second = SourceRecord.create!(name: "not migrated")
117
+ end
118
+
119
+ subject { ExampleConversion.remaining_items.all }
120
+
121
+ it { should_not include(@first) }
122
+ it { should include(@second) }
123
+ end
124
+
125
+ end
126
+
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-conversion
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Maximilian Schneider
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-10 00:00:00.000000000 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: &2157775120 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 3.2.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2157775120
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: &2157774700 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *2157774700
37
+ - !ruby/object:Gem::Dependency
38
+ name: sqlite3
39
+ requirement: &2157774240 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2157774240
48
+ description: Aids you in migrating legacy model data. Saves its progress in the database
49
+ and is able to resume after interruptions.
50
+ email:
51
+ - mail@maximilianschneider.net
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - .gitignore
57
+ - Gemfile
58
+ - LICENSE
59
+ - README.md
60
+ - Rakefile
61
+ - activerecord-conversion.gemspec
62
+ - lib/activerecord/conversion.rb
63
+ - lib/activerecord/conversion/version.rb
64
+ - spec/conversion_spec.rb
65
+ has_rdoc: true
66
+ homepage: https://github.com/mschneider/activerecord-conversion
67
+ licenses: []
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 1.6.2
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Simple data migration based on ActiveRecord
90
+ test_files:
91
+ - spec/conversion_spec.rb