acts_as_revisable 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +221 -0
- data/Rakefile +44 -0
- data/generators/revisable_migration/revisable_migration_generator.rb +21 -0
- data/generators/revisable_migration/templates/migration.rb +14 -0
- data/lib/acts_as_revisable.rb +10 -0
- data/lib/acts_as_revisable/acts/common.rb +209 -0
- data/lib/acts_as_revisable/acts/deletable.rb +33 -0
- data/lib/acts_as_revisable/acts/revisable.rb +485 -0
- data/lib/acts_as_revisable/acts/revision.rb +148 -0
- data/lib/acts_as_revisable/base.rb +54 -0
- data/lib/acts_as_revisable/gem_spec_options.rb +18 -0
- data/lib/acts_as_revisable/options.rb +22 -0
- data/lib/acts_as_revisable/quoted_columns.rb +31 -0
- data/lib/acts_as_revisable/validations.rb +11 -0
- data/lib/acts_as_revisable/version.rb +11 -0
- data/rails/init.rb +1 -0
- data/spec/associations_spec.rb +22 -0
- data/spec/branch_spec.rb +42 -0
- data/spec/deletable_spec.rb +16 -0
- data/spec/find_spec.rb +34 -0
- data/spec/general_spec.rb +115 -0
- data/spec/options_spec.rb +83 -0
- data/spec/quoted_columns_spec.rb +19 -0
- data/spec/revert_spec.rb +42 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +121 -0
- data/spec/sti_spec.rb +42 -0
- data/spec/validations_spec.rb +25 -0
- metadata +86 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 JamLab, LLC.
|
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,221 @@
|
|
1
|
+
= acts_as_revisable
|
2
|
+
|
3
|
+
http://github.com/rich/acts_as_revisable
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
acts_as_revisable enables revision tracking, querying, reverting and branching of ActiveRecord models. It does this while providing more Rails-like API than similar plugins. This includes extensions of standard ActiveRecord methods and numerous custom callbacks for the entire AAR life-cycle.
|
8
|
+
|
9
|
+
This plugin wouldn't exist without Rick Olsen's acts_as_versioned. AAV has been a critical part of practically every Rails project I've developed. It's only through extensive usage of AAV that the concepts for AAR came about.
|
10
|
+
|
11
|
+
== FEATURES:
|
12
|
+
|
13
|
+
* Both the revisable and revision models must be explicitly defined.
|
14
|
+
Yes, this is a feature. The less magic needed the better. This allows you to build up your revision models just as you would any other.
|
15
|
+
|
16
|
+
If you absolutely need a generated revision model, you may pass ":generate_revision_class => true" to acts_as_revisable and it will generate the class at runtime for you. Think of this like scaffolding and not to be kept around for a real application.
|
17
|
+
|
18
|
+
* Numerous custom callbacks for both revisable and revision models.
|
19
|
+
* revisable models
|
20
|
+
* before_revise
|
21
|
+
* after_revise
|
22
|
+
* before_revert
|
23
|
+
* after_revert
|
24
|
+
* before_changeset
|
25
|
+
* after_changeset
|
26
|
+
* after_branch_created
|
27
|
+
* before_revise_on_destroy (when :on_destroy => :revise is set)
|
28
|
+
* after_revise_on_destroy (when :on_destroy => :revise is set)
|
29
|
+
* revision models
|
30
|
+
* before_restore
|
31
|
+
* after_restore
|
32
|
+
* both revisable and revision models
|
33
|
+
* before_branch
|
34
|
+
* after_branch
|
35
|
+
These work like any other ActiveRecord callbacks. The before_* callbacks can stop the the action. This uses the Callbacks module in ActiveSupport.
|
36
|
+
* Works with a single table.
|
37
|
+
* Provides migration generators to add the revisable columns.
|
38
|
+
* Grouping several revisable actions into a single revision (changeset).
|
39
|
+
* Monitor all or just specified columns to trigger a revision.
|
40
|
+
* Uses ActiveRecord's dirty attribute tracking.
|
41
|
+
* Several ways to find revisions including:
|
42
|
+
* revision number
|
43
|
+
* relative keywords (:first, :previous and :last)
|
44
|
+
* timestamp
|
45
|
+
* Reverting
|
46
|
+
* Branching
|
47
|
+
* Selectively disable revision tracking
|
48
|
+
* Naming revisions
|
49
|
+
|
50
|
+
== SYNOPSIS:
|
51
|
+
|
52
|
+
Given a simple model:
|
53
|
+
|
54
|
+
class Project < ActiveRecord::Base
|
55
|
+
# columns: id, name, unimportant, created_at
|
56
|
+
end
|
57
|
+
|
58
|
+
Let's make the projects table revisable:
|
59
|
+
|
60
|
+
ruby script/generate revisable_migration Project
|
61
|
+
rake db:migrate
|
62
|
+
|
63
|
+
Now Project itself:
|
64
|
+
|
65
|
+
class Project < ActiveRecord::Base
|
66
|
+
has_one :owner
|
67
|
+
|
68
|
+
acts_as_revisable do
|
69
|
+
revision_class_name "Session"
|
70
|
+
except :unimportant
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Create the revision class:
|
75
|
+
|
76
|
+
class Session < ActiveRecord::Base
|
77
|
+
# we can accept the more standard hash syntax
|
78
|
+
acts_as_revision :revisable_class_name => "Project"
|
79
|
+
end
|
80
|
+
|
81
|
+
Some example usage:
|
82
|
+
|
83
|
+
@project = Project.create(:name => "Rich", :unimportant => "some text")
|
84
|
+
@project.revision_number # => 0
|
85
|
+
|
86
|
+
@project.update_attribute(:unimportant, "more text")
|
87
|
+
@project.revision_number # => 0
|
88
|
+
|
89
|
+
@project.name = "Stephen"
|
90
|
+
@project.save(:without_revision => true)
|
91
|
+
@project.name # => "Stephen"
|
92
|
+
@project.revision_number # => 0
|
93
|
+
|
94
|
+
@project.name = "Sam"
|
95
|
+
@project.save(:revision_name => "Changed name")
|
96
|
+
@project.revision_number # => 1
|
97
|
+
|
98
|
+
@project.updated_attribute(:name, "Third")
|
99
|
+
@project.revision_number # => 2
|
100
|
+
|
101
|
+
Navigating revisions:
|
102
|
+
|
103
|
+
@previous = @project.find_revision(:previous)
|
104
|
+
# or
|
105
|
+
@previous = @project.revisions.first
|
106
|
+
|
107
|
+
@previous.name # => "Sam"
|
108
|
+
@previous.current_revision.name # => "Third"
|
109
|
+
@previous.project.name # => "Third"
|
110
|
+
@previous.revision_name # => "Changed name"
|
111
|
+
|
112
|
+
@previous.previous.name # => "Rich"
|
113
|
+
|
114
|
+
# Forcing the creation of a new revision.
|
115
|
+
@project.updated_attribute("Rogelio")
|
116
|
+
@project.revision_number # => 3
|
117
|
+
|
118
|
+
@newest = @project.find_revision(:previous)
|
119
|
+
@newest.ancestors.map(&:name) # => ["Third", "Rich"]
|
120
|
+
|
121
|
+
@oldest = @project.find_revision(:first)
|
122
|
+
@oldest.descendants.map(&:name) # => ["Sam", "Third"]
|
123
|
+
|
124
|
+
Reverting:
|
125
|
+
|
126
|
+
@project.revert_to!(:previous)
|
127
|
+
@project.revision_number # => 2
|
128
|
+
@project.name # => "Rich"
|
129
|
+
|
130
|
+
@project.revert_to!(1, :without_revision => true)
|
131
|
+
@project.revision_number # => 2
|
132
|
+
@project.name # => "Sam"
|
133
|
+
|
134
|
+
Branching:
|
135
|
+
|
136
|
+
@branch = @project.branch(:name => "Bruno")
|
137
|
+
@branch.revision_number # => 0
|
138
|
+
@branch.branch_source.name # => "Sam"
|
139
|
+
|
140
|
+
Changesets:
|
141
|
+
|
142
|
+
@project.revision_number # => 2
|
143
|
+
|
144
|
+
@project.changeset! do
|
145
|
+
@project.name = "Josh"
|
146
|
+
|
147
|
+
# save would normally trigger a revision
|
148
|
+
@project.save
|
149
|
+
|
150
|
+
# update_attribute triggers a save triggering a revision (normally)
|
151
|
+
@project.updated_attribute(:name, "Chris")
|
152
|
+
|
153
|
+
# revise! normally forces a revision to be created
|
154
|
+
@project.revise!
|
155
|
+
end
|
156
|
+
|
157
|
+
# our revision number has only incremented by one
|
158
|
+
@project.revision_number # => 3
|
159
|
+
|
160
|
+
Maybe we don't want to be able to branch from revisions:
|
161
|
+
|
162
|
+
class Session < ActiveRecord::Base
|
163
|
+
# assuming we still have the other code from Session above
|
164
|
+
|
165
|
+
before_branch do
|
166
|
+
false
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
@project.revisions.first.branch # Raises an exception
|
171
|
+
@project.branch # works as expected
|
172
|
+
|
173
|
+
If the owner isn't set let's prevent reverting:
|
174
|
+
|
175
|
+
class Project < ActiveRecord::Base
|
176
|
+
# assuming we still have the other code from Project above
|
177
|
+
|
178
|
+
before_revert :check_owner_befor_reverting
|
179
|
+
def check_owner_befor_reverting
|
180
|
+
false unless self.owner?
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
== REQUIREMENTS:
|
185
|
+
|
186
|
+
This plugin requires Rails 2.3. Use version 0.9.8 of this plugin for Rails 2.1 and 2.2.
|
187
|
+
|
188
|
+
== INSTALL:
|
189
|
+
|
190
|
+
acts_as_revisable uses Rails' new ability to use gems as plugins. Installing AAR is as simple as installing a gem:
|
191
|
+
|
192
|
+
sudo gem install rich-acts_as_revisable --source=http://gems.github.com
|
193
|
+
|
194
|
+
Once the gem is installed you'll want to activate it in your Rails app by adding the following line to config/environment.rb:
|
195
|
+
|
196
|
+
config.gem "rich-acts_as_revisable", :lib => "acts_as_revisable", :source => "http://gems.github.com"
|
197
|
+
|
198
|
+
== LICENSE:
|
199
|
+
|
200
|
+
(The MIT License)
|
201
|
+
|
202
|
+
Copyright (c) 2009 Rich Cavanaugh
|
203
|
+
|
204
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
205
|
+
a copy of this software and associated documentation files (the
|
206
|
+
'Software'), to deal in the Software without restriction, including
|
207
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
208
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
209
|
+
permit persons to whom the Software is furnished to do so, subject to
|
210
|
+
the following conditions:
|
211
|
+
|
212
|
+
The above copyright notice and this permission notice shall be
|
213
|
+
included in all copies or substantial portions of the Software.
|
214
|
+
|
215
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
216
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
217
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
218
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
219
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
220
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
221
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'fileutils'
|
4
|
+
require "#{File.dirname(__FILE__)}/lib/acts_as_revisable/version"
|
5
|
+
require "#{File.dirname(__FILE__)}/lib/acts_as_revisable/gem_spec_options"
|
6
|
+
|
7
|
+
Rake::RDocTask.new do |rdoc|
|
8
|
+
files = ['README.rdoc','LICENSE','lib/**/*.rb','doc/**/*.rdoc','spec/*.rb']
|
9
|
+
rdoc.rdoc_files.add(files)
|
10
|
+
rdoc.main = 'README.rdoc'
|
11
|
+
rdoc.title = 'acts_as_revisable RDoc'
|
12
|
+
rdoc.rdoc_dir = 'doc'
|
13
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
14
|
+
end
|
15
|
+
|
16
|
+
spec = Gem::Specification.new do |s|
|
17
|
+
WithoutScope::ActsAsRevisable::GemSpecOptions::HASH.each do |key, value|
|
18
|
+
s.send("#{key.to_s}=",value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Rake::GemPackageTask.new(spec) do |package|
|
23
|
+
package.gem_spec = spec
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Generate the static gemspec required for github."
|
27
|
+
task :generate_gemspec do
|
28
|
+
options = WithoutScope::ActsAsRevisable::GemSpecOptions::HASH.clone
|
29
|
+
options[:name] = "acts_as_revisable"
|
30
|
+
|
31
|
+
spec = ["Gem::Specification.new do |s|"]
|
32
|
+
options.each do |key, value|
|
33
|
+
spec << " s.#{key.to_s} = #{value.inspect}"
|
34
|
+
end
|
35
|
+
spec << "end"
|
36
|
+
|
37
|
+
open("acts_as_revisable.gemspec", "w").write(spec.join("\n"))
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Install acts_as_revisable"
|
41
|
+
task :install => :repackage do
|
42
|
+
options = WithoutScope::ActsAsRevisable::GemSpecOptions::HASH.clone
|
43
|
+
sh %{sudo gem install pkg/#{options[:name]}-#{spec.version} --no-rdoc --no-ri}
|
44
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class RevisableMigrationGenerator < Rails::Generator::NamedBase
|
2
|
+
def manifest
|
3
|
+
record do |m|
|
4
|
+
revisable_columns = [
|
5
|
+
["revisable_original_id", "integer"],
|
6
|
+
["revisable_branched_from_id", "integer"],
|
7
|
+
["revisable_number", "integer", 0],
|
8
|
+
["revisable_name", "string"],
|
9
|
+
["revisable_type", "string"],
|
10
|
+
["revisable_current_at", "datetime"],
|
11
|
+
["revisable_revised_at", "datetime"],
|
12
|
+
["revisable_deleted_at", "datetime"],
|
13
|
+
["revisable_is_current", "boolean", 1]
|
14
|
+
]
|
15
|
+
|
16
|
+
m.migration_template 'migration.rb', 'db/migrate',
|
17
|
+
:migration_file_name => "make_#{class_name.underscore.pluralize}_revisable",
|
18
|
+
:assigns => { :cols => revisable_columns }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<% table_name = class_name.underscore.pluralize -%>
|
2
|
+
class Make<%= class_name.underscore.pluralize.camelize %>Revisable < ActiveRecord::Migration
|
3
|
+
def self.up
|
4
|
+
<% cols.each do |column_name,column_type,default| -%>
|
5
|
+
add_column :<%= table_name %>, :<%= column_name %>, :<%= column_type %><%= ", :default => #{default}" unless default.blank? %>
|
6
|
+
<% end -%>
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down
|
10
|
+
<% cols.each do |column_name,_| -%>
|
11
|
+
remove_column :<%= table_name %>, :<%= column_name %>
|
12
|
+
<% end -%>
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'activesupport' unless defined? ActiveSupport
|
5
|
+
require 'activerecord' unless defined? ActiveRecord
|
6
|
+
|
7
|
+
require 'acts_as_revisable/version.rb'
|
8
|
+
require 'acts_as_revisable/base'
|
9
|
+
|
10
|
+
ActiveRecord::Base.send(:include, WithoutScope::ActsAsRevisable)
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module WithoutScope
|
2
|
+
module ActsAsRevisable
|
3
|
+
# This module is mixed into the revision and revisable classes.
|
4
|
+
#
|
5
|
+
# ==== Callbacks
|
6
|
+
#
|
7
|
+
# * +before_branch+ is called on the +Revisable+ or +Revision+ that is
|
8
|
+
# being branched
|
9
|
+
# * +after_branch+ is called on the +Revisable+ or +Revision+ that is
|
10
|
+
# being branched
|
11
|
+
module Common
|
12
|
+
def self.included(base) #:nodoc:
|
13
|
+
base.send(:extend, ClassMethods)
|
14
|
+
|
15
|
+
base.class_inheritable_hash :revisable_after_callback_blocks
|
16
|
+
base.revisable_after_callback_blocks = {}
|
17
|
+
|
18
|
+
base.class_inheritable_hash :revisable_current_states
|
19
|
+
base.revisable_current_states = {}
|
20
|
+
|
21
|
+
base.instance_eval do
|
22
|
+
define_callbacks :before_branch, :after_branch
|
23
|
+
has_many :branches, (revisable_options.revision_association_options || {}).merge({:class_name => base.name, :foreign_key => :revisable_branched_from_id})
|
24
|
+
|
25
|
+
belongs_to :branch_source, :class_name => base.name, :foreign_key => :revisable_branched_from_id
|
26
|
+
after_save :execute_blocks_after_save
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Executes the blocks stored in an accessor after a save.
|
31
|
+
def execute_blocks_after_save #:nodoc:
|
32
|
+
return unless revisable_after_callback_blocks[:save]
|
33
|
+
revisable_after_callback_blocks[:save].each do |block|
|
34
|
+
block.call
|
35
|
+
end
|
36
|
+
revisable_after_callback_blocks.delete(:save)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Stores a block for later execution after a given callback.
|
40
|
+
# The parameter +key+ is the callback the block should be
|
41
|
+
# executed after.
|
42
|
+
def execute_after(key, &block) #:nodoc:
|
43
|
+
return unless block_given?
|
44
|
+
revisable_after_callback_blocks[key] ||= []
|
45
|
+
revisable_after_callback_blocks[key] << block
|
46
|
+
end
|
47
|
+
|
48
|
+
# Branch the +Revisable+ or +Revision+ and return the new
|
49
|
+
# +revisable+ instance. The instance has not been saved yet.
|
50
|
+
#
|
51
|
+
# ==== Callbacks
|
52
|
+
# * +before_branch+ is called on the +Revisable+ or +Revision+ that is
|
53
|
+
# being branched
|
54
|
+
# * +after_branch+ is called on the +Revisable+ or +Revision+ that is
|
55
|
+
# being branched
|
56
|
+
# * +after_branch_created+ is called on the newly created
|
57
|
+
# +Revisable+ instance.
|
58
|
+
def branch(*args, &block)
|
59
|
+
is_branching!
|
60
|
+
|
61
|
+
unless run_callbacks(:before_branch) { |r, o| r == false}
|
62
|
+
raise ActiveRecord::RecordNotSaved
|
63
|
+
end
|
64
|
+
|
65
|
+
options = args.extract_options!
|
66
|
+
options[:revisable_branched_from_id] = self.id
|
67
|
+
self.class.column_names.each do |col|
|
68
|
+
next unless self.class.revisable_should_clone_column? col
|
69
|
+
options[col.to_sym] = self[col] unless options.has_key?(col.to_sym)
|
70
|
+
end
|
71
|
+
|
72
|
+
br = self.class.revisable_class.new(options)
|
73
|
+
br.is_branching!
|
74
|
+
|
75
|
+
br.execute_after(:save) do
|
76
|
+
begin
|
77
|
+
run_callbacks(:after_branch)
|
78
|
+
br.run_callbacks(:after_branch_created)
|
79
|
+
ensure
|
80
|
+
br.is_branching!(false)
|
81
|
+
is_branching!(false)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
block.call(br) if block_given?
|
86
|
+
|
87
|
+
br
|
88
|
+
end
|
89
|
+
|
90
|
+
# Same as #branch except it calls #save! on the new +Revisable+ instance.
|
91
|
+
def branch!(*args)
|
92
|
+
branch(*args) do |br|
|
93
|
+
br.save!
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Globally sets the reverting state of this record.
|
98
|
+
def is_branching!(value=true) #:nodoc:
|
99
|
+
set_revisable_state(:branching, value)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns true if the _record_ (not just this instance
|
103
|
+
# of the record) is currently being branched.
|
104
|
+
def is_branching?
|
105
|
+
get_revisable_state(:branching)
|
106
|
+
end
|
107
|
+
|
108
|
+
# When called on a +Revision+ it returns the original id. When
|
109
|
+
# called on a +Revisable+ it returns the id.
|
110
|
+
def original_id
|
111
|
+
self[:revisable_original_id] || self[:id]
|
112
|
+
end
|
113
|
+
|
114
|
+
# Globally sets the state for a given record. This is keyed
|
115
|
+
# on the primary_key of a saved record or the object_id
|
116
|
+
# on a new instance.
|
117
|
+
def set_revisable_state(type, value) #:nodoc:
|
118
|
+
key = self.read_attribute(self.class.primary_key)
|
119
|
+
key = object_id if key.nil?
|
120
|
+
revisable_current_states[type] ||= {}
|
121
|
+
revisable_current_states[type][key] = value
|
122
|
+
revisable_current_states[type].delete(key) unless value
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns the state of the given record.
|
126
|
+
def get_revisable_state(type) #:nodoc:
|
127
|
+
key = self.read_attribute(self.class.primary_key)
|
128
|
+
revisable_current_states[type] ||= {}
|
129
|
+
revisable_current_states[type][key] || revisable_current_states[type][object_id] || false
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns true if the instance is the first revision.
|
133
|
+
def first_revision?
|
134
|
+
self.revision_number == 1
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns true if the instance is the most recent revision.
|
138
|
+
def latest_revision?
|
139
|
+
self.revision_number == self.current_revision.revision_number
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns true if the instance is the current record and not a revision.
|
143
|
+
def current_revision?
|
144
|
+
self.is_a? self.class.revisable_class
|
145
|
+
end
|
146
|
+
|
147
|
+
# Accessor for revisable_number just to make external API more pleasant.
|
148
|
+
def revision_number
|
149
|
+
self[:revisable_number] ||= 0
|
150
|
+
end
|
151
|
+
|
152
|
+
def revision_number=(value)
|
153
|
+
self[:revisable_number] = value
|
154
|
+
end
|
155
|
+
|
156
|
+
def diffs(what)
|
157
|
+
what = current_revision.find_revision(what)
|
158
|
+
returning({}) do |changes|
|
159
|
+
self.class.revisable_class.revisable_watch_columns.each do |c|
|
160
|
+
changes[c] = [self[c], what[c]] unless self[c] == what[c]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def deleted?
|
166
|
+
self.revisable_deleted_at.present?
|
167
|
+
end
|
168
|
+
|
169
|
+
module ClassMethods
|
170
|
+
# Returns true if the revision should clone the given column.
|
171
|
+
def revisable_should_clone_column?(col) #:nodoc:
|
172
|
+
return false if (REVISABLE_SYSTEM_COLUMNS + REVISABLE_UNREVISABLE_COLUMNS).member? col
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
# acts_as_revisable's override for instantiate so we can
|
177
|
+
# return the appropriate type of model based on whether
|
178
|
+
# or not the record is the current record.
|
179
|
+
def instantiate(record) #:nodoc:
|
180
|
+
is_current = columns_hash["revisable_is_current"].type_cast(
|
181
|
+
record["revisable_is_current"])
|
182
|
+
|
183
|
+
if (is_current && self == self.revisable_class) || (!is_current && self == self.revision_class)
|
184
|
+
return super(record)
|
185
|
+
end
|
186
|
+
|
187
|
+
object = if is_current
|
188
|
+
self.revisable_class
|
189
|
+
else
|
190
|
+
self.revision_class
|
191
|
+
end.allocate
|
192
|
+
|
193
|
+
object.instance_variable_set("@attributes", record)
|
194
|
+
object.instance_variable_set("@attributes_cache", Hash.new)
|
195
|
+
|
196
|
+
if object.respond_to_without_attributes?(:after_find)
|
197
|
+
object.send(:callback, :after_find)
|
198
|
+
end
|
199
|
+
|
200
|
+
if object.respond_to_without_attributes?(:after_initialize)
|
201
|
+
object.send(:callback, :after_initialize)
|
202
|
+
end
|
203
|
+
|
204
|
+
object
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|