mover 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +31 -56
- data/lib/mover.rb +71 -52
- data/require.rb +4 -8
- data/spec/db/migrate/001_create_fixtures.rb +28 -0
- data/spec/fixtures/article.rb +3 -1
- data/spec/fixtures/article_archive.rb +6 -0
- data/spec/fixtures/comment.rb +0 -1
- data/spec/fixtures/comment_archive.rb +3 -0
- data/spec/log/test.log +4967 -19176
- data/spec/mover_spec.rb +75 -0
- data/spec/spec_helper.rb +1 -9
- metadata +6 -12
- data/lib/mover/migrator.rb +0 -44
- data/lib/mover/record.rb +0 -114
- data/lib/mover/table.rb +0 -82
- data/log/development.log +0 -6
- data/spec/db/migrate/001_create_articles.rb +0 -34
- data/spec/db/migrate/002_add_permalink.rb +0 -9
- data/spec/db/migrate/003_remove_magic_columns.rb +0 -9
- data/spec/mover/migrator_spec.rb +0 -34
- data/spec/mover/record_spec.rb +0 -156
- data/spec/mover/table_spec.rb +0 -80
data/README.markdown
CHANGED
@@ -10,86 +10,61 @@ Requirements
|
|
10
10
|
sudo gem install mover
|
11
11
|
</pre>
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
Create the movable table
|
16
|
-
------------------------
|
17
|
-
|
18
|
-
Migration:
|
13
|
+
Move records
|
14
|
+
------------
|
19
15
|
|
20
16
|
<pre>
|
21
|
-
|
22
|
-
|
23
|
-
Article.create_movable_table(
|
24
|
-
:archive,
|
25
|
-
:columns => %w(id title body created_at),
|
26
|
-
:indexes => %w(id created_at)
|
27
|
-
)
|
28
|
-
add_column :articles_archive, :move_id, :string
|
29
|
-
add_column :articles_archive, :moved_at, :datetime
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.down
|
33
|
-
Article.drop_movable_table(:archive)
|
34
|
-
end
|
35
|
-
end
|
17
|
+
Article.last.move_to(ArticleArchive)
|
18
|
+
Article.move_to(ArticleArchive, [ "created_at > ?", Date.today ])
|
36
19
|
</pre>
|
37
20
|
|
38
|
-
The
|
39
|
-
|
40
|
-
Options:
|
21
|
+
The <code>move_to</code> method is available to all models.
|
41
22
|
|
42
|
-
|
43
|
-
* <code>:indexes</code> - Only create certain indexes. Defaults to all.
|
23
|
+
The two tables do not have to be identical. Only shared columns transfer.
|
44
24
|
|
45
|
-
|
25
|
+
Callbacks
|
26
|
+
---------
|
46
27
|
|
47
|
-
|
28
|
+
In this example, we want an "archive" table for articles and comments.
|
48
29
|
|
49
|
-
|
50
|
-
----------------
|
30
|
+
We also want the article's comments to be archived when the article is.
|
51
31
|
|
52
32
|
<pre>
|
53
33
|
class Article < ActiveRecord::Base
|
54
|
-
|
34
|
+
has_many :comments
|
35
|
+
before_move_to :ArticleArchive do
|
36
|
+
comments.each { |c| c.move_to(CommentArchive) }
|
37
|
+
end
|
55
38
|
end
|
56
|
-
</pre>
|
57
39
|
|
58
|
-
|
40
|
+
class ArticleArchive < ActiveRecord::Base
|
41
|
+
has_many :comments, :class_name => 'CommentArchive', :foreign_key => 'article_id'
|
42
|
+
before_move_to :Article do
|
43
|
+
comments.each { |c| c.move_to(Comment) }
|
44
|
+
end
|
45
|
+
end
|
59
46
|
|
60
|
-
|
61
|
-
|
47
|
+
class Comment < ActiveRecord::Base
|
48
|
+
belongs_to :article
|
49
|
+
end
|
62
50
|
|
63
|
-
<
|
64
|
-
|
65
|
-
|
51
|
+
class CommentArchive < ActiveRecord::Base
|
52
|
+
belongs_to :article, :class_name => 'ArticleArchive', :foreign_key => 'article_id'
|
53
|
+
end
|
66
54
|
</pre>
|
67
55
|
|
68
|
-
|
56
|
+
Reserve a spot
|
57
|
+
--------------
|
69
58
|
|
70
|
-
|
71
|
-
-----------------
|
59
|
+
Before you create a record, you can "reserve a spot" on a table that you will move the record to later.
|
72
60
|
|
73
61
|
<pre>
|
74
|
-
|
75
|
-
ArticleArchive.last.move_from
|
62
|
+
ArticleArchive.create(:id => Article.reserve_id)
|
76
63
|
</pre>
|
77
64
|
|
78
|
-
You can access the movable table by prepending its name to the original class name. In this example, you would use <code>ArticleArchive</code>.
|
79
|
-
|
80
|
-
<a name="magic_columns"></a>
|
81
|
-
|
82
65
|
Magic columns
|
83
66
|
-------------
|
84
67
|
|
85
|
-
### move_id
|
86
|
-
|
87
|
-
By default, restoring a record will only restore itself and not its movable relationships.
|
88
|
-
|
89
|
-
To restore the relationships automatically, add the <code>move_id</code> column to all movable tables involved.
|
90
|
-
|
91
68
|
### moved_at
|
92
69
|
|
93
|
-
If
|
94
|
-
|
95
|
-
See the <a href="#create_the_movable_table">create the movable table</a> section for an example of how to add the magic columns.
|
70
|
+
If a table contains the column <code>moved_at</code>, it will automatically be populated with the date and time it was moved.
|
data/lib/mover.rb
CHANGED
@@ -2,68 +2,87 @@ require File.expand_path("#{File.dirname(__FILE__)}/../require")
|
|
2
2
|
Require.lib!
|
3
3
|
|
4
4
|
module Mover
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
base.send :include, Included
|
10
|
-
end
|
5
|
+
def self.included(base)
|
6
|
+
unless base.included_modules.include?(InstanceMethods)
|
7
|
+
base.extend ClassMethods
|
8
|
+
base.send :include, InstanceMethods
|
11
9
|
end
|
10
|
+
end
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
def after_move_to(to_class, &block)
|
15
|
+
@after_move_to ||= []
|
16
|
+
@after_move_to << [ to_class, block ]
|
17
|
+
end
|
18
|
+
|
19
|
+
def before_move_to(to_class, &block)
|
20
|
+
@before_move_to ||= []
|
21
|
+
@before_move_to << [ to_class, block ]
|
22
|
+
end
|
23
|
+
|
24
|
+
def move_to(to_class, conditions, instance=nil)
|
25
|
+
from_class = self
|
26
|
+
# Conditions
|
27
|
+
add_conditions! where = '', conditions
|
28
|
+
# Columns
|
29
|
+
insert = from_class.column_names & to_class.column_names
|
30
|
+
insert -= [ 'moved_at' ]
|
31
|
+
insert.collect! { |col| connection.quote_column_name(col) }
|
32
|
+
select = insert.clone
|
33
|
+
# Magic columns
|
34
|
+
if to_class.column_names.include?('moved_at')
|
35
|
+
insert << connection.quote_column_name('moved_at')
|
36
|
+
select << connection.quote(Time.now.utc)
|
37
|
+
end
|
38
|
+
# Callbacks
|
39
|
+
collector = lambda { |(klass, block)| block if eval(klass.to_s) == to_class }
|
40
|
+
before = (@before_move_to || []).collect(&collector).compact
|
41
|
+
after = (@after_move_to || []).collect(&collector).compact
|
42
|
+
# Instances
|
43
|
+
instances =
|
44
|
+
if instance
|
45
|
+
[ instance ]
|
46
|
+
elsif before.empty? && after.empty?
|
47
|
+
[]
|
48
|
+
else
|
49
|
+
self.find(:all, :conditions => where[5..-1])
|
22
50
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
include Mover::Base::Record::InstanceMethods
|
28
|
-
|
29
|
-
self.table_name = "#{self.table_name}_#{type}"
|
30
|
-
|
31
|
-
def self.movable_type
|
32
|
-
#{type.inspect}
|
33
|
-
end
|
34
|
-
|
35
|
-
def moved_from_class
|
36
|
-
#{self.name}
|
37
|
-
end
|
38
|
-
end
|
39
|
-
RUBY
|
51
|
+
# Callback executor
|
52
|
+
exec_callbacks = lambda do |callbacks|
|
53
|
+
callbacks.each do |block|
|
54
|
+
instances.each { |instance| instance.instance_eval(&block) }
|
40
55
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
56
|
+
end
|
57
|
+
# Execute
|
58
|
+
transaction do
|
59
|
+
exec_callbacks.call before
|
60
|
+
connection.execute(<<-SQL)
|
61
|
+
INSERT INTO #{to_class.table_name} (#{insert.join(', ')})
|
62
|
+
SELECT #{select.join(', ')}
|
63
|
+
FROM #{from_class.table_name}
|
64
|
+
#{where}
|
65
|
+
SQL
|
66
|
+
connection.execute("DELETE FROM #{from_class.table_name} #{where}")
|
67
|
+
exec_callbacks.call after
|
45
68
|
end
|
46
69
|
end
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
base.send :include, Included
|
54
|
-
base.class_eval do
|
55
|
-
class <<self
|
56
|
-
alias_method :method_missing_without_mover, :method_missing
|
57
|
-
alias_method :method_missing, :method_missing_with_mover
|
58
|
-
end
|
59
|
-
end
|
70
|
+
|
71
|
+
def reserve_id
|
72
|
+
id = nil
|
73
|
+
transaction do
|
74
|
+
id = connection.insert("INSERT INTO #{self.table_name} () VALUES ()")
|
75
|
+
connection.execute("DELETE FROM #{self.table_name} WHERE id = #{id}") if id
|
60
76
|
end
|
77
|
+
id
|
61
78
|
end
|
62
79
|
end
|
63
80
|
|
64
|
-
module
|
81
|
+
module InstanceMethods
|
82
|
+
def move_to(to_class)
|
83
|
+
self.class.move_to(to_class, "#{self.class.primary_key} = #{id}", self)
|
84
|
+
end
|
65
85
|
end
|
66
86
|
end
|
67
87
|
|
68
|
-
ActiveRecord::Base.send(:include, Mover
|
69
|
-
ActiveRecord::Migration.send(:include, Mover::Migration)
|
88
|
+
ActiveRecord::Base.send(:include, Mover)
|
data/require.rb
CHANGED
@@ -17,17 +17,11 @@ Require do
|
|
17
17
|
name 'mover'
|
18
18
|
homepage "http://github.com/winton/#{name}"
|
19
19
|
summary "Move ActiveRecord records across tables like it ain't no thang"
|
20
|
-
version '0.
|
20
|
+
version '0.2.0'
|
21
21
|
end
|
22
22
|
|
23
23
|
bin { require 'lib/mover' }
|
24
|
-
|
25
|
-
lib do
|
26
|
-
require 'digest/md5'
|
27
|
-
require 'lib/mover/migrator'
|
28
|
-
require 'lib/mover/record'
|
29
|
-
require 'lib/mover/table'
|
30
|
-
end
|
24
|
+
lib {}
|
31
25
|
|
32
26
|
rakefile do
|
33
27
|
gem(:active_wrapper)
|
@@ -44,6 +38,8 @@ Require do
|
|
44
38
|
require 'rails/init'
|
45
39
|
require 'pp'
|
46
40
|
require 'spec/fixtures/article'
|
41
|
+
require 'spec/fixtures/article_archive'
|
47
42
|
require 'spec/fixtures/comment'
|
43
|
+
require 'spec/fixtures/comment_archive'
|
48
44
|
end
|
49
45
|
end
|
@@ -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
|
data/spec/fixtures/article.rb
CHANGED
data/spec/fixtures/comment.rb
CHANGED