mover 0.1.1 → 0.2.0

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/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