mover_postgres 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ .DS_Store
2
+ *.gem
3
+ coverage
4
+ pkg
5
+ spec/config/database.yml
6
+ spec/log
7
+ tmp
8
+ .rvmrc
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2010
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ 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, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS 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.md ADDED
@@ -0,0 +1,101 @@
1
+ Mover
2
+ =====
3
+
4
+ Move ActiveRecord records across tables like it ain't no thang.
5
+
6
+ Requirements
7
+ ------------
8
+
9
+ <pre>
10
+ sudo gem install mover
11
+ </pre>
12
+
13
+ Move records
14
+ ------------
15
+
16
+ Move the last article:
17
+
18
+ <pre>
19
+ Article.last.move_to(ArticleArchive)
20
+ </pre>
21
+
22
+ Move today's articles:
23
+
24
+ <pre>
25
+ Article.move_to(
26
+ ArticleArchive,
27
+ :conditions => [ "created_at > ?", Date.today ]
28
+ )
29
+ </pre>
30
+
31
+ The two tables do not have to be identical. Only shared columns transfer.
32
+
33
+ If a primary key collision occurs, the destination record is updated.
34
+
35
+ Callbacks
36
+ ---------
37
+
38
+ In this example, we want an "archive" table for articles and comments.
39
+
40
+ We also want the article's comments to be archived when the article is.
41
+
42
+ <pre>
43
+ class Article &lt; ActiveRecord::Base
44
+ has_many :comments
45
+ before_move :ArticleArchive do
46
+ comments.each { |c| c.move_to(CommentArchive) }
47
+ end
48
+ end
49
+
50
+ class ArticleArchive &lt; ActiveRecord::Base
51
+ has_many :comments, :class_name => 'CommentArchive', :foreign_key => 'article_id'
52
+ before_move :Article do
53
+ comments.each { |c| c.move_to(Comment) }
54
+ end
55
+ end
56
+
57
+ class Comment &lt; ActiveRecord::Base
58
+ belongs_to :article
59
+ end
60
+
61
+ class CommentArchive &lt; ActiveRecord::Base
62
+ belongs_to :article, :class_name => 'ArticleArchive', :foreign_key => 'article_id'
63
+ end
64
+ </pre>
65
+
66
+ The <code>after\_move</code> callback is also available.
67
+
68
+ Magic column
69
+ ------------
70
+
71
+ If a table contains a <code>moved_at</code> column, it will magically populate with the date and time it was moved.
72
+
73
+ Options
74
+ -------
75
+
76
+ There are other options, in addition to <code>conditions</code>:
77
+
78
+ <pre>
79
+ Article.move_to(
80
+ ArticleArchive,
81
+ :copy => true, # Do not delete Article after move
82
+ :generic => true, # UPDATE using a JOIN instead of ON DUPLICATE KEY UPDATE (default: false on MySQL engines)
83
+ :magic => 'updated_at', # Custom magic column
84
+ :migrate => true, # Copies the original value of the magic column
85
+ :quick => true # You are certain only INSERTs are necessary, no primary key collisions possible
86
+ # May only be a little faster on MySQL, but dramatically faster on other engines
87
+ )
88
+ </pre>
89
+
90
+ You can access these options from callbacks using <code>move_options</code>.
91
+
92
+ Reserve a spot
93
+ --------------
94
+
95
+ Before you create a record, you can "reserve a spot" on a table that you will move the record to later.
96
+
97
+ <pre>
98
+ archive = ArticleArchive.new
99
+ archive.id = Article.reserve_id
100
+ archive.save
101
+ </pre>
data/Rakefile ADDED
@@ -0,0 +1,90 @@
1
+ require File.dirname(__FILE__) + '/lib/mover/gems'
2
+
3
+ Mover::Gems.activate %w(rake rspec)
4
+
5
+ require 'rake'
6
+ require 'spec/rake/spectask'
7
+
8
+ def gemspec
9
+ @gemspec ||= begin
10
+ file = File.expand_path('../mover.gemspec', __FILE__)
11
+ eval(File.read(file), binding, file)
12
+ end
13
+ end
14
+
15
+ if defined?(Spec::Rake::SpecTask)
16
+ desc "Run specs"
17
+ Spec::Rake::SpecTask.new do |t|
18
+ t.spec_files = FileList['spec/**/*_spec.rb']
19
+ t.spec_opts = %w(-fs --color)
20
+ t.warning = true
21
+ end
22
+ task :spec
23
+ task :default => :spec
24
+ end
25
+
26
+ desc "Build gem(s)"
27
+ task :gem do
28
+ old_gemset = ENV['GEMSET']
29
+ root = File.expand_path('../', __FILE__)
30
+ pkg = "#{root}/pkg"
31
+ system "rm -Rf #{pkg}"
32
+ Mover::Gems.gemset_names.each do |gemset|
33
+ ENV['GEMSET'] = gemset.to_s
34
+ system "cd #{root} && gem build mover.gemspec"
35
+ system "mkdir -p #{pkg} && mv *.gem pkg"
36
+ end
37
+ ENV['GEMSET'] = old_gemset
38
+ end
39
+
40
+ namespace :gem do
41
+ desc "Install gem(s)"
42
+ task :install do
43
+ Rake::Task['gem'].invoke
44
+ Dir["#{File.dirname(__FILE__)}/pkg/*.gem"].each do |pkg|
45
+ system "gem install #{pkg} --no-ri --no-rdoc"
46
+ end
47
+ end
48
+
49
+ desc "Push gem(s)"
50
+ task :push do
51
+ Rake::Task['gem'].invoke
52
+ Dir["#{File.dirname(__FILE__)}/pkg/*.gem"].each do |pkg|
53
+ system "gem push #{pkg}"
54
+ end
55
+ end
56
+ end
57
+
58
+ namespace :gems do
59
+ desc "Install gem dependencies (DEV=0 DOCS=0 GEMSPEC=default SUDO=0)"
60
+ task :install do
61
+ dev = ENV['DEV'] == '1'
62
+ docs = ENV['DOCS'] == '1' ? '' : '--no-ri --no-rdoc'
63
+ gemset = ENV['GEMSET']
64
+ sudo = ENV['SUDO'] == '1' ? 'sudo' : ''
65
+
66
+ Mover::Gems.gemset = gemset if gemset
67
+
68
+ if dev
69
+ gems = Mover::Gems.gemspec.development_dependencies
70
+ else
71
+ gems = Mover::Gems.gemspec.dependencies
72
+ end
73
+
74
+ gems.each do |name|
75
+ name = name.to_s
76
+ version = Mover::Gems.versions[name]
77
+ if Gem.source_index.find_name(name, version).empty?
78
+ version = version ? "-v #{version}" : ''
79
+ system "#{sudo} gem install #{name} #{version} #{docs}"
80
+ else
81
+ puts "already installed: #{name} #{version}"
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ desc "Validate the gemspec"
88
+ task :gemspec do
89
+ gemspec.validate
90
+ end
@@ -0,0 +1,5 @@
1
+ mover:
2
+
3
+ rspec: ~>1.0
4
+ default:
5
+ active_wrapper: =0.4.4
@@ -0,0 +1,13 @@
1
+ name: mover_postgres
2
+ version: 0.3.6
3
+ authors:
4
+ - Winton Welsh
5
+ email: james@njtechnologies.co.uk
6
+ homepage: https://github.com/jamesharker/mover
7
+ summary: Move ActiveRecord records across tables like it ain't no thang
8
+ description: Move ActiveRecord records across tables like it ain't no thang
9
+ dependencies: null
10
+ development_dependencies:
11
+ - active_wrapper
12
+ - rake
13
+ - rspec
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
data/lib/mover.rb ADDED
@@ -0,0 +1,213 @@
1
+ require File.dirname(__FILE__) + '/mover/gems'
2
+
3
+ $:.unshift File.dirname(__FILE__)
4
+
5
+ module Mover
6
+
7
+ def self.included(base)
8
+ unless base.included_modules.include?(InstanceMethods)
9
+ base.extend ClassMethods
10
+ base.send :include, InstanceMethods
11
+ base.send :attr_accessor, :move_options
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ def after_move(*to_class, &block)
18
+ @after_move ||= []
19
+ @after_move << [ to_class, block ]
20
+ end
21
+
22
+ def before_move(*to_class, &block)
23
+ @before_move ||= []
24
+ @before_move << [ to_class, block ]
25
+ end
26
+
27
+ def move_to(to_class, options={})
28
+ from_class = self
29
+
30
+ # Conditions
31
+ conditions = options[:conditions] || '1'
32
+ conditions = self.sanitize_sql(conditions)
33
+ where = "WHERE #{conditions}"
34
+
35
+ # Columns
36
+ magic = options[:magic] || 'moved_at'
37
+ from = {
38
+ :columns => from_class.column_names,
39
+ :table => from_class.table_name
40
+ }
41
+ to = {
42
+ :columns => to_class.column_names,
43
+ :table => to_class.table_name
44
+ }
45
+
46
+ # insert[column] = value
47
+ insert = (from[:columns] & to[:columns]).inject({}) do |hash, column|
48
+ if column == magic && !options[:migrate]
49
+ hash[column] = Time.now.utc
50
+ else
51
+ hash[column] = column
52
+ end
53
+ hash
54
+ end
55
+
56
+ # Magic column not in "from" table
57
+ if to[:columns].include?(magic) && !from[:columns].include?(magic)
58
+ insert[magic] = Time.now.utc
59
+ end
60
+
61
+ # Quote everything
62
+ insert = insert.inject({}) do |hash, (column, value)|
63
+ if value.is_a?(::Time)
64
+ hash[connection.quote_column_name(column)] = connection.quote(value)
65
+ else
66
+ hash[connection.quote_column_name(column)] = connection.quote_column_name(value)
67
+ end
68
+ hash
69
+ end
70
+
71
+ # Callbacks
72
+ collector = lambda do |(classes, block)|
73
+ classes.collect! { |c| eval(c.to_s) }
74
+ block if classes.include?(to_class) || classes.empty?
75
+ end
76
+ before = (@before_move || []).collect(&collector).compact
77
+ after = (@after_move || []).collect(&collector).compact
78
+
79
+ # Instances
80
+ instances =
81
+ if options[:instance]
82
+ [ options[:instance] ]
83
+ elsif before.empty? && after.empty?
84
+ []
85
+ else
86
+ self.find(:all, :conditions => conditions)
87
+ end
88
+ options.delete(:instance)
89
+
90
+ # Callback executor
91
+ exec_callbacks = lambda do |callbacks|
92
+ callbacks.each do |block|
93
+ instances.each do |instance|
94
+ instance.move_options = options
95
+ instance.instance_eval(&block)
96
+ instance.move_options = nil
97
+ end
98
+ end
99
+ end
100
+
101
+ # Execute
102
+ transaction do
103
+ exec_callbacks.call before
104
+
105
+ if options[:quick]
106
+ connection.execute(<<-SQL)
107
+ INSERT INTO #{to[:table]} (#{insert.keys.join(', ')})
108
+ SELECT #{insert.values.join(', ')}
109
+ FROM #{from[:table]}
110
+ #{where}
111
+ SQL
112
+ elsif !options[:generic] && connection.class.to_s.include?('Mysql')
113
+ update = insert.collect do |column, value|
114
+ if value.include?("'")
115
+ "#{to[:table]}.#{column} = #{value}"
116
+ else
117
+ "#{to[:table]}.#{column} = #{from[:table]}.#{value}"
118
+ end
119
+ end
120
+
121
+ connection.execute(<<-SQL)
122
+ INSERT INTO #{to[:table]} (#{insert.keys.join(', ')})
123
+ SELECT #{insert.values.join(', ')}
124
+ FROM #{from[:table]}
125
+ #{where}
126
+ ON DUPLICATE KEY
127
+ UPDATE #{update.join(', ')};
128
+ SQL
129
+ else
130
+ conditions.gsub!(to[:table], 't')
131
+ conditions.gsub!(from[:table], 'f')
132
+ conditions.gsub!(/\"id\"/,'f.id') if connection.class.to_s.include?('PostgreSQL')
133
+
134
+ select = insert.values.collect { |i| i.include?("'") ? i : "f.#{i}" }
135
+ set = insert.collect do |column, value|
136
+ prefix = 't.' unless connection.class.to_s.include?('PostgreSQL')
137
+ if value.include?("'")
138
+ "#{prefix}#{column} = #{value}"
139
+ else
140
+ "#{prefix}#{column} = f.#{value}"
141
+ end
142
+ end
143
+
144
+ if connection.class.to_s.include?('PostgreSQL')
145
+ connection.execute(<<-SQL)
146
+ UPDATE #{to[:table]}
147
+ AS t
148
+ SET #{set.join(', ')}
149
+ FROM #{from[:table]}
150
+ AS f
151
+ WHERE f.id = t.id
152
+ AND #{conditions}
153
+ SQL
154
+ else
155
+ connection.execute(<<-SQL)
156
+ UPDATE #{to[:table]}
157
+ AS t
158
+ INNER JOIN #{from[:table]}
159
+ AS f
160
+ ON f.id = t.id
161
+ AND #{conditions}
162
+ SET #{set.join(', ')}
163
+ SQL
164
+ end
165
+
166
+ connection.execute(<<-SQL)
167
+ INSERT INTO #{to[:table]} (#{insert.keys.join(', ')})
168
+ SELECT #{select.join(', ')}
169
+ FROM #{from[:table]}
170
+ AS f
171
+ LEFT OUTER JOIN #{to[:table]}
172
+ AS t
173
+ ON f.id = t.id
174
+ WHERE (
175
+ t.id IS NULL
176
+ AND #{conditions}
177
+ )
178
+ SQL
179
+ end
180
+
181
+ unless options[:copy]
182
+ connection.execute("DELETE FROM #{from[:table]} #{where}")
183
+ end
184
+
185
+ exec_callbacks.call after
186
+ end
187
+ end
188
+
189
+ def reserve_id
190
+ id = nil
191
+ transaction do
192
+ if connection.class.to_s.include?('PostgreSQL')
193
+ id = connection.insert("INSERT INTO #{self.table_name} (id) VALUES (nextval('#{self.table_name}_id_seq'::regclass))").to_i
194
+ else
195
+ id = connection.insert("INSERT INTO #{self.table_name} () VALUES ()")
196
+ end
197
+ connection.execute("DELETE FROM #{self.table_name} WHERE id = #{id}") if id
198
+ end
199
+ id
200
+ end
201
+ end
202
+
203
+ module InstanceMethods
204
+
205
+ def move_to(to_class, options={})
206
+ options[:conditions] = "#{self.class.table_name}.#{self.class.primary_key} = #{id}"
207
+ options[:instance] = self
208
+ self.class.move_to(to_class, options)
209
+ end
210
+ end
211
+ end
212
+
213
+ ActiveRecord::Base.send(:include, Mover)
data/lib/mover/gems.rb ADDED
@@ -0,0 +1,154 @@
1
+ unless defined?(Mover::Gems)
2
+
3
+ require 'yaml'
4
+
5
+ module Mover
6
+ module Gems
7
+ class <<self
8
+
9
+ attr_accessor :config
10
+ attr_reader :gemset, :gemsets, :versions
11
+
12
+ class SimpleStruct
13
+ attr_reader :hash
14
+
15
+ def initialize(hash)
16
+ @hash = hash
17
+ @hash.each do |key, value|
18
+ self.class.send(:define_method, key) { @hash[key] }
19
+ self.class.send(:define_method, "#{key}=") { |v| @hash[key] = v }
20
+ end
21
+ end
22
+ end
23
+
24
+ Gems.config = SimpleStruct.new(
25
+ :gemsets => [ "#{File.expand_path('../../../', __FILE__)}/config/gemsets.yml" ],
26
+ :gemspec => "#{File.expand_path('../../../', __FILE__)}/config/gemspec.yml",
27
+ :warn => true
28
+ )
29
+
30
+ def activate(*gems)
31
+ begin
32
+ require 'rubygems' unless defined?(::Gem)
33
+ rescue LoadError
34
+ puts "rubygems library could not be required" if @config.warn
35
+ end
36
+
37
+ self.gemset ||= gemset_from_loaded_specs
38
+
39
+ gems.flatten.collect(&:to_sym).each do |name|
40
+ version = @versions[name]
41
+ vendor = File.expand_path("../../../vendor/#{name}/lib", __FILE__)
42
+ if File.exists?(vendor)
43
+ $:.unshift vendor
44
+ elsif defined?(gem)
45
+ gem name.to_s, version
46
+ else
47
+ puts "#{name} #{"(#{version})" if version} failed to activate" if @config.warn
48
+ end
49
+ end
50
+ end
51
+
52
+ def dependencies
53
+ dependency_filter(@gemspec.dependencies, @gemset)
54
+ end
55
+
56
+ def development_dependencies
57
+ dependency_filter(@gemspec.development_dependencies, @gemset)
58
+ end
59
+
60
+ def gemset=(gemset)
61
+ if gemset
62
+ @gemset = gemset.to_sym
63
+
64
+ @gemsets = @config.gemsets.reverse.collect { |config|
65
+ if config.is_a?(::String)
66
+ YAML::load(File.read(config)) rescue {}
67
+ elsif config.is_a?(::Hash)
68
+ config
69
+ end
70
+ }.inject({}) do |hash, config|
71
+ deep_merge(hash, symbolize_keys(config))
72
+ end
73
+
74
+ @versions = (@gemsets[gemspec.name.to_sym] || {}).inject({}) do |hash, (key, value)|
75
+ if !value.is_a?(::Hash) && value
76
+ hash[key] = value
77
+ elsif key == @gemset
78
+ (value || {}).each { |k, v| hash[k] = v }
79
+ end
80
+ hash
81
+ end
82
+ else
83
+ @gemset = nil
84
+ @gemsets = nil
85
+ @versions = nil
86
+ end
87
+ end
88
+
89
+ def gemset_names
90
+ (
91
+ [ :default ] +
92
+ @gemsets[gemspec.name.to_sym].inject([]) { |array, (key, value)|
93
+ array.push(key) if value.is_a?(::Hash) || value.nil?
94
+ array
95
+ }
96
+ ).uniq
97
+ end
98
+
99
+ def gemspec(reload=false)
100
+ if @gemspec && !reload
101
+ @gemspec
102
+ else
103
+ data = YAML::load(File.read(@config.gemspec)) rescue {}
104
+ @gemspec = SimpleStruct.new(data)
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def deep_merge(first, second)
111
+ merger = lambda do |key, v1, v2|
112
+ Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
113
+ end
114
+ first.merge(second, &merger)
115
+ end
116
+
117
+ def dependency_filter(dependencies, match)
118
+ (dependencies || []).inject([]) { |array, value|
119
+ if value.is_a?(::Hash)
120
+ array += value[match.to_s] if value[match.to_s]
121
+ else
122
+ array << value
123
+ end
124
+ array
125
+ }.uniq.collect(&:to_sym)
126
+ end
127
+
128
+ def gemset_from_loaded_specs
129
+ if defined?(Gem)
130
+ Gem.loaded_specs.each do |name, spec|
131
+ if name == gemspec.name
132
+ return :default
133
+ elsif name[0..gemspec.name.length] == "#{gemspec.name}-"
134
+ return name[gemspec.name.length+1..-1].to_sym
135
+ end
136
+ end
137
+ :default
138
+ else
139
+ :none
140
+ end
141
+ end
142
+
143
+ def symbolize_keys(hash)
144
+ return {} unless hash.is_a?(::Hash)
145
+ hash.inject({}) do |options, (key, value)|
146
+ value = symbolize_keys(value) if value.is_a?(::Hash)
147
+ options[(key.to_sym rescue key) || key] = value
148
+ options
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
data/mover.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ root = File.expand_path('../', __FILE__)
3
+ lib = "#{root}/lib"
4
+ $:.unshift lib unless $:.include?(lib)
5
+
6
+ require 'mover/gems'
7
+ Mover::Gems.gemset ||= ENV['GEMSET'] || :default
8
+
9
+ Gem::Specification.new do |s|
10
+ Mover::Gems.gemspec.hash.each do |key, value|
11
+ if key == 'name' && Mover::Gems.gemset != :default
12
+ s.name = "#{value}-#{Mover::Gems.gemset}"
13
+ elsif key == 'summary' && Mover::Gems.gemset == :solo
14
+ s.summary = value + " (no dependencies)"
15
+ elsif !%w(dependencies development_dependencies).include?(key)
16
+ s.send "#{key}=", value
17
+ end
18
+ end
19
+
20
+ Mover::Gems.dependencies.each do |g|
21
+ s.add_dependency g.to_s, Mover::Gems.versions[g]
22
+ end
23
+
24
+ Mover::Gems.development_dependencies.each do |g|
25
+ s.add_development_dependency g.to_s, Mover::Gems.versions[g]
26
+ end
27
+
28
+ s.executables = `cd #{root} && git ls-files -- {bin}/*`.split("\n").collect { |f| File.basename(f) }
29
+ s.files = `cd #{root} && git ls-files`.split("\n")
30
+ s.require_paths = %w(lib)
31
+ s.test_files = `cd #{root} && git ls-files -- {features,test,spec}/*`.split("\n")
32
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/mover')
data/spec/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/mover/gems')
2
+ Mover::Gems.activate(:active_wrapper)
3
+
4
+ require 'active_wrapper/tasks'
5
+
6
+ ActiveWrapper::Tasks.new(
7
+ :base => File.dirname(__FILE__),
8
+ :env => ENV['ENV']
9
+ )
@@ -0,0 +1,6 @@
1
+ test:
2
+ adapter: mysql
3
+ database: mover
4
+ username: root
5
+ password:
6
+ host: localhost
@@ -0,0 +1,28 @@
1
+ class CreateFixtures < ActiveRecord::Migration
2
+ def self.up
3
+ [ :articles, :article_archives ].each do |table|
4
+ create_table table do |t|
5
+ t.string :title
6
+ t.string :body
7
+ t.boolean :read
8
+ t.datetime :moved_at
9
+ end
10
+ end
11
+
12
+ [ :comments, :comment_archives ].each do |table|
13
+ create_table table do |t|
14
+ t.string :title
15
+ t.string :body
16
+ t.boolean :read
17
+ t.integer :article_id
18
+ t.datetime :moved_at
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.down
24
+ [ :articles, :article_archives, :comments, :comment_archives ].each do |table|
25
+ drop_table table
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ class Article < ActiveRecord::Base
2
+ has_many :comments
3
+ before_move :ArticleArchive do
4
+ comments.each { |c| c.move_to(CommentArchive, move_options) }
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class ArticleArchive < ActiveRecord::Base
2
+ has_many :comments, :class_name => 'CommentArchive', :foreign_key => 'article_id'
3
+ before_move :Article do
4
+ comments.each { |c| c.move_to(Comment, move_options) }
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ class Comment < ActiveRecord::Base
2
+ belongs_to :article
3
+ end
@@ -0,0 +1,3 @@
1
+ class CommentArchive < ActiveRecord::Base
2
+ belongs_to :article, :class_name => 'ArticleArchive', :foreign_key => 'article_id'
3
+ end
@@ -0,0 +1,9 @@
1
+ name:
2
+ rake: =0.8.7
3
+ default:
4
+ mysql: =2.8.1
5
+ rspec: =1.3.1
6
+ rspec2:
7
+ mysql2: =0.2.6
8
+ rspec: =2.3.0
9
+ solo: null
@@ -0,0 +1,15 @@
1
+ name: name
2
+ version: 0.1.0
3
+ authors:
4
+ - Author
5
+ email: email@email.com
6
+ homepage: http://github.com/author/name
7
+ summary: Summary
8
+ description: Description
9
+ dependencies:
10
+ - rake
11
+ - default:
12
+ - mysql
13
+ - rspec2:
14
+ - mysql2
15
+ development_dependencies: null
@@ -0,0 +1,249 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mover::Gems do
4
+
5
+ before(:each) do
6
+ @old_config = Mover::Gems.config
7
+
8
+ Mover::Gems.config.gemspec = "#{$root}/spec/fixtures/gemspec.yml"
9
+ Mover::Gems.config.gemsets = [
10
+ "#{$root}/spec/fixtures/gemsets.yml"
11
+ ]
12
+ Mover::Gems.config.warn = true
13
+
14
+ Mover::Gems.gemspec true
15
+ Mover::Gems.gemset = nil
16
+ end
17
+
18
+ after(:each) do
19
+ Mover::Gems.config = @old_config
20
+ end
21
+
22
+ describe :activate do
23
+ it "should activate gems" do
24
+ Mover::Gems.stub!(:gem)
25
+ Mover::Gems.should_receive(:gem).with('rspec', '=1.3.1')
26
+ Mover::Gems.should_receive(:gem).with('rake', '=0.8.7')
27
+ Mover::Gems.activate :rspec, 'rake'
28
+ end
29
+ end
30
+
31
+ describe :gemset= do
32
+ before(:each) do
33
+ Mover::Gems.config.gemsets = [
34
+ {
35
+ :name => {
36
+ :rake => '>0.8.6',
37
+ :default => {
38
+ :externals => '=1.0.2'
39
+ }
40
+ }
41
+ },
42
+ "#{$root}/spec/fixtures/gemsets.yml"
43
+ ]
44
+ end
45
+
46
+ describe :default do
47
+ before(:each) do
48
+ Mover::Gems.gemset = :default
49
+ end
50
+
51
+ it "should set @gemset" do
52
+ Mover::Gems.gemset.should == :default
53
+ end
54
+
55
+ it "should set @gemsets" do
56
+ Mover::Gems.gemsets.should == {
57
+ :name => {
58
+ :rake => ">0.8.6",
59
+ :default => {
60
+ :externals => '=1.0.2',
61
+ :mysql => "=2.8.1",
62
+ :rspec => "=1.3.1"
63
+ },
64
+ :rspec2 => {
65
+ :mysql2 => "=0.2.6",
66
+ :rspec => "=2.3.0"
67
+ },
68
+ :solo => nil
69
+ }
70
+ }
71
+ end
72
+
73
+ it "should set Gems.versions" do
74
+ Mover::Gems.versions.should == {
75
+ :externals => "=1.0.2",
76
+ :mysql => "=2.8.1",
77
+ :rake => ">0.8.6",
78
+ :rspec => "=1.3.1"
79
+ }
80
+ end
81
+
82
+ it "should return proper values for Gems.dependencies" do
83
+ Mover::Gems.dependencies.should == [ :rake, :mysql ]
84
+ Mover::Gems.development_dependencies.should == []
85
+ end
86
+
87
+ it "should return proper values for Gems.gemset_names" do
88
+ Mover::Gems.gemset_names.should == [ :default, :rspec2, :solo ]
89
+ end
90
+ end
91
+
92
+ describe :rspec2 do
93
+ before(:each) do
94
+ Mover::Gems.gemset = "rspec2"
95
+ end
96
+
97
+ it "should set @gemset" do
98
+ Mover::Gems.gemset.should == :rspec2
99
+ end
100
+
101
+ it "should set @gemsets" do
102
+ Mover::Gems.gemsets.should == {
103
+ :name => {
104
+ :rake => ">0.8.6",
105
+ :default => {
106
+ :externals => '=1.0.2',
107
+ :mysql => "=2.8.1",
108
+ :rspec => "=1.3.1"
109
+ },
110
+ :rspec2 => {
111
+ :mysql2=>"=0.2.6",
112
+ :rspec => "=2.3.0"
113
+ },
114
+ :solo => nil
115
+ }
116
+ }
117
+ end
118
+
119
+ it "should set Gems.versions" do
120
+ Mover::Gems.versions.should == {
121
+ :mysql2 => "=0.2.6",
122
+ :rake => ">0.8.6",
123
+ :rspec => "=2.3.0"
124
+ }
125
+ end
126
+
127
+ it "should return proper values for Gems.dependencies" do
128
+ Mover::Gems.dependencies.should == [ :rake, :mysql2 ]
129
+ Mover::Gems.development_dependencies.should == []
130
+ end
131
+
132
+ it "should return proper values for Gems.gemset_names" do
133
+ Mover::Gems.gemset_names.should == [ :default, :rspec2, :solo ]
134
+ end
135
+ end
136
+
137
+ describe :solo do
138
+ before(:each) do
139
+ Mover::Gems.gemset = :solo
140
+ end
141
+
142
+ it "should set @gemset" do
143
+ Mover::Gems.gemset.should == :solo
144
+ end
145
+
146
+ it "should set @gemsets" do
147
+ Mover::Gems.gemsets.should == {
148
+ :name => {
149
+ :rake => ">0.8.6",
150
+ :default => {
151
+ :externals => '=1.0.2',
152
+ :mysql => "=2.8.1",
153
+ :rspec => "=1.3.1"
154
+ },
155
+ :rspec2 => {
156
+ :mysql2=>"=0.2.6",
157
+ :rspec => "=2.3.0"
158
+ },
159
+ :solo => nil
160
+ }
161
+ }
162
+ end
163
+
164
+ it "should set Gems.versions" do
165
+ Mover::Gems.versions.should == {:rake=>">0.8.6"}
166
+ end
167
+
168
+ it "should return proper values for Gems.dependencies" do
169
+ Mover::Gems.dependencies.should == [:rake]
170
+ Mover::Gems.development_dependencies.should == []
171
+ end
172
+
173
+ it "should return proper values for Gems.gemset_names" do
174
+ Mover::Gems.gemset_names.should == [ :default, :rspec2, :solo ]
175
+ end
176
+ end
177
+
178
+ describe :nil do
179
+ before(:each) do
180
+ Mover::Gems.gemset = nil
181
+ end
182
+
183
+ it "should set everything to nil" do
184
+ Mover::Gems.gemset.should == nil
185
+ Mover::Gems.gemsets.should == nil
186
+ Mover::Gems.versions.should == nil
187
+ end
188
+ end
189
+ end
190
+
191
+ describe :gemset_from_loaded_specs do
192
+ before(:each) do
193
+ Gem.stub!(:loaded_specs)
194
+ end
195
+
196
+ it "should return the correct gemset for name gem" do
197
+ Gem.should_receive(:loaded_specs).and_return({ "name" => nil })
198
+ Mover::Gems.send(:gemset_from_loaded_specs).should == :default
199
+ end
200
+
201
+ it "should return the correct gemset for name-rspec gem" do
202
+ Gem.should_receive(:loaded_specs).and_return({ "name-rspec2" => nil })
203
+ Mover::Gems.send(:gemset_from_loaded_specs).should == :rspec2
204
+ end
205
+ end
206
+
207
+ describe :reload_gemspec do
208
+ it "should populate @gemspec" do
209
+ Mover::Gems.gemspec.hash.should == {
210
+ "name" => "name",
211
+ "version" => "0.1.0",
212
+ "authors" => ["Author"],
213
+ "email" => "email@email.com",
214
+ "homepage" => "http://github.com/author/name",
215
+ "summary" => "Summary",
216
+ "description" => "Description",
217
+ "dependencies" => [
218
+ "rake",
219
+ { "default" => [ "mysql" ] },
220
+ { "rspec2" => [ "mysql2" ] }
221
+ ],
222
+ "development_dependencies" => nil
223
+ }
224
+ end
225
+
226
+ it "should create methods from keys of @gemspec" do
227
+ Mover::Gems.gemspec.name.should == "name"
228
+ Mover::Gems.gemspec.version.should == "0.1.0"
229
+ Mover::Gems.gemspec.authors.should == ["Author"]
230
+ Mover::Gems.gemspec.email.should == "email@email.com"
231
+ Mover::Gems.gemspec.homepage.should == "http://github.com/author/name"
232
+ Mover::Gems.gemspec.summary.should == "Summary"
233
+ Mover::Gems.gemspec.description.should == "Description"
234
+ Mover::Gems.gemspec.dependencies.should == [
235
+ "rake",
236
+ { "default" => ["mysql"] },
237
+ { "rspec2" => [ "mysql2" ] }
238
+ ]
239
+ Mover::Gems.gemspec.development_dependencies.should == nil
240
+ end
241
+
242
+ it "should produce a valid gemspec" do
243
+ Mover::Gems.gemset = :default
244
+ gemspec = File.expand_path("../../../mover.gemspec", __FILE__)
245
+ gemspec = eval(File.read(gemspec), binding, gemspec)
246
+ gemspec.validate.should == true
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mover do
4
+
5
+ [ :none, :generic, :quick ].each do |option|
6
+
7
+ describe "(option: #{option})" do
8
+
9
+ before(:all) do
10
+ @options = {}
11
+ @options[option] = true
12
+ $db.drop_db
13
+ $db.create_db
14
+ $db.establish_connection
15
+ end
16
+
17
+ before(:each) do
18
+ [ 1, 0, 1 ].each { |v| $db.migrate(v) }
19
+ @articles = create_records(Article)
20
+ @comments = create_records(Comment)
21
+ @articles[0].move_to(ArticleArchive, @options)
22
+ Article.move_to(
23
+ ArticleArchive,
24
+ @options.merge(:conditions => [ 'articles.id = ?', 2 ])
25
+ )
26
+ end
27
+
28
+ describe 'move records' do
29
+
30
+ it "should move both articles and their associations" do
31
+ Article.count.should == 3
32
+ Comment.count.should == 3
33
+ ArticleArchive.count.should == 2
34
+ CommentArchive.count.should == 2
35
+ Article.find_by_id(1).nil?.should == true
36
+ Comment.find_by_id(2).nil?.should == true
37
+ ArticleArchive.find_by_id(1).nil?.should == false
38
+ CommentArchive.find_by_id(2).nil?.should == false
39
+ comments = ArticleArchive.find_by_id(1).comments
40
+ comments.length.should == 1
41
+ comments.first.id.should == 1
42
+ comments = ArticleArchive.find_by_id(2).comments
43
+ comments.length.should == 1
44
+ comments.first.id.should == 2
45
+ end
46
+
47
+ it "should assign moved_at" do
48
+ ArticleArchive.find_by_id(1).moved_at.utc.to_s.should == Time.now.utc.to_s
49
+ end
50
+ end
51
+
52
+ describe 'move records back' do
53
+
54
+ before(:each) do
55
+ ArticleArchive.find(1).move_to(Article, @options)
56
+ ArticleArchive.move_to(
57
+ Article,
58
+ @options.merge(:conditions => [ 'article_archives.id = ?', 2 ])
59
+ )
60
+ end
61
+
62
+ it "should move both articles and their associations" do
63
+ Article.count.should == 5
64
+ Comment.count.should == 5
65
+ ArticleArchive.count.should == 0
66
+ CommentArchive.count.should == 0
67
+ Article.find_by_id(1).nil?.should == false
68
+ Comment.find_by_id(2).nil?.should == false
69
+ ArticleArchive.find_by_id(1).nil?.should == true
70
+ CommentArchive.find_by_id(2).nil?.should == true
71
+ comments = Article.find_by_id(1).comments
72
+ comments.length.should == 1
73
+ comments.first.id.should == 1
74
+ comments = Article.find_by_id(2).comments
75
+ comments.length.should == 1
76
+ comments.first.id.should == 2
77
+ end
78
+
79
+ unless option == :quick
80
+ it "should copy articles" do
81
+ article_1 = Article.find(1)
82
+ article_1.update_attributes(:title => 'edited')
83
+ article_2 = Article.find(2)
84
+ article_2.update_attributes(:title => 'edited')
85
+ article_1.move_to(ArticleArchive, @options.merge(:copy => true))
86
+ Article.move_to(
87
+ ArticleArchive,
88
+ @options.merge(
89
+ :conditions => [ 'articles.id = ?', 2 ],
90
+ :copy => true
91
+ )
92
+ )
93
+ ArticleArchive.find(1).title.should == 'edited'
94
+ ArticleArchive.find(2).title.should == 'edited'
95
+ Article.count.should == 5
96
+ Comment.count.should == 5
97
+ ArticleArchive.count.should == 2
98
+ CommentArchive.count.should == 2
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ describe 'migrate magic column' do
106
+
107
+ before(:each) do
108
+ [ 1, 0, 1 ].each { |v| $db.migrate(v) }
109
+ @moved_at = Time.now.utc - 60
110
+ @articles = create_records(Article, :moved_at => @moved_at)
111
+ @comments = create_records(Comment)
112
+ @articles[0].move_to(ArticleArchive, :migrate => true)
113
+ Article.move_to(
114
+ ArticleArchive,
115
+ :conditions => [ 'articles.id = ?', 2 ],
116
+ :migrate => true
117
+ )
118
+ end
119
+
120
+ it "should migrate magic column" do
121
+ ArticleArchive.find(1).moved_at.to_s.should == @moved_at.to_s
122
+ ArticleArchive.find(2).moved_at.to_s.should == @moved_at.to_s
123
+ end
124
+ end
125
+
126
+ describe :reserve_id do
127
+
128
+ it "should return an id" do
129
+ Article.reserve_id.class.should == Fixnum
130
+ end
131
+
132
+ it "should delete the record" do
133
+ id = Article.reserve_id
134
+ Article.find_by_id(id).should == nil
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,58 @@
1
+ require 'pp'
2
+
3
+ $root = File.expand_path('../../', __FILE__)
4
+ require "#{$root}/lib/mover/gems"
5
+
6
+ Mover::Gems.activate %w(active_wrapper rspec)
7
+
8
+ require 'active_wrapper'
9
+ require 'fileutils'
10
+
11
+ require "#{$root}/lib/mover"
12
+
13
+ require "#{$root}/spec/fixtures/article"
14
+ require "#{$root}/spec/fixtures/article_archive"
15
+ require "#{$root}/spec/fixtures/comment"
16
+ require "#{$root}/spec/fixtures/comment_archive"
17
+
18
+ Spec::Runner.configure do |config|
19
+ end
20
+
21
+ $db, $log = ActiveWrapper.setup(
22
+ :base => File.dirname(__FILE__),
23
+ :env => 'test'
24
+ )
25
+ $db.establish_connection
26
+
27
+ def record_match?(original, copy)
28
+ (original.class.column_names & copy.class.column_names).each do |col|
29
+ copy.send(col).should == original.send(col)
30
+ end
31
+ end
32
+
33
+ def columns(table)
34
+ connection.columns(table).collect(&:name)
35
+ end
36
+
37
+ def connection
38
+ ActiveRecord::Base.connection
39
+ end
40
+
41
+ def create_records(klass, values={})
42
+ klass.delete_all
43
+ keys = values.keys
44
+ (1..5).collect do |x|
45
+ klass.column_names.each do |column|
46
+ next if column == 'id'
47
+ if column == 'article_id' && !keys.include?(:article_id)
48
+ values[:article_id] = x
49
+ elsif !keys.include?(column.intern)
50
+ values[column.intern] = "#{klass} #{x} #{column}"
51
+ end
52
+ end
53
+ record = klass.new
54
+ record.id = x
55
+ record.update_attributes(values)
56
+ record
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mover_postgres
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.6
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Winton Welsh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: active_wrapper
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Move ActiveRecord records across tables like it ain't no thang
63
+ email: james@njtechnologies.co.uk
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - .gitignore
69
+ - LICENSE
70
+ - README.md
71
+ - Rakefile
72
+ - config/gemsets.yml
73
+ - config/gemspec.yml
74
+ - init.rb
75
+ - lib/mover.rb
76
+ - lib/mover/gems.rb
77
+ - mover.gemspec
78
+ - rails/init.rb
79
+ - spec/Rakefile
80
+ - spec/config/database.yml.example
81
+ - spec/db/migrate/001_create_fixtures.rb
82
+ - spec/fixtures/article.rb
83
+ - spec/fixtures/article_archive.rb
84
+ - spec/fixtures/comment.rb
85
+ - spec/fixtures/comment_archive.rb
86
+ - spec/fixtures/gemsets.yml
87
+ - spec/fixtures/gemspec.yml
88
+ - spec/mover/gems_spec.rb
89
+ - spec/mover_spec.rb
90
+ - spec/spec_helper.rb
91
+ homepage: https://github.com/jamesharker/mover
92
+ licenses: []
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 1.8.24
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Move ActiveRecord records across tables like it ain't no thang
115
+ test_files:
116
+ - spec/Rakefile
117
+ - spec/config/database.yml.example
118
+ - spec/db/migrate/001_create_fixtures.rb
119
+ - spec/fixtures/article.rb
120
+ - spec/fixtures/article_archive.rb
121
+ - spec/fixtures/comment.rb
122
+ - spec/fixtures/comment_archive.rb
123
+ - spec/fixtures/gemsets.yml
124
+ - spec/fixtures/gemspec.yml
125
+ - spec/mover/gems_spec.rb
126
+ - spec/mover_spec.rb
127
+ - spec/spec_helper.rb
128
+ has_rdoc: