acts_as_versionable 0.3.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
- === Generator
10
-
11
- rails g versionable MODEL
12
+ add two fields to your model
12
13
 
13
- rails g model Document title:string body:text
14
- rake db:migrate
14
+ version_number:integer
15
+ version_id:integer
15
16
 
16
- _generate versionable table_
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
- create_table :document_versions do |t|
24
- t.column :title, :string
25
- t.column :body, :text
26
- t.column :current_version, :integer
27
- t.integer :document_id
28
- t.integer :versioned_as
29
- t.timestamps
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
- drop_table :document_versions
37
- remove_column :documents, :current_version
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
- document.new_from_version(2) => #<Document ...>
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
@@ -12,4 +12,4 @@ Rake::TestTask.new(:test) do |t|
12
12
  t.libs << 'test'
13
13
  t.pattern = 'test/**/*_test.rb'
14
14
  t.verbose = true
15
- end
15
+ end
@@ -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 = ["Carlos Segura"]
8
+ s.authors = ["carlos segura"]
9
9
  s.email = ["csegura@ideseg.com"]
10
10
  s.homepage = ""
11
- s.summary = "Minimalist engine for versions"
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
 
@@ -1,3 +1,3 @@
1
1
  module ActsAsVersionable
2
- VERSION = "0.3.5"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -1,118 +1,112 @@
1
- require 'acts_as_versionable/version'
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
- included do
11
- end
8
+ extend ActiveSupport::Concern
12
9
 
13
- module ClassMethods
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
- def revert_to(version)
57
- revision = versions.get_version(version)
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
- move_columns revision, self
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
- def new_from_version(version)
67
- revision = versions.get_version(version)
68
- dummy = self.class.new
69
-
70
- move_columns revision, dummy
71
-
72
- dummy
73
- end
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
- private
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
- def _get_version(version)
78
- revision = versions.find_by_versioned_as(version)
79
- raise NoSuchVersionError, "Couldn't find #{version} version" if revision.blank?
80
- revision
81
- end
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
- def apply_versioning
84
- unless self.local_changes
85
- version_content = {}
86
- last_version = version + 1
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
- move_columns self, version_content
62
+ # check if we are in main version
63
+ def versionable?
64
+ version_id.nil?
65
+ end
89
66
 
90
- version_content.merge!(:versioned_as => last_version)
67
+ # return the current version
68
+ def version
69
+ return last_version if versionable?
70
+ version_number
71
+ end
91
72
 
92
- if versions.create(version_content)
93
- self.local_changes = true
94
- self.update_attribute(:current_version, last_version)
95
- end
96
- end
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
- self.local_changes = false
99
- end
79
+ private
100
80
 
101
- def clear_old_versions
102
- return if self.max_versions == 0
103
- excess_baggage = self.current_version - self.max_versions
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("versioned_as <= ?",excess_baggage).destroy_all
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.class_eval { include ActsAsVersionable }
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.3.5
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
- - Carlos Segura
8
+ - carlos segura
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-20 00:00:00.000000000Z
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,10 +0,0 @@
1
- == 0.3.5
2
-
3
- * fixed generator
4
- * get_version
5
-
6
- == 0.3.4
7
-
8
- * generator only rejects id & timestamp columns
9
- * new_from_version returns a new record from a determined version
10
-
@@ -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
-