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