mm-versionable 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +28 -0
- data/Rakefile +39 -0
- data/config.ru +11 -0
- data/lib/versionable/models/version.rb +19 -0
- data/lib/versionable/plugins/versionable.rb +111 -0
- data/lib/versionable/version.rb +3 -0
- data/lib/versionable.rb +2 -0
- data/mm-versionable.gemspec +64 -0
- data/test/config/config.rb +1 -0
- data/test/config/database.yml +5 -0
- data/test/models/post.rb +8 -0
- data/test/models/user.rb +11 -0
- data/test/performance/read_write.rb +29 -0
- data/test/test_helper.rb +20 -0
- data/test/unit/test_versioning.rb +84 -0
- metadata +107 -0
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
MongoMapper Versioning
|
2
|
+
======================
|
3
|
+
A MongoMapper plugin which enables document versioning.
|
4
|
+
|
5
|
+
Install
|
6
|
+
-------
|
7
|
+
$ gem install mm-versionable
|
8
|
+
|
9
|
+
Note on Patches/Pull Requests
|
10
|
+
-----------------------------
|
11
|
+
*Fork the project
|
12
|
+
*Make your feature addition or bug fix.
|
13
|
+
*Add tests for it. This is critical so that things dont break unintentionally.
|
14
|
+
*Commit, do not make any changes in the rakefile, version, or history. (If you want to have your own version, that is fine but bump the version in a commit by itself so I can ignore it when I pull)
|
15
|
+
*Send me a pull requests.
|
16
|
+
|
17
|
+
Development
|
18
|
+
-----------
|
19
|
+
|
20
|
+
Problems or Questions?
|
21
|
+
----------------------
|
22
|
+
Hit up on the mongomapper google group: http://groups.google.com/group/mongomapper
|
23
|
+
Hop on IRC: irc://chat.freenode.net/#mongomapper
|
24
|
+
I am available on IRC with the name dhruvasagar, you could alternatively also contact me directly on dhruva.sagar@gmail.com
|
25
|
+
|
26
|
+
Copyright
|
27
|
+
---------
|
28
|
+
See LICENCE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'jeweler'
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), '/lib/versionable/version')
|
6
|
+
|
7
|
+
require 'rake/testtask'
|
8
|
+
namespace :test do
|
9
|
+
Rake::TestTask.new(:units) do |test|
|
10
|
+
test.libs << 'test'
|
11
|
+
test.ruby_opts << '-rubygems'
|
12
|
+
test.pattern = 'test/unit/**/test_*.rb'
|
13
|
+
test.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
Rake::TestTask.new(:performance) do |test|
|
17
|
+
test.libs << 'test'
|
18
|
+
test.ruby_opts << '-rubygems'
|
19
|
+
test.pattern = 'test/performance/**/*.rb'
|
20
|
+
test.verbose = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
task :default => 'test:units'
|
25
|
+
|
26
|
+
Jeweler::Tasks.new do |gem|
|
27
|
+
gem.name = 'mm-versionable'
|
28
|
+
gem.summary = 'A MongoMapper extension for document versioning'
|
29
|
+
gem.description = 'A MongoMapper extension that enables document versionable'
|
30
|
+
gem.email = 'dhruva.sagar@gmail.com'
|
31
|
+
gem.homepage = 'https://github.com/artha42/mm-versionable/'
|
32
|
+
gem.authors = ['Dhruva Sagar']
|
33
|
+
gem.version = Versionable::Version
|
34
|
+
|
35
|
+
gem.add_dependency('differ')
|
36
|
+
gem.add_dependency('mongo_mapper')
|
37
|
+
end
|
38
|
+
|
39
|
+
Jeweler::GemcutterTasks.new
|
data/config.ru
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
$LOAD_PATH.unshift(".") unless $LOAD_PATH.include?(".")
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
$:.unshift(::File.join(::File.expand_path('.'), '/lib'))
|
7
|
+
require 'versionable.rb'
|
8
|
+
|
9
|
+
require 'test/config/config'
|
10
|
+
require 'test/models/post'
|
11
|
+
require 'test/models/user'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Version
|
2
|
+
include MongoMapper::Document
|
3
|
+
|
4
|
+
key :data, Hash
|
5
|
+
key :date, Time
|
6
|
+
key :pos, Integer
|
7
|
+
key :doc_id, String
|
8
|
+
key :message, String
|
9
|
+
key :updater_id, String
|
10
|
+
|
11
|
+
def content(key)
|
12
|
+
cdata = self.data[key]
|
13
|
+
if cdata.respond_to?(:join)
|
14
|
+
cdata.join(" ")
|
15
|
+
else
|
16
|
+
cdata
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'versionable/models/version'
|
2
|
+
|
3
|
+
module Versionable
|
4
|
+
def self.configure(model)
|
5
|
+
model.enable_versioning
|
6
|
+
end
|
7
|
+
|
8
|
+
module InstanceMethods
|
9
|
+
def save(options={})
|
10
|
+
save_version(options[:updater_id]) if self.respond_to?(:rolling_back) && !rolling_back
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def save_version(updater_id=nil)
|
16
|
+
if self.respond_to?(:versions)
|
17
|
+
version = self.current_version
|
18
|
+
version.message = self.version_message
|
19
|
+
if self.versions.empty?
|
20
|
+
version.pos = 0
|
21
|
+
else
|
22
|
+
version.pos = self.versions.last.pos + 1
|
23
|
+
end
|
24
|
+
version.updater_id = updater_id
|
25
|
+
version.save
|
26
|
+
|
27
|
+
self.versions.shift
|
28
|
+
self.versions << version
|
29
|
+
|
30
|
+
@versions_count = @versions_count.to_i + 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
def enable_versioning(opts={})
|
37
|
+
attr_accessor :rolling_back
|
38
|
+
|
39
|
+
key :version_message, String
|
40
|
+
key :version_number, Integer
|
41
|
+
|
42
|
+
define_method(:versions_count) do
|
43
|
+
@versions_count ||= Version.count(:doc_id => self._id.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method(:versions) do
|
47
|
+
@versions ||= Version.all(:doc_id => self._id.to_s, :order => 'pos desc', :limit => (opts[:limit] || 10)).reverse
|
48
|
+
end
|
49
|
+
|
50
|
+
define_method(:all_versions) do
|
51
|
+
Version.where(:doc_id => self._id.to_s).sort(:pos.desc)
|
52
|
+
end
|
53
|
+
|
54
|
+
define_method(:rollback) do |pos = nil|
|
55
|
+
#The last version is always same as the current version, so -2 instead of -1
|
56
|
+
pos = self.versions.count-2 if pos.nil?
|
57
|
+
version = self.version_at(pos)
|
58
|
+
|
59
|
+
if version
|
60
|
+
self.attributes = version.data
|
61
|
+
end
|
62
|
+
|
63
|
+
self.version_number = version.pos
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
define_method(:rollback!) do |pos = nil|
|
68
|
+
self.rollback(pos)
|
69
|
+
|
70
|
+
@rolling_back = true
|
71
|
+
save!
|
72
|
+
@rolling_back = false
|
73
|
+
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
define_method(:diff) do |key, pos1, pos2, format = :html|
|
78
|
+
version1 = self.version_at(pos1)
|
79
|
+
version2 = self.version_at(pos2)
|
80
|
+
|
81
|
+
Differ.diff_by_word(version1.content(key), version2.content(key)).format_as(format)
|
82
|
+
end
|
83
|
+
|
84
|
+
define_method(:current_version) do
|
85
|
+
Version.new(:data => self.attributes, :date => Time.now, :doc_id => self._id.to_s)
|
86
|
+
end
|
87
|
+
|
88
|
+
define_method(:version_at) do |pos|
|
89
|
+
case pos
|
90
|
+
when :current
|
91
|
+
current_version
|
92
|
+
when :first
|
93
|
+
index = self.versions.index {|v| v.pos == 0}
|
94
|
+
version = self.versions[index] if index
|
95
|
+
version ||= Version.first(:user_id => self._id.to_s, :pos => 0)
|
96
|
+
version
|
97
|
+
when :last
|
98
|
+
#The last version is always same as the current version, so -2 instead of -1
|
99
|
+
self.versions[self.versions.count-2]
|
100
|
+
when :latest
|
101
|
+
self.versions.last
|
102
|
+
else
|
103
|
+
index = self.versions.index {|v| v.pos == pos}
|
104
|
+
version = self.versions[index] if index
|
105
|
+
version ||= Version.first(:user_id => self._id.to_s, :pos => pos)
|
106
|
+
version
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/versionable.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mm-versionable}
|
8
|
+
s.version = "0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Dhruva Sagar"]
|
12
|
+
s.date = %q{2010-11-26}
|
13
|
+
s.description = %q{A MongoMapper extension that enables document versionable}
|
14
|
+
s.email = %q{dhruva.sagar@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"README.md",
|
20
|
+
"Rakefile",
|
21
|
+
"config.ru",
|
22
|
+
"lib/versionable.rb",
|
23
|
+
"lib/versionable/models/version.rb",
|
24
|
+
"lib/versionable/plugins/versionable.rb",
|
25
|
+
"lib/versionable/version.rb",
|
26
|
+
"mm-versionable.gemspec",
|
27
|
+
"test/config/config.rb",
|
28
|
+
"test/config/database.yml",
|
29
|
+
"test/models/post.rb",
|
30
|
+
"test/models/user.rb",
|
31
|
+
"test/performance/read_write.rb",
|
32
|
+
"test/test_helper.rb",
|
33
|
+
"test/unit/test_versioning.rb"
|
34
|
+
]
|
35
|
+
s.homepage = %q{https://github.com/artha42/mm-versionable/}
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = %q{1.3.7}
|
38
|
+
s.summary = %q{A MongoMapper extension for document versioning}
|
39
|
+
s.test_files = [
|
40
|
+
"test/config/config.rb",
|
41
|
+
"test/models/post.rb",
|
42
|
+
"test/models/user.rb",
|
43
|
+
"test/performance/read_write.rb",
|
44
|
+
"test/test_helper.rb",
|
45
|
+
"test/unit/test_versioning.rb"
|
46
|
+
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<differ>, [">= 0"])
|
54
|
+
s.add_runtime_dependency(%q<mongo_mapper>, [">= 0"])
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<differ>, [">= 0"])
|
57
|
+
s.add_dependency(%q<mongo_mapper>, [">= 0"])
|
58
|
+
end
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<differ>, [">= 0"])
|
61
|
+
s.add_dependency(%q<mongo_mapper>, [">= 0"])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
MongoMapper.database = YAML.load(File.read('config/database.yml'))[ENV['RACK_ENV']]['database'] rescue 'versionable_test'
|
data/test/models/post.rb
ADDED
data/test/models/user.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../lib/versionable')
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
MongoMapper.database = 'versionable_performance'
|
6
|
+
|
7
|
+
class Foo
|
8
|
+
include MongoMapper::Document
|
9
|
+
plugin Versionable
|
10
|
+
|
11
|
+
enable_versioning
|
12
|
+
|
13
|
+
key :approved, Boolean
|
14
|
+
key :count, Integer
|
15
|
+
key :approved_at, Time
|
16
|
+
key :expire_on, Date
|
17
|
+
end
|
18
|
+
Foo.collection.remove
|
19
|
+
|
20
|
+
Benchmark.bm(5) do |x|
|
21
|
+
ids = []
|
22
|
+
hhids = []
|
23
|
+
x.report("write ") do
|
24
|
+
1000.times { |i| ids << Foo.create(:count => 0, :approved => true, :approved_at => Time.now, :expire_on => Date.today).id }
|
25
|
+
end
|
26
|
+
x.report("read ") do
|
27
|
+
ids.each { |id| Foo.first(:id => id) }
|
28
|
+
end
|
29
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.unshift('.') unless $LOAD_PATH.include?('.')
|
2
|
+
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/versionable')
|
4
|
+
|
5
|
+
require 'pp'
|
6
|
+
require 'shoulda'
|
7
|
+
|
8
|
+
require 'test/models/post'
|
9
|
+
require 'test/models/user'
|
10
|
+
|
11
|
+
require 'config/config'
|
12
|
+
|
13
|
+
User.delete_all
|
14
|
+
u = User.create(:fname => 'dhruva', :lname => 'sagar', :email => 'dhruva.sagar@gmail.com')
|
15
|
+
u.fname = 'Dhruva'
|
16
|
+
u.lname = 'Sagar'
|
17
|
+
u.save
|
18
|
+
|
19
|
+
u.posts << Post.new(:title => 'Dummy title', :body => 'Dummy post body', :date => Time.now)
|
20
|
+
u.save
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class VersioningTest < Test::Unit::TestCase
|
4
|
+
context 'Versioning enabled' do
|
5
|
+
setup do
|
6
|
+
@user = User.first
|
7
|
+
end
|
8
|
+
should 'respond to method version_message' do
|
9
|
+
assert @user.respond_to?(:version_message)
|
10
|
+
end
|
11
|
+
should 'respond to method version_number' do
|
12
|
+
assert @user.respond_to?(:version_number)
|
13
|
+
end
|
14
|
+
should 'respond to method versions' do
|
15
|
+
assert @user.respond_to?(:versions)
|
16
|
+
end
|
17
|
+
should 'respond to method all_versions' do
|
18
|
+
assert @user.respond_to?(:all_versions)
|
19
|
+
end
|
20
|
+
should 'respond to method rollback' do
|
21
|
+
assert @user.respond_to?(:rollback)
|
22
|
+
end
|
23
|
+
should 'respond to method rollback!' do
|
24
|
+
assert @user.respond_to?(:rollback!)
|
25
|
+
end
|
26
|
+
should 'respond to method diff' do
|
27
|
+
assert @user.respond_to?(:diff)
|
28
|
+
end
|
29
|
+
should 'respond to method current_version' do
|
30
|
+
assert @user.respond_to?(:current_version)
|
31
|
+
end
|
32
|
+
should 'respond to method version_at' do
|
33
|
+
assert @user.respond_to?(:version_at)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'Version manipulations' do
|
38
|
+
setup do
|
39
|
+
@user = User.first
|
40
|
+
end
|
41
|
+
should 'return the total versions_count' do
|
42
|
+
assert @user.versions_count
|
43
|
+
end
|
44
|
+
should 'return last 10 or lesser versions' do
|
45
|
+
assert @user.versions
|
46
|
+
assert @user.versions.count <= 10
|
47
|
+
end
|
48
|
+
should 'return the plucky query for all versions' do
|
49
|
+
assert @user.all_versions
|
50
|
+
assert @user.all_versions.is_a?(Plucky::Query)
|
51
|
+
end
|
52
|
+
should 'load first version on rollback :first' do
|
53
|
+
assert @user.rollback(:first).fname == 'dhruva'
|
54
|
+
assert @user.version_number == 0
|
55
|
+
end
|
56
|
+
should 'load last version on rollback :last' do
|
57
|
+
assert @user.rollback(:last).fname == 'Dhruva'
|
58
|
+
assert @user.posts.empty?
|
59
|
+
assert @user.version_number == (@user.versions_count - 2)
|
60
|
+
end
|
61
|
+
should 'load latest version on rollback :latest' do
|
62
|
+
assert @user.rollback(:latest).fname == 'Dhruva'
|
63
|
+
assert !@user.posts.empty?
|
64
|
+
assert @user.version_number == (@user.versions_count - 1)
|
65
|
+
end
|
66
|
+
should 'revert to first version on rollback!' do
|
67
|
+
@user.rollback!(:first)
|
68
|
+
assert @user.fname == 'dhruva'
|
69
|
+
assert @user.version_number == 0
|
70
|
+
end
|
71
|
+
should 'revert to last version on rollback!' do
|
72
|
+
@user.rollback!(:last)
|
73
|
+
assert @user.fname == 'Dhruva'
|
74
|
+
assert @user.posts.empty?
|
75
|
+
assert @user.version_number == (@user.versions_count - 2)
|
76
|
+
end
|
77
|
+
should 'revert to latest version on rollback!' do
|
78
|
+
@user.rollback!(:latest)
|
79
|
+
assert @user.fname == 'Dhruva'
|
80
|
+
assert !@user.posts.empty?
|
81
|
+
assert @user.version_number == (@user.versions_count - 1)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mm-versionable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
version: "0.2"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Dhruva Sagar
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-11-26 00:00:00 +05:30
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: differ
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: mongo_mapper
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
43
|
+
type: :runtime
|
44
|
+
version_requirements: *id002
|
45
|
+
description: A MongoMapper extension that enables document versionable
|
46
|
+
email: dhruva.sagar@gmail.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README.md
|
53
|
+
files:
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- config.ru
|
57
|
+
- lib/versionable.rb
|
58
|
+
- lib/versionable/models/version.rb
|
59
|
+
- lib/versionable/plugins/versionable.rb
|
60
|
+
- lib/versionable/version.rb
|
61
|
+
- mm-versionable.gemspec
|
62
|
+
- test/config/config.rb
|
63
|
+
- test/config/database.yml
|
64
|
+
- test/models/post.rb
|
65
|
+
- test/models/user.rb
|
66
|
+
- test/performance/read_write.rb
|
67
|
+
- test/test_helper.rb
|
68
|
+
- test/unit/test_versioning.rb
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: https://github.com/artha42/mm-versionable/
|
71
|
+
licenses: []
|
72
|
+
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.3.7
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: A MongoMapper extension for document versioning
|
101
|
+
test_files:
|
102
|
+
- test/config/config.rb
|
103
|
+
- test/models/post.rb
|
104
|
+
- test/models/user.rb
|
105
|
+
- test/performance/read_write.rb
|
106
|
+
- test/test_helper.rb
|
107
|
+
- test/unit/test_versioning.rb
|