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
@@ -0,0 +1,148 @@
|
|
1
|
+
module WithoutScope
|
2
|
+
module ActsAsRevisable
|
3
|
+
# This module is mixed into the revision classes.
|
4
|
+
#
|
5
|
+
# ==== Callbacks
|
6
|
+
#
|
7
|
+
# * +before_restore+ is called on the revision class before it is
|
8
|
+
# restored as the current record.
|
9
|
+
# * +after_restore+ is called on the revision class after it is
|
10
|
+
# restored as the current record.
|
11
|
+
module Revision
|
12
|
+
def self.included(base) #:nodoc:
|
13
|
+
base.send(:extend, ClassMethods)
|
14
|
+
|
15
|
+
class << base
|
16
|
+
attr_accessor :revisable_revisable_class, :revisable_cloned_associations
|
17
|
+
end
|
18
|
+
|
19
|
+
base.instance_eval do
|
20
|
+
set_table_name(revisable_class.table_name)
|
21
|
+
default_scope :conditions => {:revisable_is_current => false}
|
22
|
+
|
23
|
+
define_callbacks :before_restore, :after_restore
|
24
|
+
before_create :revision_setup
|
25
|
+
after_create :grab_my_branches
|
26
|
+
|
27
|
+
named_scope :deleted, :conditions => ["? is not null", :revisable_deleted_at]
|
28
|
+
|
29
|
+
[:current_revision, revisable_association_name.to_sym].each do |a|
|
30
|
+
belongs_to a, :class_name => revisable_class_name, :foreign_key => :revisable_original_id
|
31
|
+
end
|
32
|
+
|
33
|
+
[[:ancestors, "<"], [:descendants, ">"]].each do |a|
|
34
|
+
# Jumping through hoops here to try and make sure the
|
35
|
+
# :finder_sql is cross-database compatible. :finder_sql
|
36
|
+
# in a plugin is evil but, I see no other option.
|
37
|
+
has_many a.first, :class_name => revision_class_name, :finder_sql => "select * from #{quoted_table_name} where #{quote_bound_value(:revisable_original_id)} = \#{revisable_original_id} and #{quote_bound_value(:revisable_number)} #{a.last} \#{revisable_number} and #{quote_bound_value(:revisable_is_current)} = #{quote_value(false)} order by #{quote_bound_value(:revisable_number)} #{(a.last.eql?("<") ? "DESC" : "ASC")}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_revision(*args)
|
43
|
+
current_revision.find_revision(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return the revision prior to this one.
|
47
|
+
def previous_revision
|
48
|
+
self.class.find(:first, :conditions => {:revisable_original_id => revisable_original_id, :revisable_number => revisable_number - 1})
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return the revision after this one.
|
52
|
+
def next_revision
|
53
|
+
self.class.find(:first, :conditions => {:revisable_original_id => revisable_original_id, :revisable_number => revisable_number + 1})
|
54
|
+
end
|
55
|
+
|
56
|
+
# Setter for revisable_name just to make external API more pleasant.
|
57
|
+
def revision_name=(val) #:nodoc:
|
58
|
+
self[:revisable_name] = val
|
59
|
+
end
|
60
|
+
|
61
|
+
# Accessor for revisable_name just to make external API more pleasant.
|
62
|
+
def revision_name #:nodoc:
|
63
|
+
self[:revisable_name]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Sets some initial values for a new revision.
|
67
|
+
def revision_setup #:nodoc:
|
68
|
+
now = Time.current
|
69
|
+
prev = current_revision.revisions.first
|
70
|
+
prev.update_attribute(:revisable_revised_at, now) if prev
|
71
|
+
self[:revisable_current_at] = now + 1.second
|
72
|
+
self[:revisable_is_current] = false
|
73
|
+
self[:revisable_branched_from_id] = current_revision[:revisable_branched_from_id]
|
74
|
+
self[:revisable_type] = current_revision[:type] || current_revision.class.name
|
75
|
+
end
|
76
|
+
|
77
|
+
def grab_my_branches
|
78
|
+
self.class.revisable_class.update_all(["revisable_branched_from_id = ?", self[:id]], ["revisable_branched_from_id = ?", self[:revisable_original_id]])
|
79
|
+
end
|
80
|
+
|
81
|
+
def from_revisable
|
82
|
+
current_revision.for_revision
|
83
|
+
end
|
84
|
+
|
85
|
+
def reverting_from
|
86
|
+
from_revisable[:reverting_from]
|
87
|
+
end
|
88
|
+
|
89
|
+
def reverting_from=(val)
|
90
|
+
from_revisable[:reverting_from] = val
|
91
|
+
end
|
92
|
+
|
93
|
+
def reverting_to
|
94
|
+
from_revisable[:reverting_to]
|
95
|
+
end
|
96
|
+
|
97
|
+
def reverting_to=(val)
|
98
|
+
from_revisable[:reverting_to] = val
|
99
|
+
end
|
100
|
+
|
101
|
+
module ClassMethods
|
102
|
+
# Returns the +revisable_class_name+ as configured in
|
103
|
+
# +acts_as_revisable+.
|
104
|
+
def revisable_class_name #:nodoc:
|
105
|
+
self.revisable_options.revisable_class_name || self.name.gsub(/Revision/, '')
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the actual +Revisable+ class based on the
|
109
|
+
# #revisable_class_name.
|
110
|
+
def revisable_class #:nodoc:
|
111
|
+
self.revisable_revisable_class ||= self.revisable_class_name.constantize
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns the revision_class which in this case is simply +self+.
|
115
|
+
def revision_class #:nodoc:
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
def revision_class_name #:nodoc:
|
120
|
+
self.name
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns the name of the association acts_as_revision
|
124
|
+
# creates.
|
125
|
+
def revisable_association_name #:nodoc:
|
126
|
+
revisable_class_name.underscore
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns an array of the associations that should be cloned.
|
130
|
+
def revision_cloned_associations #:nodoc:
|
131
|
+
clone_associations = self.revisable_options.clone_associations
|
132
|
+
|
133
|
+
self.revisable_cloned_associations ||= if clone_associations.blank?
|
134
|
+
[]
|
135
|
+
elsif clone_associations.eql? :all
|
136
|
+
revisable_class.reflect_on_all_associations.map(&:name)
|
137
|
+
elsif clone_associations.is_a? [].class
|
138
|
+
clone_associations
|
139
|
+
elsif clone_associations[:only]
|
140
|
+
[clone_associations[:only]].flatten
|
141
|
+
elsif clone_associations[:except]
|
142
|
+
revisable_class.reflect_on_all_associations.map(&:name) - [clone_associations[:except]].flatten
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'acts_as_revisable/options'
|
2
|
+
require 'acts_as_revisable/quoted_columns'
|
3
|
+
require 'acts_as_revisable/validations'
|
4
|
+
require 'acts_as_revisable/acts/common'
|
5
|
+
require 'acts_as_revisable/acts/revision'
|
6
|
+
require 'acts_as_revisable/acts/revisable'
|
7
|
+
require 'acts_as_revisable/acts/deletable'
|
8
|
+
|
9
|
+
module WithoutScope
|
10
|
+
# define the columns used internall by AAR
|
11
|
+
REVISABLE_SYSTEM_COLUMNS = %w(revisable_original_id revisable_branched_from_id revisable_number revisable_name revisable_type revisable_current_at revisable_revised_at revisable_deleted_at revisable_is_current)
|
12
|
+
|
13
|
+
# define the ActiveRecord magic columns that should not be monitored
|
14
|
+
REVISABLE_UNREVISABLE_COLUMNS = %w(id type created_at updated_at)
|
15
|
+
|
16
|
+
module ActsAsRevisable
|
17
|
+
def self.included(base)
|
18
|
+
base.send(:extend, ClassMethods)
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
|
23
|
+
# This +acts_as+ extension provides for making a model the
|
24
|
+
# revisable model in an acts_as_revisable pair.
|
25
|
+
def acts_as_revisable(*args, &block)
|
26
|
+
revisable_shared_setup(args, block)
|
27
|
+
self.send(:include, Revisable)
|
28
|
+
self.send(:include, Deletable) if self.revisable_options.on_delete == :revise
|
29
|
+
end
|
30
|
+
|
31
|
+
# This +acts_as+ extension provides for making a model the
|
32
|
+
# revision model in an acts_as_revisable pair.
|
33
|
+
def acts_as_revision(*args, &block)
|
34
|
+
revisable_shared_setup(args, block)
|
35
|
+
self.send(:include, Revision)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
# Performs the setup needed for both kinds of acts_as_revisable
|
40
|
+
# models.
|
41
|
+
def revisable_shared_setup(args, block)
|
42
|
+
class << self
|
43
|
+
attr_accessor :revisable_options
|
44
|
+
end
|
45
|
+
options = args.extract_options!
|
46
|
+
self.revisable_options = Options.new(options, &block)
|
47
|
+
|
48
|
+
self.send(:include, Common)
|
49
|
+
self.send(:extend, Validations) unless self.revisable_options.no_validation_scoping?
|
50
|
+
self.send(:include, WithoutScope::QuotedColumnConditions)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module WithoutScope #:nodoc:
|
2
|
+
module ActsAsRevisable
|
3
|
+
class GemSpecOptions
|
4
|
+
HASH = {
|
5
|
+
:name => "acts_as_revisable",
|
6
|
+
:version => WithoutScope::ActsAsRevisable::VERSION::STRING,
|
7
|
+
:summary => "acts_as_revisable enables revision tracking, querying, reverting and branching of ActiveRecord models. Inspired by acts_as_versioned.",
|
8
|
+
:email => "rich@withoutscope.com",
|
9
|
+
:homepage => "http://github.com/rich/acts_as_revisable",
|
10
|
+
:has_rdoc => true,
|
11
|
+
:authors => ["Rich Cavanaugh", "Stephen Caudill"],
|
12
|
+
:files => %w( LICENSE README.rdoc Rakefile ) + Dir["{spec,lib,generators,rails}/**/*"],
|
13
|
+
:rdoc_options => ["--main", "README.rdoc"],
|
14
|
+
:extra_rdoc_files => ["README.rdoc", "LICENSE"]
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module WithoutScope
|
2
|
+
module ActsAsRevisable
|
3
|
+
# This class provides for a flexible method of setting
|
4
|
+
# options and querying them. This is especially useful
|
5
|
+
# for giving users flexibility when using your plugin.
|
6
|
+
class Options
|
7
|
+
def initialize(*options, &block)
|
8
|
+
@options = options.extract_options!
|
9
|
+
instance_eval(&block) if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(key, *args)
|
13
|
+
return (@options[key.to_s.gsub(/\?$/, '').to_sym].eql?(true)) if key.to_s.match(/\?$/)
|
14
|
+
if args.blank?
|
15
|
+
@options[key.to_sym]
|
16
|
+
else
|
17
|
+
@options[key.to_sym] = args.size == 1 ? args.first : args
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# This module is more about the pretty than anything else. This allows
|
2
|
+
# you to use symbols for column names in a conditions hash.
|
3
|
+
#
|
4
|
+
# User.find(:all, :conditions => ["? = ?", :name, "sam"])
|
5
|
+
#
|
6
|
+
# Would generate:
|
7
|
+
#
|
8
|
+
# select * from users where "users"."name" = 'sam'
|
9
|
+
#
|
10
|
+
# This is consistent with Rails and Ruby where symbols are used to
|
11
|
+
# represent methods. Only a symbol matching a column name will
|
12
|
+
# trigger this beavior.
|
13
|
+
module WithoutScope::QuotedColumnConditions
|
14
|
+
def self.included(base)
|
15
|
+
base.send(:extend, ClassMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def quote_bound_value(value)
|
20
|
+
if value.is_a?(Symbol) && column_names.member?(value.to_s)
|
21
|
+
# code borrowed from sanitize_sql_hash_for_conditions
|
22
|
+
attr = value.to_s
|
23
|
+
table_name = quoted_table_name
|
24
|
+
|
25
|
+
return "#{table_name}.#{connection.quote_column_name(attr)}"
|
26
|
+
end
|
27
|
+
|
28
|
+
super(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'acts_as_revisable'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe WithoutScope::ActsAsRevisable do
|
4
|
+
after(:each) do
|
5
|
+
cleanup_db
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@project = Project.create(:name => "Rich", :notes => "this plugin's author")
|
10
|
+
@project.update_attribute(:name, "one")
|
11
|
+
@project.update_attribute(:name, "two")
|
12
|
+
@project.update_attribute(:name, "three")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have a pretty named association" do
|
16
|
+
lambda { @project.sessions }.should_not raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return all the revisions" do
|
20
|
+
@project.revisions.size.should == 3
|
21
|
+
end
|
22
|
+
end
|
data/spec/branch_spec.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
class Project
|
4
|
+
validates_presence_of :name
|
5
|
+
end
|
6
|
+
|
7
|
+
describe WithoutScope::ActsAsRevisable, "with branching" do
|
8
|
+
after(:each) do
|
9
|
+
cleanup_db
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
@project = Project.create(:name => "Rich", :notes => "a note")
|
14
|
+
@project.update_attribute(:name, "Sam")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should allow for branch creation" do
|
18
|
+
@project.should == @project.branch.branch_source
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should branch without saving" do
|
22
|
+
@project.branch.should be_new_record
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should branch and save" do
|
26
|
+
@project.branch!.should_not be_new_record
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should not raise an error for a valid branch" do
|
30
|
+
lambda { @project.branch!(:name => "A New User") }.should_not raise_error
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should raise an error for invalid records" do
|
34
|
+
lambda { @project.branch!(:name => nil) }.should raise_error
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not save an invalid record" do
|
38
|
+
@branch = @project.branch(:name => nil)
|
39
|
+
@branch.save.should be_false
|
40
|
+
@branch.should be_new_record
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe WithoutScope::ActsAsRevisable::Deletable do
|
4
|
+
after(:each) do
|
5
|
+
cleanup_db
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@person = Person.create(:name => "Rich", :notes => "a note")
|
10
|
+
@person.update_attribute(:name, "Sam")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should store a revision on destroy" do
|
14
|
+
lambda{ @person.destroy }.should change(OldPerson, :count).from(1).to(2)
|
15
|
+
end
|
16
|
+
end
|
data/spec/find_spec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe WithoutScope::ActsAsRevisable do
|
4
|
+
after(:each) do
|
5
|
+
cleanup_db
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "with a single revision" do
|
9
|
+
before(:each) do
|
10
|
+
@project1 = Project.create(:name => "Rich", :notes => "a note")
|
11
|
+
@project1.update_attribute(:name, "Sam")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should just find the current revision by default" do
|
15
|
+
Project.find(:first).name.should == "Sam"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should accept the :with_revisions options" do
|
19
|
+
lambda { Project.find(:all, :with_revisions => true) }.should_not raise_error
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should find current and revisions with the :with_revisions option" do
|
23
|
+
Project.find(:all, :with_revisions => true).size.should == 2
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should find revisions with conditions" do
|
27
|
+
Project.find(:all, :conditions => {:name => "Rich"}, :with_revisions => true).should == [@project1.find_revision(:previous)]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should find last revision" do
|
31
|
+
@project1.find_revision(:last).should == @project1.find_revision(:previous)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe WithoutScope::ActsAsRevisable do
|
4
|
+
after(:each) do
|
5
|
+
cleanup_db
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@project = Project.create(:name => "Rich", :notes => "this plugin's author")
|
10
|
+
@post = Post.create(:name => 'a name')
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "with auto-detected revision class" do
|
14
|
+
it "should find the revision class" do
|
15
|
+
Post.revision_class.should == PostRevision
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should find the revisable class" do
|
19
|
+
PostRevision.revisable_class.should == Post
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should use the revision class" do
|
23
|
+
@post.update_attribute(:name, 'another name')
|
24
|
+
@post.revisions(true).first.class.should == PostRevision
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "with auto-generated revision class" do
|
29
|
+
it "should have a revision class" do
|
30
|
+
Foo.revision_class.should == FooRevision
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "without revisions" do
|
35
|
+
it "should have a revision_number of zero" do
|
36
|
+
@project.revision_number.should be_zero
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be the current revision" do
|
40
|
+
@project.revisable_is_current.should be_true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should respond to current_revision? positively" do
|
44
|
+
@project.current_revision?.should be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not have any revisions in the generic association" do
|
48
|
+
@project.revisions.should be_empty
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not have any revisions in the pretty named association" do
|
52
|
+
@project.sessions.should be_empty
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "with revisions" do
|
57
|
+
before(:each) do
|
58
|
+
@project.update_attribute(:name, "Stephen")
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should have a revision_number of one" do
|
62
|
+
@project.revision_number.should == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should have a single revision in the generic association" do
|
66
|
+
@project.revisions.size.should == 1
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should have a single revision in the pretty named association" do
|
70
|
+
@project.sessions.size.should == 1
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should have a single revision with a revision_number of zero" do
|
74
|
+
@project.revisions.collect{ |rev| rev.revision_number }.should == [0]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should return an instance of the revision class" do
|
78
|
+
@project.revisions.first.should be_an_instance_of(Session)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should have the original revision's data" do
|
82
|
+
@project.revisions.first.name.should == "Rich"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "with multiple revisions" do
|
87
|
+
before(:each) do
|
88
|
+
@project.update_attribute(:name, "Stephen")
|
89
|
+
@project.update_attribute(:name, "Michael")
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should have a revision_number of two" do
|
93
|
+
@project.revision_number.should == 2
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should have revisions with revision_number values of zero and one" do
|
97
|
+
@project.revisions.collect{ |rev| rev.revision_number }.should == [1,0]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
describe "with excluded columns modified" do
|
103
|
+
before(:each) do
|
104
|
+
@project.update_attribute(:unimportant, "a new value")
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should maintain the revision_number at zero" do
|
108
|
+
@project.revision_number.should be_zero
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should not have any revisions" do
|
112
|
+
@project.revisions.should be_empty
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|