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 CHANGED
@@ -10,86 +10,61 @@ Requirements
10
10
  sudo gem install mover
11
11
  </pre>
12
12
 
13
- <a name="create_the_movable_table"></a>
14
-
15
- Create the movable table
16
- ------------------------
17
-
18
- Migration:
13
+ Move records
14
+ ------------
19
15
 
20
16
  <pre>
21
- class CreateArticlesArchive < ActiveRecord::Migration
22
- def self.up
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 first parameter names your movable table. In this example, the table is named <code>articles_archive</code>.
39
-
40
- Options:
21
+ The <code>move_to</code> method is available to all models.
41
22
 
42
- * <code>:columns</code> - Only use certain columns from the original table. Defaults to all.
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
- We also added two columns, <code>move\_id</code> and <code>moved\_at</code>. These are <a href="#magic_columns">magic columns</a>.
25
+ Callbacks
26
+ ---------
46
27
 
47
- <a name="define_the_model"></a>
28
+ In this example, we want an "archive" table for articles and comments.
48
29
 
49
- Define the model
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
- is_movable :archive
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
- The <code>is_movable</code> method takes any number of parameters for multiple movable tables.
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
- Moving records
61
- --------------
47
+ class Comment < ActiveRecord::Base
48
+ belongs_to :article
49
+ end
62
50
 
63
- <pre>
64
- Article.last.move_to(:archive)
65
- Article.move_to(:archive, [ "created_at > ?", Date.today ])
51
+ class CommentArchive < ActiveRecord::Base
52
+ belongs_to :article, :class_name => 'ArticleArchive', :foreign_key => 'article_id'
53
+ end
66
54
  </pre>
67
55
 
68
- Associations move if they are movable and if all movable tables have a <code>move_id</code> column (see <a href="#magic_columns">magic columns</a>).
56
+ Reserve a spot
57
+ --------------
69
58
 
70
- Restoring records
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
- Article.move_from(:archive, [ "created_at > ?", Date.today ])
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 you need to know when the record was moved, add the <code>moved\_at</code> column to your movable table.
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
- module Base
6
- def self.included(base)
7
- unless base.included_modules.include?(Included)
8
- base.extend ClassMethods
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
- module ClassMethods
14
- def is_movable(*types)
15
- @movable_types = types
16
-
17
- self.class_eval do
18
- attr_accessor :movable_id
19
- class <<self
20
- attr_reader :movable_types
21
- end
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
- types.each do |type|
25
- eval <<-RUBY
26
- class ::#{self.name}#{type.to_s.classify} < ActiveRecord::Base
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
- extend Table
43
- extend Record::ClassMethods
44
- include Record::InstanceMethods
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
- end
48
-
49
- module Migration
50
- def self.included(base)
51
- unless base.included_modules.include?(Included)
52
- base.extend Migrator
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 Included
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::Base)
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.1.1'
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
@@ -1,4 +1,6 @@
1
1
  class Article < ActiveRecord::Base
2
2
  has_many :comments
3
- is_movable :archive, :draft
3
+ before_move_to :ArticleArchive do
4
+ comments.each { |c| c.move_to(CommentArchive) }
5
+ end
4
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_to :Article do
4
+ comments.each { |c| c.move_to(Comment) }
5
+ end
6
+ end
@@ -1,4 +1,3 @@
1
1
  class Comment < ActiveRecord::Base
2
2
  belongs_to :article
3
- is_movable :archive
4
3
  end
@@ -0,0 +1,3 @@
1
+ class CommentArchive < ActiveRecord::Base
2
+ belongs_to :article, :class_name => 'ArticleArchive', :foreign_key => 'article_id'
3
+ end