acts_as_versionable 0.3.5 → 0.5.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.rdoc +39 -27
- data/Rakefile +1 -1
- data/acts_as_versionable.gemspec +3 -3
- data/lib/acts_as_versionable/version.rb +1 -1
- data/lib/acts_as_versionable.rb +88 -94
- metadata +6 -10
- data/CHANGELOG.rdoc +0 -10
- data/lib/generators/versionable/templates/migration.rb.erb +0 -25
- data/lib/generators/versionable/versionable_generator.rb +0 -22
- data/test/acts_as_versionable_test.rb +0 -135
data/README.rdoc
CHANGED
@@ -1,57 +1,69 @@
|
|
1
1
|
= Acts As Versionable
|
2
2
|
|
3
3
|
Minimalist versionable engine for rails > 3
|
4
|
+
Maintains versions in same table, just adding two new fields
|
5
|
+
|
6
|
+
|
4
7
|
|
5
8
|
=== Installation
|
6
9
|
|
7
10
|
gem 'acts_as_versionable'
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
rails g versionable MODEL
|
12
|
+
add two fields to your model
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
version_number:integer
|
15
|
+
version_id:integer
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
rails g versionable Document
|
19
|
-
create db/migrate/20120119150513_create_document_versions.rb
|
20
|
-
|
21
|
-
class CreateDocumentVersions < ActiveRecord::Migration
|
17
|
+
class ActsAsVersionableDocuments < ActiveRecord::Migration
|
22
18
|
def self.up
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
add_index :document_versions, :versioned_as
|
32
|
-
add_column :documents, :current_version, :integer
|
19
|
+
add_column :documents, :version_number, :default => 0
|
20
|
+
add_column :documents, :version_id, :default => null
|
21
|
+
|
22
|
+
# optional indexes
|
23
|
+
add_index :documents, :version_number, :name => "index_documents_on_version_number"
|
24
|
+
add_index :documents, :version_id, :name => "index_documents_on_version_id"
|
25
|
+
# optional unique :id, :version_number
|
33
26
|
end
|
34
27
|
|
35
28
|
def self.down
|
36
|
-
|
37
|
-
remove_column :documents, :
|
29
|
+
remove_column :documents, :version_number
|
30
|
+
remove_column :documents, :version_id
|
38
31
|
end
|
39
32
|
end
|
40
|
-
|
33
|
+
|
41
34
|
=== Example
|
42
35
|
|
43
36
|
class Document < ActiveRecord::Base
|
44
37
|
acts_as_versionable :limit => 3
|
45
38
|
end
|
46
39
|
|
40
|
+
# use scope to get top versions
|
41
|
+
documents = Document.last_versions => scope that return top versions
|
42
|
+
|
43
|
+
# cerate first version
|
47
44
|
document = Document.create(:title => 'title', :body => 'body')
|
48
45
|
document.version => 1
|
46
|
+
document.last_version => 1
|
49
47
|
|
48
|
+
# modify
|
50
49
|
document.title = 'new title'
|
51
50
|
document.save => true
|
51
|
+
|
52
|
+
# new version was created
|
52
53
|
document.version => 2
|
54
|
+
document.last_version => 2
|
53
55
|
document.versions => Array(2)
|
54
|
-
document.revert_to(1) => #<Document ...>
|
55
|
-
document.versions.get_version 2 => #<DocumentVersion ... versioned => 2>
|
56
56
|
|
57
|
-
|
57
|
+
# revert to version 1
|
58
|
+
document.revert_to_version(1) => #<Document ...>
|
59
|
+
document.version => 3 # after revert a new version is created with content of version 1
|
60
|
+
document.title => 'title'
|
61
|
+
|
62
|
+
# get a version
|
63
|
+
version2 = document.get_version 2 => #<Document ... version_number => 2>
|
64
|
+
version2.version => 2
|
65
|
+
version2.title => 'new title"
|
66
|
+
version2.last_version => 4
|
67
|
+
|
68
|
+
|
69
|
+
|
data/Rakefile
CHANGED
data/acts_as_versionable.gemspec
CHANGED
@@ -5,11 +5,11 @@ require "acts_as_versionable/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "acts_as_versionable"
|
7
7
|
s.version = ActsAsVersionable::VERSION
|
8
|
-
s.authors = ["
|
8
|
+
s.authors = ["carlos segura"]
|
9
9
|
s.email = ["csegura@ideseg.com"]
|
10
10
|
s.homepage = ""
|
11
|
-
s.summary =
|
12
|
-
s.description =
|
11
|
+
s.summary = %q{Minimalist engine for versions}
|
12
|
+
s.description = %q{Maintain versions in same table only add two fields version_number:integer and version_id:integer}
|
13
13
|
|
14
14
|
s.rubyforge_project = "acts_as_versionable"
|
15
15
|
|
data/lib/acts_as_versionable.rb
CHANGED
@@ -1,118 +1,112 @@
|
|
1
|
-
require
|
1
|
+
require "acts_as_versionable/version"
|
2
2
|
|
3
3
|
module ActsAsVersionable
|
4
4
|
|
5
5
|
class NoSuchVersionError < Exception
|
6
6
|
end
|
7
|
-
|
8
|
-
extend ActiveSupport::Concern
|
9
7
|
|
10
|
-
|
11
|
-
end
|
8
|
+
extend ActiveSupport::Concern
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
def create_class(name, superclass, &block)
|
16
|
-
klass = Class.new superclass, &block
|
17
|
-
Object.const_set name, klass
|
18
|
-
end
|
19
|
-
|
20
|
-
def acts_as_versionable options={}
|
21
|
-
options.symbolize_keys!
|
22
|
-
|
23
|
-
has_many :versions, :class_name => "#{self.name}Versions", :dependent => :destroy
|
24
|
-
after_save :apply_versioning
|
25
|
-
after_save :clear_old_versions
|
26
|
-
|
27
|
-
attr_accessor :local_changes
|
28
|
-
cattr_accessor :max_versions
|
29
|
-
|
30
|
-
self.max_versions = options[:limit].to_i
|
31
|
-
|
32
|
-
class << create_class("#{self.name}Versions", ActiveRecord::Base)
|
33
|
-
REJECT_COLUMNS = %w(id created_at updated_at versioned_as)
|
34
|
-
def actual_columns
|
35
|
-
columns.reject { |c| REJECT_COLUMNS.include?(c.name) }
|
36
|
-
end
|
37
|
-
|
38
|
-
def get_version(version)
|
39
|
-
revision = self.find_by_versioned_as(version)
|
40
|
-
raise NoSuchVersionError, "Couldn't find #{version} version" if revision.blank?
|
41
|
-
revision
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
include InstanceMethods
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
module InstanceMethods
|
51
|
-
|
52
|
-
def version
|
53
|
-
current_version || 0
|
54
|
-
end
|
10
|
+
included do |base|
|
11
|
+
end
|
55
12
|
|
56
|
-
|
57
|
-
|
13
|
+
module ClassMethods
|
14
|
+
def acts_as_versionable(options = {})
|
15
|
+
cattr_accessor :max_versions
|
16
|
+
self.max_versions = (options[:max_versions] || 10)
|
58
17
|
|
59
|
-
|
60
|
-
|
61
|
-
self.current_version = version
|
62
|
-
self.local_changes = true
|
63
|
-
self.save
|
64
|
-
end
|
18
|
+
after_save :create_new_version
|
65
19
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
20
|
+
has_many :internal_versions,
|
21
|
+
:class_name => self.name,
|
22
|
+
:foreign_key => "version_id",
|
23
|
+
:order => "version_number desc",
|
24
|
+
:dependent => :destroy
|
25
|
+
|
26
|
+
belongs_to :parent_version, :class_name => self.name, :foreign_key => "version_id"
|
27
|
+
|
28
|
+
scope :last_versions, where(:version_id => nil)
|
29
|
+
scope :get_versionable, lambda { |id, version| where(:version_id => id, :version_number => version) }
|
30
|
+
|
31
|
+
include InstanceMethods
|
32
|
+
end
|
33
|
+
end
|
74
34
|
|
75
|
-
|
35
|
+
module InstanceMethods
|
36
|
+
def revert_to_version(number)
|
37
|
+
version = get_version number
|
38
|
+
editable = last_version_editable
|
39
|
+
copy_version_values version, editable
|
40
|
+
editable.version_number = nil
|
41
|
+
editable.version_id = nil
|
42
|
+
editable.save
|
43
|
+
editable
|
44
|
+
end
|
76
45
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
46
|
+
# return a determined version number
|
47
|
+
def get_version(number)
|
48
|
+
version = versions.where(:version_number => number).first
|
49
|
+
raise NoSuchVersionError if version.nil?
|
50
|
+
version
|
51
|
+
end
|
82
52
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
53
|
+
# return an array with all versions
|
54
|
+
def versions
|
55
|
+
if versionable?
|
56
|
+
internal_versions
|
57
|
+
else
|
58
|
+
self.class.where(:version_id => self.version_id).order(:version_number).reverse_order
|
59
|
+
end
|
60
|
+
end
|
87
61
|
|
88
|
-
|
62
|
+
# check if we are in main version
|
63
|
+
def versionable?
|
64
|
+
version_id.nil?
|
65
|
+
end
|
89
66
|
|
90
|
-
|
67
|
+
# return the current version
|
68
|
+
def version
|
69
|
+
return last_version if versionable?
|
70
|
+
version_number
|
71
|
+
end
|
91
72
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
73
|
+
# return the last version number
|
74
|
+
def last_version
|
75
|
+
return 0 if versions.count == 0
|
76
|
+
versions.first.version_number
|
77
|
+
end
|
97
78
|
|
98
|
-
|
99
|
-
end
|
79
|
+
private
|
100
80
|
|
101
|
-
|
102
|
-
|
103
|
-
|
81
|
+
# return the last version editable
|
82
|
+
def last_version_editable
|
83
|
+
parent_version.nil? ? self : parent_version
|
84
|
+
end
|
85
|
+
|
86
|
+
# callback after save
|
87
|
+
def create_new_version
|
88
|
+
if versionable?
|
89
|
+
|
90
|
+
cloned = copy_version_values self, self.class.new
|
91
|
+
cloned.version_id = self.id
|
92
|
+
cloned.version_number = last_version + 1
|
93
|
+
cloned.save
|
94
|
+
|
95
|
+
# purge max limit
|
96
|
+
excess_baggage = cloned.version_number - max_versions
|
104
97
|
if excess_baggage > 0
|
105
|
-
versions.where("
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def move_columns from, to
|
110
|
-
versions.actual_columns.each do |column|
|
111
|
-
to[column.name] = from[column.name]
|
98
|
+
versions.where("version_number <= ?", excess_baggage).delete_all
|
112
99
|
end
|
113
|
-
end
|
100
|
+
end
|
101
|
+
end
|
114
102
|
|
103
|
+
def copy_version_values(from, to)
|
104
|
+
columns = self.class.columns.reject { |c| c.name == "id" }
|
105
|
+
columns.each {|c| to[c.name] = from[c.name] }
|
106
|
+
to
|
115
107
|
end
|
108
|
+
end
|
109
|
+
|
116
110
|
end
|
117
111
|
|
118
|
-
ActiveRecord::Base.
|
112
|
+
ActiveRecord::Base.send :extend, ActsAsVersionable::ClassMethods
|
metadata
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_versionable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
-
|
8
|
+
- carlos segura
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-22 00:00:00.000000000Z
|
13
13
|
dependencies: []
|
14
|
-
description:
|
14
|
+
description: Maintain versions in same table only add two fields version_number:integer
|
15
|
+
and version_id:integer
|
15
16
|
email:
|
16
17
|
- csegura@ideseg.com
|
17
18
|
executables: []
|
@@ -19,16 +20,12 @@ extensions: []
|
|
19
20
|
extra_rdoc_files: []
|
20
21
|
files:
|
21
22
|
- .gitignore
|
22
|
-
- CHANGELOG.rdoc
|
23
23
|
- Gemfile
|
24
24
|
- README.rdoc
|
25
25
|
- Rakefile
|
26
26
|
- acts_as_versionable.gemspec
|
27
27
|
- lib/acts_as_versionable.rb
|
28
28
|
- lib/acts_as_versionable/version.rb
|
29
|
-
- lib/generators/versionable/templates/migration.rb.erb
|
30
|
-
- lib/generators/versionable/versionable_generator.rb
|
31
|
-
- test/acts_as_versionable_test.rb
|
32
29
|
homepage: ''
|
33
30
|
licenses: []
|
34
31
|
post_install_message:
|
@@ -53,5 +50,4 @@ rubygems_version: 1.8.10
|
|
53
50
|
signing_key:
|
54
51
|
specification_version: 3
|
55
52
|
summary: Minimalist engine for versions
|
56
|
-
test_files:
|
57
|
-
- test/acts_as_versionable_test.rb
|
53
|
+
test_files: []
|
data/CHANGELOG.rdoc
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
<%
|
2
|
-
versions_table = "#{@model_name}_versions".downcase
|
3
|
-
rejects_columns = %w(id created_at updated_at current_version)
|
4
|
-
columns = @model.classify.constantize.columns.reject { |c| rejects_columns.include?(c.name) }
|
5
|
-
%>
|
6
|
-
class <%= migration_name %> < ActiveRecord::Migration
|
7
|
-
def self.up
|
8
|
-
create_table :<%= versions_table %> do |t|
|
9
|
-
<% columns.each do |column| %>
|
10
|
-
t.column :<%= column.name %>, :<%= column.type %>
|
11
|
-
<% end %>
|
12
|
-
t.integer :<%= model_name + '_id' %>
|
13
|
-
t.integer :versioned_as
|
14
|
-
t.timestamps
|
15
|
-
end
|
16
|
-
|
17
|
-
add_index :<%= versions_table %>, :versioned_as
|
18
|
-
add_column :<%= model_name.pluralize %>, :current_version, :integer
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.down
|
22
|
-
drop_table :<%= versions_table %>
|
23
|
-
remove_column :<%= model_name.pluralize %>, :current_version
|
24
|
-
end
|
25
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'rails/generators'
|
2
|
-
require 'rails/generators/migration'
|
3
|
-
|
4
|
-
class VersionableGenerator < Rails::Generators::Base
|
5
|
-
include Rails::Generators::Migration
|
6
|
-
source_root File.expand_path('../templates', __FILE__)
|
7
|
-
argument :klass, :type => :string, :description => "Model to versionate"
|
8
|
-
|
9
|
-
attr_accessor :migration_name, :model_name, :model
|
10
|
-
|
11
|
-
def self.next_migration_number(path)
|
12
|
-
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
13
|
-
end
|
14
|
-
|
15
|
-
def create_model_file
|
16
|
-
@model_name = klass.downcase
|
17
|
-
@model = klass
|
18
|
-
@migration_name = "create_#{@model_name}_versions".camelize
|
19
|
-
migration_template "migration.rb.erb", "db/migrate/create_#{@model_name}_versions.rb"
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
@@ -1,135 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
gem 'activerecord'
|
3
|
-
require 'active_record'
|
4
|
-
require 'test/unit'
|
5
|
-
#require "#{File.dirname(__FILE__)}/../init"
|
6
|
-
require File.join(File.dirname(__FILE__), '../lib', 'acts_as_versionable')
|
7
|
-
|
8
|
-
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
9
|
-
|
10
|
-
def setup_db
|
11
|
-
ActiveRecord::Schema.define(:version => 1) do
|
12
|
-
create_table :articles do |t|
|
13
|
-
t.string :title
|
14
|
-
t.text :body
|
15
|
-
|
16
|
-
t.integer :user_id
|
17
|
-
t.integer :current_version
|
18
|
-
t.timestamps
|
19
|
-
end
|
20
|
-
|
21
|
-
create_table :article_versions do |t|
|
22
|
-
t.string :title
|
23
|
-
t.text :body
|
24
|
-
t.integer :article_id
|
25
|
-
t.integer :versioned_as
|
26
|
-
t.timestamps
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def teardown_db
|
32
|
-
ActiveRecord::Base.connection.tables.each do |table|
|
33
|
-
ActiveRecord::Base.connection.drop_table(table)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class Article < ActiveRecord::Base
|
38
|
-
acts_as_versionable
|
39
|
-
end
|
40
|
-
|
41
|
-
class VersionableTest < Test::Unit::TestCase
|
42
|
-
def setup
|
43
|
-
setup_db
|
44
|
-
|
45
|
-
(1..3).each { |c|
|
46
|
-
Article.create!(:title => c.to_s, :body => c.to_s)
|
47
|
-
}
|
48
|
-
end
|
49
|
-
|
50
|
-
def teardown
|
51
|
-
teardown_db
|
52
|
-
end
|
53
|
-
|
54
|
-
def test_if_mixed_methods_present
|
55
|
-
article = Article.first
|
56
|
-
[:versions, :version, :revert_to].each do |method|
|
57
|
-
assert_equal true, article.respond_to?(method)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_initial_versions_of_the_articles
|
62
|
-
Article.all.each do |a|
|
63
|
-
assert_equal 1, a.versions.size
|
64
|
-
assert_equal 1, a.version
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def test_version_change_to_2
|
69
|
-
article = Article.first
|
70
|
-
|
71
|
-
assert_not_nil article
|
72
|
-
assert_equal '1', article.title
|
73
|
-
assert_equal 1, article.version
|
74
|
-
|
75
|
-
article.update_attributes(:title => '4', :body => '4')
|
76
|
-
|
77
|
-
assert_equal 2, article.version
|
78
|
-
assert_equal 2, article.versions.size
|
79
|
-
assert_equal '4', article.title
|
80
|
-
assert_equal '4', article.body
|
81
|
-
end
|
82
|
-
|
83
|
-
def test_revert_to
|
84
|
-
article = Article.first
|
85
|
-
assert_not_nil article
|
86
|
-
|
87
|
-
article.update_attributes(:title => '4', :body => '4')
|
88
|
-
assert_equal 2, article.versions.size
|
89
|
-
|
90
|
-
article.revert_to(1)
|
91
|
-
assert_equal 1, article.version
|
92
|
-
assert_equal '1', article.title
|
93
|
-
assert_equal '1', article.body
|
94
|
-
|
95
|
-
article.revert_to(2)
|
96
|
-
assert_equal 2, article.version
|
97
|
-
assert_equal '4', article.title
|
98
|
-
assert_equal '4', article.body
|
99
|
-
|
100
|
-
assert_raise ActsAsVersionable::NoSuchVersionError do
|
101
|
-
article.revert_to(3)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def test_new_from_version
|
106
|
-
article = Article.first
|
107
|
-
assert_not_nil article
|
108
|
-
|
109
|
-
dummy = article.new_from_version 1
|
110
|
-
|
111
|
-
article.revert_to(1)
|
112
|
-
assert_equal dummy.title, article.title
|
113
|
-
assert_equal dummy.body, article.body
|
114
|
-
end
|
115
|
-
|
116
|
-
def test_get_version
|
117
|
-
article = Article.first
|
118
|
-
assert_not_nil article
|
119
|
-
|
120
|
-
article.update_attributes(:title => '5', :body => '5')
|
121
|
-
assert_equal 2, article.versions.count
|
122
|
-
|
123
|
-
first = article.versions[1]
|
124
|
-
one = article.versions.get_version(2)
|
125
|
-
|
126
|
-
assert_equal first.title, one.title
|
127
|
-
assert_equal first.body, one.body
|
128
|
-
end
|
129
|
-
|
130
|
-
def test_dependent_destroy
|
131
|
-
Article.destroy_all
|
132
|
-
assert_equal [], ArticleVersions.all
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|