controlled_versioning 0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 006bad30ccaea1ec63957fcf41c9987bf2ba5027
4
+ data.tar.gz: 75b870f39473c849265c9d8baa990be953c52b2d
5
+ SHA512:
6
+ metadata.gz: 8978fffbb883fddfb408d0c27f634935e97be9fef9a573c83c2462acb4f12146064fd46d45ddaf8280646195adc24bd3404a5c9487fa489317411ee3a31201ba
7
+ data.tar.gz: 13f85a9d20a79641716580c601d801fc110f89b1dab9f60f59566c36b349dd19af898031818fc2aac4e2e6d03d06adadb80b4cb7fea0e888f7cbedc01128fb7f
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 Timothy Baron
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = ControlledVersioning
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ControlledVersioning'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
@@ -0,0 +1,4 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ no_revisions_made: "You must edit at least one attribute"
@@ -0,0 +1,110 @@
1
+ # Core
2
+ require "controlled_versioning/revision"
3
+ require "controlled_versioning/revision_auditor"
4
+ require "controlled_versioning/revision_builder"
5
+ require "controlled_versioning/revision_accepter"
6
+ require "controlled_versioning/initial_version_builder"
7
+
8
+ # Add utility classes
9
+ require "support/array_converter"
10
+ require "support/attribute_encapsulator"
11
+
12
+ module ControlledVersioning
13
+ module ActsAsVersionable
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ end
18
+
19
+ module ClassMethods
20
+ def acts_as_versionable(options = {})
21
+
22
+ send :include, InstanceMethods
23
+
24
+ def set_versionable_attribute_names(nonversionables)
25
+ nonversionables = [] unless nonversionables.present?
26
+ ArrayConverter.to_s!(nonversionables)
27
+ nonversionables += ["id", "updated_at", "created_at"]
28
+ attribute_names - nonversionables
29
+ end
30
+
31
+ def nested_associations
32
+ nested_attributes_options.keys
33
+ end
34
+
35
+ def has_nested_associations?
36
+ nested_associations.present?
37
+ end
38
+
39
+ attr_accessor :user
40
+
41
+ after_create :create_initial_version
42
+
43
+ cattr_accessor :nested_within
44
+ self.nested_within = options[:nested_within]
45
+
46
+ def is_a_nested_association?
47
+ nested_within.present?
48
+ end
49
+
50
+ if is_a_nested_association?
51
+ has_many :version_children,
52
+ class_name: 'ControlledVersioning::VersionChild',
53
+ as: :versionable
54
+ else
55
+ has_many :versions,
56
+ lambda { order("created_at ASC")},
57
+ class_name: 'ControlledVersioning::Version',
58
+ as: :versionable
59
+ end
60
+
61
+ cattr_accessor :versionable_attribute_names
62
+ self.versionable_attribute_names = options[:versionable_attributes] ||
63
+ set_versionable_attribute_names(
64
+ options[:nonversionable_attributes])
65
+ end
66
+ end
67
+
68
+ module InstanceMethods
69
+ def is_a_nested_association?
70
+ self.class.is_a_nested_association?
71
+ end
72
+
73
+ def versionable_attributes
74
+ versionable_attributes = self.versionable_attribute_names
75
+ ArrayConverter.to_s!(versionable_attributes)
76
+ self.attributes.slice(*versionable_attributes)
77
+ end
78
+
79
+ def initial_version
80
+ if is_a_nested_association?
81
+ version_children.first
82
+ else
83
+ versions.find_by(initial: true)
84
+ end
85
+ end
86
+
87
+ def submit_revision(suggested_attributes)
88
+ assign_attributes(suggested_attributes)
89
+ if invalid?
90
+ errors
91
+ elsif !RevisionAuditor.new(self).changes_original?
92
+ errors[:base] << I18n.t("errors.messages.no_revisions_made")
93
+ errors
94
+ else
95
+ version = versions.build
96
+ RevisionBuilder.new(versionable: self, version: version).version
97
+ version.save
98
+ version
99
+ end
100
+ end
101
+
102
+ private
103
+ def create_initial_version
104
+ InitialVersionBuilder.new(self)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ ActiveRecord::Base.send :include, ControlledVersioning::ActsAsVersionable
@@ -0,0 +1,49 @@
1
+ class InitialVersionBuilder < Revision
2
+
3
+ attr_reader :versionable, :version
4
+ def initialize(versionable)
5
+ @versionable = versionable
6
+ @version = build_version_type
7
+ build_initial_version
8
+ end
9
+
10
+ private
11
+ def build_version_type
12
+ if is_a_nested_association?
13
+ parent.initial_version.version_children.build(versionable: versionable)
14
+ else
15
+ versionable.versions.build(initial: true)
16
+ end
17
+ end
18
+
19
+ def build_initial_version
20
+ build_version_attributes
21
+ version.save
22
+ end
23
+
24
+ def build_version_attributes
25
+ versionable_attributes.each do |attr|
26
+ attr = AttributeEncapsulator.new(attr)
27
+ build_attribute(attr)
28
+ end
29
+ end
30
+
31
+ def build_attribute(attr)
32
+ version_attributes.build(
33
+ name: attr.key,
34
+ new_value: attr.value)
35
+ end
36
+
37
+ def is_a_nested_association?
38
+ versionable.is_a_nested_association?
39
+ end
40
+
41
+ def nested_within
42
+ versionable.nested_within
43
+ end
44
+
45
+ def parent
46
+ versionable.public_send(nested_within)
47
+ end
48
+
49
+ end
@@ -0,0 +1,34 @@
1
+ class Revision
2
+
3
+ private
4
+ def versionable_attributes
5
+ versionable.versionable_attributes
6
+ end
7
+
8
+ def changed_attributes
9
+ versionable.changed_attributes
10
+ end
11
+
12
+ def nested_associations
13
+ versionable.class.nested_associations
14
+ end
15
+
16
+ def version_attributes
17
+ version.version_attributes
18
+ end
19
+
20
+ def version_children
21
+ version.version_children
22
+ end
23
+
24
+ def versionable_changes(key)
25
+ values = versionable.changes[key]
26
+ ValueChange.new(values[0], values[1])
27
+ end
28
+
29
+ def previous_value(key)
30
+ versionable_changes(key).previous_value
31
+ end
32
+
33
+ ValueChange = Struct.new(:previous_value, :new_value)
34
+ end
@@ -0,0 +1,51 @@
1
+ class RevisionAccepter < Revision
2
+
3
+ attr_reader :version, :versionable
4
+ def initialize(version)
5
+ @version = version
6
+ @versionable = version.versionable
7
+ accept_revision
8
+ end
9
+
10
+ private
11
+ def accept_revision
12
+ create_or_update_versionable
13
+ update_version_children
14
+ end
15
+
16
+ def create_or_update_versionable
17
+ versionable.present? ? update_versionable : create_versionable
18
+ end
19
+
20
+ def new_attributes
21
+ version_attributes.each_with_object({}) {|v, h| h[v.name] = v.new_value }
22
+ end
23
+
24
+ def create_versionable
25
+ version.versionable = version.versionable_type.constantize.
26
+ create(new_attributes)
27
+ end
28
+
29
+ def update_versionable
30
+ versionable.update_attributes(new_attributes)
31
+ end
32
+
33
+ def update_version_children
34
+ version_children.each do |child|
35
+ update_or_destroy_child(child)
36
+ end
37
+ end
38
+
39
+ def update_or_destroy_child(child)
40
+ if child.marked_for_removal?
41
+ destroy_child(child)
42
+ else
43
+ RevisionAccepter.new(child)
44
+ end
45
+ end
46
+
47
+ def destroy_child(child)
48
+ child.versionable.destroy
49
+ end
50
+
51
+ end
@@ -0,0 +1,42 @@
1
+ class RevisionAuditor < Revision
2
+
3
+ attr_reader :versionable
4
+ def initialize(versionable)
5
+ @versionable = versionable
6
+ end
7
+
8
+ def changes_original?
9
+ return true if versionable.marked_for_destruction?
10
+ return true if changes_at_least_one_attribute?
11
+ return true if changes_at_least_one_nested_association?
12
+ return false
13
+ end
14
+
15
+ private
16
+ def changes_at_least_one_attribute?
17
+ versionable_attributes.each do |attr|
18
+ attr = AttributeEncapsulator.new(attr)
19
+ return true if changed_attributes.include?(attr.key)
20
+ end
21
+ return false
22
+ end
23
+
24
+ def changes_at_least_one_nested_association?
25
+ nested_associations.each do |association|
26
+ return true if association_changed?(association)
27
+ end
28
+ return false
29
+ end
30
+
31
+ def association_changed?(association)
32
+ versionable.public_send(association).each do |child|
33
+ return true if child_changed?(child)
34
+ end
35
+ return false
36
+ end
37
+
38
+ def child_changed?(child)
39
+ child.new_record? || child.marked_for_destruction? ||
40
+ RevisionAuditor.new(child).changes_original?
41
+ end
42
+ end
@@ -0,0 +1,60 @@
1
+ class RevisionBuilder < Revision
2
+
3
+ attr_reader :versionable, :version
4
+ def initialize(args)
5
+ @versionable = args[:versionable]
6
+ @version = args[:version]
7
+ build_revision
8
+ end
9
+
10
+ private
11
+ def build_revision
12
+ mark_for_removal
13
+ build_attributes
14
+ build_children
15
+ end
16
+
17
+ def mark_for_removal
18
+ version.marked_for_removal = true if versionable.marked_for_destruction?
19
+ end
20
+
21
+ def build_attributes
22
+ versionable_attributes.each do |attr|
23
+ attr = AttributeEncapsulator.new(attr)
24
+ if changed_attributes.include?(attr.key)
25
+ build_attribute(attr)
26
+ end
27
+ end
28
+ end
29
+
30
+ def build_attribute(attr)
31
+ version_attributes.build(
32
+ name: attr.key,
33
+ new_value: attr.value,
34
+ old_value: previous_value(attr.key))
35
+ end
36
+
37
+ def build_children
38
+ nested_associations.each do |association|
39
+ versionable.public_send(association).each do |child|
40
+ build_child(child)
41
+ end
42
+ end
43
+ end
44
+
45
+ def build_child(child)
46
+ if RevisionAuditor.new(child).changes_original?
47
+ version_child = build_version_child(child)
48
+ RevisionBuilder.new(versionable: child, version: version_child)
49
+ end
50
+ end
51
+
52
+ def build_version_child(child)
53
+ if child.new_record?
54
+ version_children.build(versionable_type: child.class.name)
55
+ else
56
+ version_children.build(versionable: child)
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,3 @@
1
+ module ControlledVersioning
2
+ VERSION = "0.6"
3
+ end
@@ -0,0 +1,30 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+ require 'rails/generators/active_record'
4
+
5
+ module ControlledVersioning
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include ::Rails::Generators::Migration
8
+
9
+ source_root File.expand_path('../../templates', __FILE__)
10
+
11
+ desc 'Generates (but does not run) a migration to add a versions table.'
12
+
13
+ def create_model_file
14
+ template "version.rb", "app/models/controlled_versioning/version.rb"
15
+ template "version_attribute.rb",
16
+ "app/models/controlled_versioning/version_attribute.rb"
17
+ template "version_child.rb",
18
+ "app/models/controlled_versioning/version_child.rb"
19
+ migration_template "create_versions.rb", "db/migrate/create_versions.rb"
20
+ migration_template "create_version_attributes.rb",
21
+ "db/migrate/create_version_attributes.rb"
22
+ migration_template "create_version_children.rb",
23
+ "db/migrate/create_version_children.rb"
24
+ end
25
+
26
+ def self.next_migration_number(dirname)
27
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ class CreateVersionAttributes < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :version_attributes do |t|
4
+ t.string :version_type, null: false
5
+ t.integer :version_id, null: false
6
+ t.string :name, null: false
7
+ t.text :old_value
8
+ t.text :new_value
9
+ end
10
+ add_index :version_attributes, [:version_type, :version_id]
11
+ end
12
+
13
+ def self.down
14
+ remove_index :version_attributes, [:version_type, :version_id]
15
+ drop_table :version_attributes
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ class CreateVersionChildren < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :version_children do |t|
4
+ t.string :version_type, null: false
5
+ t.integer :version_id, null: false
6
+ t.string :versionable_type
7
+ t.integer :versionable_id
8
+
9
+ t.boolean :marked_for_removal, null: false, default: false
10
+ end
11
+ add_index :version_children, [:version_type, :version_id]
12
+ add_index :version_children, [:versionable_type, :versionable_id]
13
+ end
14
+
15
+ def self.down
16
+ remove_index :version_children, [:version_type, :version_id]
17
+ remove_index :version_children, [:versionable_type, :versionable_id]
18
+ drop_table :version_children
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ class CreateVersions < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :versions do |t|
4
+ t.string :versionable_type, null: false
5
+ t.integer :versionable_id, null: false
6
+
7
+ t.integer :user_id
8
+
9
+ t.text :notes
10
+
11
+ t.boolean :initial, null: false, default: false
12
+
13
+ t.boolean :marked_for_removal, null: false, default: false
14
+
15
+ t.boolean :approved, null: false, default: false
16
+ t.boolean :declined, null: false, default: false
17
+ t.boolean :pending, null: false, default: true
18
+
19
+ t.datetime :created_at
20
+ end
21
+ add_index :versions, :initial
22
+ add_index :versions, [:versionable_type, :versionable_id]
23
+ end
24
+
25
+ def self.down
26
+ remove_index :versions, :initial
27
+ remove_index :versions, [:verstionable_type, :versionable_id]
28
+ drop_table :versions
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ class ControlledVersioning::Version < ActiveRecord::Base
2
+
3
+ belongs_to :versionable, polymorphic: true
4
+ belongs_to :user
5
+ has_many :version_attributes, as: :version
6
+ has_many :version_children, as: :version
7
+
8
+ validates :versionable, presence: true
9
+
10
+ def accept
11
+ RevisionAccepter.new(self)
12
+ update_attributes(pending: false, approved: true)
13
+ end
14
+
15
+ def decline
16
+ update_attributes(pending: false, declined: true)
17
+ end
18
+
19
+ end
@@ -0,0 +1,7 @@
1
+ class ControlledVersioning::VersionAttribute < ActiveRecord::Base
2
+
3
+ belongs_to :version, polymorphic: true
4
+
5
+ validates :name, presence: true
6
+
7
+ end
@@ -0,0 +1,8 @@
1
+ class ControlledVersioning::VersionChild < ActiveRecord::Base
2
+
3
+ belongs_to :versionable, polymorphic: true
4
+ belongs_to :version, polymorphic: true
5
+ has_many :version_attributes, as: :version
6
+ has_many :version_children, as: :version
7
+
8
+ end
@@ -0,0 +1,6 @@
1
+ class ArrayConverter
2
+
3
+ def self.to_s!(array)
4
+ array.map! { |s| s.to_s } if array.present?
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ class AttributeEncapsulator
2
+
3
+ attr_reader :attr
4
+ def initialize(attr)
5
+ @attr = AttrStruct.new(attr[0], attr[1])
6
+ end
7
+
8
+ def key
9
+ attr.key
10
+ end
11
+
12
+ def value
13
+ attr.value
14
+ end
15
+
16
+ private
17
+ AttrStruct = Struct.new(:key, :value)
18
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :controlled_versioning do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: controlled_versioning
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.6'
5
+ platform: ruby
6
+ authors:
7
+ - timothythehuman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: debugger
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-spork
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: factory_girl_rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: With Controlled Versioning, you can make any model versionable. This
112
+ will record suggested changes to the model, which can then be approved or declined
113
+ according to your application's design.
114
+ email:
115
+ - timothythehuman@gmail.com
116
+ executables: []
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - config/locales/en.yml
121
+ - lib/tasks/controlled_versioning_tasks.rake
122
+ - lib/support/attribute_encapsulator.rb
123
+ - lib/support/array_converter.rb
124
+ - lib/generators/templates/create_version_children.rb
125
+ - lib/generators/templates/create_versions.rb
126
+ - lib/generators/templates/version_attribute.rb
127
+ - lib/generators/templates/version_child.rb
128
+ - lib/generators/templates/version.rb
129
+ - lib/generators/templates/create_version_attributes.rb
130
+ - lib/generators/controlled_versioning/install_generator.rb
131
+ - lib/controlled_versioning/revision_accepter.rb
132
+ - lib/controlled_versioning/revision_builder.rb
133
+ - lib/controlled_versioning/revision.rb
134
+ - lib/controlled_versioning/revision_auditor.rb
135
+ - lib/controlled_versioning/initial_version_builder.rb
136
+ - lib/controlled_versioning/version.rb
137
+ - lib/controlled_versioning.rb
138
+ - MIT-LICENSE
139
+ - Rakefile
140
+ - README.rdoc
141
+ homepage: https://github.com/timothythehuman/controlled_versioning
142
+ licenses: []
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.0.5
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Adds model versioning to a Rails app, with the ability to approve and declined
164
+ revisions.
165
+ test_files: []