mm-versionable 0.2
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.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
|