mover_postgres 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: