versions 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,21 +1,5 @@
1
- ## MAC OS
2
1
  .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
2
  coverage
18
3
  rdoc
19
4
  pkg
20
-
21
- ## PROJECT::SPECIFIC
5
+ *.gem
data/History.txt ADDED
@@ -0,0 +1,12 @@
1
+ == 0.2.0
2
+
3
+ == 0.1.0 2010-02-14
4
+
5
+ * 2 major enhancements
6
+ * Multi working
7
+ * tested with properties (Property gem)
8
+
9
+ == 0.0.1 2010-02-13
10
+
11
+ * 1 major enhancement
12
+ * initial release, Auto working
data/README.rdoc CHANGED
@@ -6,19 +6,40 @@ website: http://zenadmin.org/650
6
6
 
7
7
  license: MIT
8
8
 
9
- == Auto
9
+ == Auto (status: beta)
10
10
 
11
11
  Duplicate on save if should_clone? returns true.
12
12
 
13
- == Multi
13
+ == Multi (status: beta)
14
14
 
15
15
  Hide many versions behind a single current one.
16
16
 
17
- == Transparent
17
+ == Transparent (status: alpha)
18
18
 
19
- Hide versions from outside world.
19
+ Hide versions from outside world (simulate read/writes on the node but
20
+ the actions are done on the version). Uses method_missing to forward
21
+ method calls.
20
22
 
21
- == Property
23
+ === Properties integration (status: beta)
22
24
 
23
- Define properties on model, store them in versions.
25
+ You can get the same functionality as 'Transparent' by using the Property gem and
26
+ storing properties in the version:
27
+
28
+ class Contact < ActiveRecord::Base
29
+ include Versions::Multi
30
+ has_multiple :versions
31
+
32
+ include Property
33
+ store_properties_in :version
34
+
35
+ property do |p|
36
+ p.string 'first_name', 'name'
37
+ end
38
+ end
39
+
40
+
41
+ == SharedAttachment (status: alpha)
42
+
43
+ Enable file attachments linked to versions. The attachments are shared between versions
44
+ and deleted when no more versions are using them.
24
45
 
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ begin
15
15
  require 'rcov/rcovtask'
16
16
  Rcov::RcovTask.new do |test|
17
17
  test.libs << 'test' << 'lib'
18
- test.pattern = 'test/**/auto_test.rb'
18
+ test.pattern = 'test/**/**_test.rb'
19
19
  test.verbose = true
20
20
  test.rcov_opts = ['-T', '--exclude-only', '"test\/,^\/"']
21
21
  end
@@ -38,11 +38,11 @@ begin
38
38
  gemspec.email = "gaspard@teti.ch"
39
39
  gemspec.homepage = "http://zenadmin.org/650"
40
40
  gemspec.authors = ["Gaspard Bucher"]
41
- gemspec.add_development_dependency "shoulda", ">= 0"
42
41
 
43
42
  gemspec.add_development_dependency('shoulda')
43
+ gemspec.add_development_dependency('property', '>= 0.8.1')
44
+
44
45
  gemspec.add_dependency('activerecord')
45
- gemspec.add_dependency('property', '>= 0.8.0')
46
46
  end
47
47
  Jeweler::GemcutterTasks.new
48
48
  rescue LoadError
data/lib/versions/auto.rb CHANGED
@@ -5,7 +5,10 @@ module Versions
5
5
  attr_reader :previous_id
6
6
 
7
7
  def self.included(base)
8
+ raise TypeError.new("Missing 'number' field in table #{base.table_name}.") unless base.column_names.include?('number')
8
9
  base.before_save :prepare_save_or_clone
10
+ base.after_save :clear_number_counter
11
+ base.attr_protected :number
9
12
  end
10
13
 
11
14
  def should_clone?
@@ -22,8 +25,13 @@ module Versions
22
25
  end
23
26
 
24
27
  def prepare_save_or_clone
25
- if !new_record? && should_clone?
28
+ if new_record?
29
+ self[:number] = 1
30
+ elsif should_clone?
26
31
  @previous_id = self[:id]
32
+ @previous_number ||= self[:number]
33
+ self[:number] = @previous_number + 1
34
+
27
35
  self[:id] = nil
28
36
  self[:created_at] = nil
29
37
  self[:updated_at] = nil
@@ -34,5 +42,10 @@ module Versions
34
42
  end
35
43
  true
36
44
  end
45
+
46
+ def clear_number_counter
47
+ @previous_number = nil
48
+ true
49
+ end
37
50
  end # Auto
38
51
  end # Versions
@@ -0,0 +1,63 @@
1
+ module Versions
2
+ # If you include this module in your model (which should also include Versions::Multi), deleting
3
+ # the last version will destroy the model.
4
+ module Destroy
5
+
6
+ # This module should be included in the model that serves as version.
7
+ module Version
8
+ def self.included(base)
9
+
10
+ base.class_eval do
11
+ attr_accessor :__destroy
12
+ belongs_to :node
13
+ before_create :setup_version_on_create
14
+ attr_protected :number, :user_id
15
+
16
+ alias_method_chain :save, :destroy
17
+ end
18
+ end
19
+
20
+ def save_with_destroy(*args)
21
+ if @__destroy
22
+ node = self.node
23
+ if destroy
24
+ # reset @version
25
+ node.send(:version_destroyed)
26
+ true
27
+ end
28
+ else
29
+ save_without_destroy(*args)
30
+ end
31
+ end
32
+
33
+ private
34
+ def setup_version_on_create
35
+ raise "You should define 'setup_version_on_create' method in '#{self.class}' class."
36
+ end
37
+ end
38
+
39
+ def self.included(base)
40
+ base.alias_method_chain :save, :destroy
41
+ end
42
+
43
+ def save_with_destroy(*args)
44
+ version = self.version
45
+ # TODO: we could use 'version.mark_for_destruction' instead of __destroy...
46
+ if version.__destroy && versions.count == 1
47
+ destroy # will destroy last version
48
+ else
49
+ save_without_destroy(*args)
50
+ end
51
+ end
52
+
53
+ private
54
+ # This is called after a version is destroyed
55
+ def version_destroyed
56
+ # remove from versions list
57
+ if versions.loaded?
58
+ node.versions -= [@version]
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -1,142 +1,124 @@
1
- module Zena
2
- module Use
3
- # This module hides 'has_many' versions as if there was only a 'belongs_to' version,
4
- # automatically registering the latest version's id.
5
- module MultiVersion
6
- # This module should be included in the model that serves as version.
7
- module Version
8
- def self.included(base)
9
-
10
- base.class_eval do
11
- attr_accessor :__destroy
12
- belongs_to :node
13
- before_create :setup_version_on_create
14
- attr_protected :number, :user_id
15
-
16
- alias_method_chain :node, :secure
17
- alias_method_chain :save, :destroy
18
- end
19
- end
1
+ module Versions
2
+ # This module hides 'has_many' versions as if there was only a 'belongs_to' version,
3
+ # automatically registering the latest version's id.
4
+ module Multi
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
20
8
 
21
- def node_with_secure
22
- @node ||= begin
23
- if n = node_without_secure
24
- visitor.visit(n)
25
- n.version = self
26
- end
27
- n
28
- end
29
- end
9
+ module ClassMethods
30
10
 
31
- def save_with_destroy(*args)
32
- if @__destroy
33
- node = self.node
34
- if destroy
35
- # reset @version
36
- node.send(:version_destroyed)
37
- true
38
- end
39
- else
40
- save_without_destroy(*args)
41
- end
42
- end
11
+ def has_multiple(versions, options = {})
12
+ name = versions.to_s.singularize
13
+ klass = (options[:class_name] || name.capitalize).constantize
14
+ owner_name = options[:as] || 'owner'
43
15
 
44
- private
45
- def setup_version_on_create
46
- raise "You should define 'setup_version_on_create' method in '#{self.class}' class."
47
- end
48
- end
16
+ raise TypeError.new("Missing 'number' field in table #{klass.table_name}.") unless klass.column_names.include?('number')
17
+ raise TypeError.new("Missing '#{owner_name}_id' in table #{klass.table_name}.") unless klass.column_names.include?("#{owner_name}_id")
49
18
 
50
- def self.included(base)
51
- base.has_many :versions, :order=>"number DESC", :dependent => :destroy #, :inverse_of => :node
52
- base.validate :validate_version
53
- base.before_update :save_version_before_update
54
- base.after_create :save_version_after_create
55
- #base.accepts_nested_attributes_for :version
19
+ has_many versions, :order => 'number DESC', :dependent => :destroy
20
+ validate :"validate_#{name}"
21
+ after_create :"save_#{name}_after_create"
22
+ before_update :"save_#{name}_before_update"
56
23
 
57
- base.alias_method_chain :save, :destroy
24
+ include module_for_multiple(name, klass, owner_name)
25
+ klass.belongs_to owner_name, :class_name => self.to_s
58
26
  end
59
27
 
60
- def save_with_destroy(*args)
61
- version = self.version
62
- # TODO: we could use 'version.mark_for_destruction' instead of __destroy...
63
- if version.__destroy && versions.count == 1
64
- self.destroy # will destroy last version
65
- else
66
- self.save_without_destroy(*args)
28
+ protected
29
+ def module_for_multiple(name, klass, owner_name)
30
+
31
+ # Eval is ugly, but it's the fastest solution I know of
32
+ line = __LINE__
33
+ definitions = <<-EOF
34
+ def #{name} # def version
35
+ @#{name} ||= begin # @version ||= begin
36
+ if v_id = #{name}_id # if v_id = version_id
37
+ version = ::#{klass}.find(v_id) # version = ::Version.find(v_id)
38
+ else # else
39
+ version = ::#{klass}.new # version = ::Version.new
40
+ end # end
41
+ version.#{owner_name} = self # version.owner = self
42
+ version # version
43
+ end # end
44
+ end # end
45
+
46
+ def #{name}=(v) # def version=(v)
47
+ @#{name} = v # @version = v
48
+ end # end
49
+
50
+ def #{name}_attributes=(attributes) # def version_attributes=(attributes)
51
+ #{name}.attributes = attributes # version.attributes = attributes
52
+ end # end
53
+
54
+ private
55
+ def validate_#{name} # def validate_version
56
+ unless #{name}.valid? # unless version.valid?
57
+ merge_multi_errors('#{name}', @#{name}) # merge_multi_errors('version', version)
58
+ end # end
59
+ end # end
60
+
61
+ def save_#{name}_before_update # def save_version_before_update
62
+ return true if !@#{name}.changed? # return true if !@version.changed?
63
+ if !@#{name}.save(false) # if !@version.save_with_validation(false)
64
+ merge_multi_errors('#{name}', @#{name}) # merge_multi_errors('version', @version)
65
+ false # false
66
+ else # else
67
+ set_current_#{name}_before_update # set_current_version_before_update
68
+ true # true
69
+ end # end
70
+ end # end
71
+ #
72
+ def save_#{name}_after_create # def save_version_after_create
73
+ @#{name}.#{owner_name}_id = self[:id] # version.owner_id = self[:id]
74
+ if !@#{name}.save(false) # if !@version.save_with_validation(false)
75
+ merge_multi_errors('#{name}', @#{name}) # merge_multi_errors('version', @version)
76
+ raise ActiveRecord::RecordInvalid.new(self) # raise ActiveRecord::RecordInvalid.new(self)
77
+ else # else
78
+ set_current_#{name}_after_create # set_current_version_after_create
79
+ end # end
80
+ true # true
81
+ end # end
82
+
83
+
84
+ # This method is triggered when the version is saved, but before the
85
+ # master record is updated. This method is usually overwritten
86
+ # in the class.
87
+ def set_current_#{name}_before_update # def set_current_version_before_update
88
+ self[:#{name}_id] = @#{name}.id # self[:version_id] = @version.id
89
+ end # end
90
+
91
+ # This method is triggered when the version is saved, after the
92
+ # master record has been created. This method is usually overwriten
93
+ # in the class.
94
+ def set_current_#{name}_after_create # def set_current_version_after_create
95
+ # raw SQL to skip callbacks and validtions #
96
+ conn = self.class.connection # conn = self.class.connection
97
+
98
+ # conn.execute("UPDATE pages SET \#{conn.quote_column_name("version_id")} = \#{conn.quote(@version.id)} WHERE id = \#{conn.quote(self.id)}")
99
+ conn.execute(
100
+ "UPDATE #{table_name} " +
101
+ "SET \#{conn.quote_column_name("#{name}_id")} = \#{conn.quote(@#{name}.id)} " +
102
+ "WHERE id = \#{conn.quote(self.id)}"
103
+ )
104
+ self[:#{name}_id] = @#{name}.id # self[:version_id] = @version.id
105
+ changed_attributes.clear # changed_attributes.clear
106
+ end # end
107
+ EOF
108
+
109
+ methods_module = Module.new
110
+ methods_module.class_eval(definitions, __FILE__, line + 1)
111
+ methods_module
112
+ end # module_for_multiple
113
+ end # ClassMethods
114
+
115
+ private
116
+
117
+ def merge_multi_errors(name, model)
118
+ model.errors.each_error do |attribute, message|
119
+ attribute = "#{name}_#{attribute}"
120
+ errors.add(attribute, message) unless errors[attribute] # FIXME: rails 3: if errors[attribute].empty?
67
121
  end
68
122
  end
69
-
70
- def version_attributes=(attributes)
71
- version.attributes = attributes
72
- end
73
-
74
- # The logic to get the 'current' version should be
75
- # rewritten in class including MultiVersion.
76
- def version
77
- raise "You should define 'version' method in '#{self.class}' class."
78
- end
79
-
80
- def version=(v)
81
- @version = v
82
- end
83
-
84
- private
85
-
86
- def validate_version
87
- # We force the existence of at least one version with this code
88
- unless version.valid?
89
- merge_version_errors
90
- end
91
- end
92
-
93
- def save_version_before_update
94
- if !@version.save #_with_validation(false)
95
- merge_version_errors
96
- else
97
- current_version_before_update
98
- end
99
- end
100
-
101
- def save_version_after_create
102
- version.node_id = self[:id]
103
- if !@version.save #_with_validation(false)
104
- merge_version_errors
105
- rollback!
106
- else
107
- current_version_after_create
108
- end
109
- true
110
- end
111
-
112
- # This method is triggered when the version is saved, but before the
113
- # master record is updated.
114
- # The role of this method is typically to do things like:
115
- # self[:version_id] = version.id
116
- def current_version_before_update
117
- end
118
-
119
- # This method is triggered when the version is saved, after the
120
- # master record has been created.
121
- # The role of this method is typically to do things like:
122
- # update_attribute(:version_id, version.id)
123
- def current_version_after_create
124
- end
125
-
126
- # This is called after a version is destroyed
127
- def version_destroyed
128
- # remove from versions list
129
- if versions.loaded?
130
- node.versions -= [@version]
131
- end
132
- end
133
-
134
- def merge_version_errors
135
- @version.errors.each_error do |attribute, message|
136
- attribute = "version_#{attribute}"
137
- errors.add(attribute, message) unless errors[attribute] # FIXME: rails 3: if errors[attribute].empty?
138
- end
139
- end
140
- end
141
- end
142
- end
123
+ end # Multi
124
+ end # Versions
@@ -0,0 +1,3 @@
1
+ module Versions
2
+ VERSION = '0.1.0'
3
+ end
data/lib/versions.rb CHANGED
@@ -1,5 +1,5 @@
1
+ require 'active_record'
1
2
  require 'versions/shared_attachment'
2
3
  require 'versions/auto'
3
4
  require 'versions/multi'
4
- require 'versions/property'
5
5
  require 'versions/transparent'
data/test/fixtures.rb CHANGED
@@ -1,3 +1,16 @@
1
+ {:default=>[:"models.multi_test/simple_version.should not contain letter x", :"messages.should not contain letter x", "should not contain letter x"], :value=>"Fox", :scope=>[:activerecord, :errors], :model=>"Multitest::simpleversion", :attribute=>"Title"}
2
+
3
+ module I18n
4
+ # I hate I18n in rails (soooo many bad surprises)
5
+ def self.translate(key, options)
6
+ if options[:default].first.to_s =~ /\A.*\.(.*)\Z/
7
+ $1
8
+ else
9
+ options[:default].last
10
+ end
11
+ end
12
+ end
13
+
1
14
  begin
2
15
  class VersionsMigration < ActiveRecord::Migration
3
16
  def self.down
@@ -7,6 +20,7 @@ begin
7
20
  def self.up
8
21
  create_table 'pages' do |t|
9
22
  t.integer 'version_id'
23
+ t.integer 'foo_id'
10
24
  t.string 'name'
11
25
  t.timestamps
12
26
  end
@@ -16,6 +30,8 @@ begin
16
30
  t.text 'text'
17
31
  t.string 'properties'
18
32
  t.integer 'attachment_id'
33
+ t.integer 'number'
34
+ t.integer 'owner_id'
19
35
  t.timestamps
20
36
  end
21
37
 
@@ -3,8 +3,8 @@ require 'helper'
3
3
  class AutoTest < Test::Unit::TestCase
4
4
 
5
5
  class BadVersion < ActiveRecord::Base
6
- include Versions::Auto
7
6
  set_table_name :versions
7
+ include Versions::Auto
8
8
  end
9
9
 
10
10
  class Version < ActiveRecord::Base
@@ -39,6 +39,10 @@ class AutoTest < Test::Unit::TestCase
39
39
  @version = Version.create('title' => 'Socrate')
40
40
  end
41
41
 
42
+ should 'start number at 1' do
43
+ assert_equal 1, subject.number
44
+ end
45
+
42
46
  context 'returning false' do
43
47
  should 'update record if should_clone is false' do
44
48
  assert_difference('Version.count', 0) do
@@ -56,6 +60,10 @@ class AutoTest < Test::Unit::TestCase
56
60
  subject.update_attributes('title' => 'Aristotle')
57
61
  assert !subject.cloned?
58
62
  end
63
+
64
+ should 'not increase version number' do
65
+ assert_equal 1, subject.number
66
+ end
59
67
  end
60
68
 
61
69
  context 'returning true' do
@@ -79,6 +87,14 @@ class AutoTest < Test::Unit::TestCase
79
87
  subject.update_attributes('title' => 'Aristotle')
80
88
  assert subject.cloned?
81
89
  end
90
+
91
+ should 'increase number on each clone' do
92
+ subject.update_attributes('title' => 'Aristotle')
93
+ assert_equal 2, subject.number
94
+
95
+ subject.update_attributes('title' => 'Aristotle')
96
+ assert_equal 3, subject.number
97
+ end
82
98
  end
83
99
  end
84
100
  end
@@ -1,7 +1,140 @@
1
1
  require 'helper'
2
2
 
3
- class MultiVersionTest < Test::Unit::TestCase
4
- def test_truth
5
- assert true
3
+ class MultiTest < Test::Unit::TestCase
4
+ class SimpleVersion < ActiveRecord::Base
5
+ set_table_name :versions
6
+ validate :title_does_not_contain_letter_x
7
+ before_save :fail_if_title_contains_y
8
+
9
+ def title_does_not_contain_letter_x
10
+ errors.add('title', 'should not contain letter x') if self[:title].to_s =~ /x/
11
+ end
12
+
13
+ def fail_if_title_contains_y
14
+ if self[:title].to_s =~ /y/
15
+ errors.add('title', 'should not contain letter y')
16
+ false
17
+ else
18
+ true
19
+ end
20
+ end
21
+ end
22
+ class SimplePage < ActiveRecord::Base
23
+ include Versions::Multi
24
+
25
+ set_table_name :pages
26
+ has_multiple :foos, :class_name => 'MultiTest::SimpleVersion'
27
+ end
28
+
29
+ class Version < ActiveRecord::Base
30
+ set_table_name :versions
31
+ include Versions::Auto
32
+ def should_clone?
33
+ changed?
34
+ end
35
+ end
36
+ class Page < ActiveRecord::Base
37
+ set_table_name :pages
38
+ include Versions::Multi
39
+ has_multiple :versions, :class_name => 'MultiTest::Version'
40
+ end
41
+
42
+ context 'A class with multiple foos' do
43
+
44
+ should 'accept foo nested attributes' do
45
+ assert_nothing_raised { SimplePage.create('name' => 'one', 'foo_attributes' => {'title' => 'First'}) }
46
+ end
47
+
48
+ should 'create a foo instance of the given type' do
49
+ page = SimplePage.create
50
+ assert page.valid?
51
+ assert_kind_of MultiTest::SimpleVersion, page.foo
52
+ end
53
+
54
+ should 'set foo_id after_create' do
55
+ page = SimplePage.create
56
+ foo_id = page.foo_id
57
+ assert foo_id
58
+ page = SimplePage.find(page)
59
+ assert_equal foo_id, page.foo_id
60
+ end
61
+
62
+ should 'replace current instance on new foos' do
63
+ page = SimplePage.create
64
+ first_foo = page.foo.id
65
+ page.foo = SimpleVersion.new('title' => 'hello')
66
+ assert page.save
67
+ page = SimplePage.find(page.id)
68
+ assert_not_equal first_foo, page.foo.id
69
+ end
70
+
71
+ should 'merge foo errors in model on create' do
72
+ page = SimplePage.create('foo_attributes' => {'title' => 'Fox'})
73
+ assert !page.valid?
74
+ assert_equal 'should not contain letter x', page.errors['foo_title']
75
+ end
76
+
77
+ should 'merge foo errors in model on update' do
78
+ page = SimplePage.create('foo_attributes' => {'title' => 'phone'})
79
+ assert page.valid?
80
+ assert !page.update_attributes('foo_attributes' => {'title' => 'fax'})
81
+ assert_equal 'should not contain letter x', page.errors['foo_title']
82
+ end
83
+
84
+ should 'rollback if foo save fails on create' do
85
+ assert_difference('MultiTest::SimpleVersion.count', 0) do
86
+ assert_difference('MultiTest::SimplePage.count', 0) do
87
+ assert_raise(ActiveRecord::RecordInvalid) do
88
+ begin
89
+ page = SimplePage.create('foo_attributes' => {'title' => 'Fly'})
90
+ rescue ActiveRecord::RecordInvalid => err
91
+ assert_equal 'Validation failed: Foo title should not contain letter y', err.message
92
+ raise
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ should 'abort if foo save fails on update' do
100
+ page = SimplePage.create('foo_attributes' => {'title' => 'mosquito'})
101
+ assert page.valid?
102
+ assert !page.update_attributes('foo_attributes' => {'title' => 'fly'})
103
+ assert_equal 'should not contain letter y', page.errors['foo_title']
104
+ end
105
+ end
106
+
107
+ context 'A class with multiple auto versions' do
108
+ should 'create new versions on update' do
109
+ page = Page.create
110
+ assert_difference('Version.count', 1) do
111
+ assert page.update_attributes('version_attributes' => {'title' => 'newTitle'})
112
+ end
113
+ end
114
+
115
+ should 'mark new version as not dirty after create' do
116
+ page = Page.create
117
+ assert !page.version.changed?
118
+ end
119
+
120
+ should 'mark new version as not dirty after update' do
121
+ page = Page.create
122
+ assert page.update_attributes('version_attributes' => {'title' => 'Yodle'})
123
+ assert !page.version.changed?
124
+ end
125
+
126
+ should 'find latest version' do
127
+ page = Page.create
128
+ v_id = page.version.id
129
+ assert page.update_attributes('version_attributes' => {'title' => 'newTitle'})
130
+ assert_not_equal v_id, page.version.id
131
+ end
132
+
133
+ should 'not create new versions on update if content did not change' do
134
+ page = Page.create('version_attributes' => {'title' => 'One'})
135
+ assert_difference('Version.count', 0) do
136
+ assert page.update_attributes('version_attributes' => {'title' => 'One'})
137
+ end
138
+ end
6
139
  end
7
- end
140
+ end
@@ -0,0 +1,66 @@
1
+ require 'helper'
2
+ require 'property'
3
+
4
+ class PropertyTest < Test::Unit::TestCase
5
+ class Version < ActiveRecord::Base
6
+ set_table_name :versions
7
+ include Versions::Auto
8
+
9
+ def should_clone?
10
+ if changed?
11
+ true
12
+ else
13
+ false
14
+ end
15
+ end
16
+ end
17
+
18
+ class Page < ActiveRecord::Base
19
+ set_table_name :pages
20
+ include Versions::Multi
21
+ has_multiple :versions, :class_name => 'PropertyTest::Version'
22
+
23
+ include Property
24
+ store_properties_in :version
25
+
26
+ property do |p|
27
+ p.text 'history'
28
+ p.string 'author', :default => 'John Malkovitch'
29
+ end
30
+ end
31
+
32
+ context 'Working with properties stored in version' do
33
+
34
+ should 'create an initial version' do
35
+ page = nil
36
+ assert_difference('PropertyTest::Version.count', 1) do
37
+ page = Page.create('history' => 'His Story')
38
+ end
39
+ assert_equal 'His Story', page.history
40
+ assert_equal 1, page.version.number
41
+ end
42
+
43
+ should 'create new versions on property update' do
44
+ page = Page.create('history' => 'His Story')
45
+ assert_difference('PropertyTest::Version.count', 1) do
46
+ assert page.update_attributes('history' => 'Her Story')
47
+ end
48
+ assert_equal 'Her Story', page.history
49
+ assert_equal 2, page.version.number
50
+ end
51
+
52
+ should 'mark as dirty on property update' do
53
+ page = Page.create('history' => 'His Story')
54
+ page.prop['history'] = 'Her Story'
55
+ assert page.changed?
56
+ end
57
+
58
+ should 'not create new versions on property update with same values' do
59
+ page = Page.create('history' => 'His Story')
60
+ assert_difference('PropertyTest::Version.count', 0) do
61
+ assert page.update_attributes('history' => 'His Story')
62
+ end
63
+ assert_equal 1, page.version.number
64
+ end
65
+ end
66
+ end
data/versions.gemspec ADDED
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{versions}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Gaspard Bucher"]
12
+ s.date = %q{2010-02-14}
13
+ s.description = %q{A list of libraries to work with ActiveRecord model versioning: Auto (duplicate on save), Multi (hide many versions behind a single one), Transparent (hide versions from outside world), Property (define properties on model, store them in versions)}
14
+ s.email = %q{gaspard@teti.ch}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "History.txt",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "lib/versions.rb",
27
+ "lib/versions/auto.rb",
28
+ "lib/versions/destroy.rb",
29
+ "lib/versions/multi.rb",
30
+ "lib/versions/shared_attachment.rb",
31
+ "lib/versions/shared_attachment/attachment.rb",
32
+ "lib/versions/shared_attachment/owner.rb",
33
+ "lib/versions/transparent.rb",
34
+ "lib/versions/version.rb",
35
+ "test/fixtures.rb",
36
+ "test/helper.rb",
37
+ "test/unit/attachment_test.rb",
38
+ "test/unit/auto_test.rb",
39
+ "test/unit/multi_test.rb",
40
+ "test/unit/property_test.rb",
41
+ "test/unit/transparent_test.rb",
42
+ "versions.gemspec"
43
+ ]
44
+ s.homepage = %q{http://zenadmin.org/650}
45
+ s.rdoc_options = ["--charset=UTF-8"]
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.3.5}
48
+ s.summary = %q{A list of libraries to work with ActiveRecord model versioning}
49
+ s.test_files = [
50
+ "test/fixtures.rb",
51
+ "test/helper.rb",
52
+ "test/unit/attachment_test.rb",
53
+ "test/unit/auto_test.rb",
54
+ "test/unit/multi_test.rb",
55
+ "test/unit/property_test.rb",
56
+ "test/unit/transparent_test.rb"
57
+ ]
58
+
59
+ if s.respond_to? :specification_version then
60
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
64
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
65
+ s.add_development_dependency(%q<property>, [">= 0.8.1"])
66
+ s.add_runtime_dependency(%q<activerecord>, [">= 0"])
67
+ else
68
+ s.add_dependency(%q<shoulda>, [">= 0"])
69
+ s.add_dependency(%q<property>, [">= 0.8.1"])
70
+ s.add_dependency(%q<activerecord>, [">= 0"])
71
+ end
72
+ else
73
+ s.add_dependency(%q<shoulda>, [">= 0"])
74
+ s.add_dependency(%q<property>, [">= 0.8.1"])
75
+ s.add_dependency(%q<activerecord>, [">= 0"])
76
+ end
77
+ end
78
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: versions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gaspard Bucher
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-13 00:00:00 +01:00
12
+ date: 2010-02-14 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -23,14 +23,14 @@ dependencies:
23
23
  version: "0"
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
- name: shoulda
26
+ name: property
27
27
  type: :development
28
28
  version_requirement:
29
29
  version_requirements: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: "0"
33
+ version: 0.8.1
34
34
  version:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: activerecord
@@ -42,16 +42,6 @@ dependencies:
42
42
  - !ruby/object:Gem::Version
43
43
  version: "0"
44
44
  version:
45
- - !ruby/object:Gem::Dependency
46
- name: property
47
- type: :runtime
48
- version_requirement:
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: 0.8.0
54
- version:
55
45
  description: "A list of libraries to work with ActiveRecord model versioning: Auto (duplicate on save), Multi (hide many versions behind a single one), Transparent (hide versions from outside world), Property (define properties on model, store them in versions)"
56
46
  email: gaspard@teti.ch
57
47
  executables: []
@@ -64,23 +54,27 @@ extra_rdoc_files:
64
54
  files:
65
55
  - .document
66
56
  - .gitignore
57
+ - History.txt
67
58
  - LICENSE
68
59
  - README.rdoc
69
60
  - Rakefile
70
61
  - lib/versions.rb
71
62
  - lib/versions/auto.rb
63
+ - lib/versions/destroy.rb
72
64
  - lib/versions/multi.rb
73
- - lib/versions/property.rb
74
65
  - lib/versions/shared_attachment.rb
75
66
  - lib/versions/shared_attachment/attachment.rb
76
67
  - lib/versions/shared_attachment/owner.rb
77
68
  - lib/versions/transparent.rb
69
+ - lib/versions/version.rb
78
70
  - test/fixtures.rb
79
71
  - test/helper.rb
80
72
  - test/unit/attachment_test.rb
81
73
  - test/unit/auto_test.rb
82
74
  - test/unit/multi_test.rb
75
+ - test/unit/property_test.rb
83
76
  - test/unit/transparent_test.rb
77
+ - versions.gemspec
84
78
  has_rdoc: true
85
79
  homepage: http://zenadmin.org/650
86
80
  licenses: []
@@ -115,4 +109,5 @@ test_files:
115
109
  - test/unit/attachment_test.rb
116
110
  - test/unit/auto_test.rb
117
111
  - test/unit/multi_test.rb
112
+ - test/unit/property_test.rb
118
113
  - test/unit/transparent_test.rb
@@ -1,98 +0,0 @@
1
- module Zena
2
- module Use
3
- # This module lets the user use a node as if it was not versioned and will
4
- # take care of routing the attributes between the node and the version.
5
- module TransparentVersion
6
-
7
- def self.included(base)
8
- base.class_eval do
9
- # When writing attributes, we send everything that we do not know of
10
- # to the version.
11
- def attributes_with_multi_version=(attributes)
12
- columns = self.class.column_names
13
- version_attributes = {}
14
- attributes.keys.each do |k|
15
- if !respond_to?("#{k}=") && !columns.include?(k)
16
- version_attributes[k] = attributes.delete(k)
17
- end
18
- end
19
- version.attributes = version_attributes
20
- self.attributes_without_multi_version = attributes
21
- end
22
-
23
- alias_method_chain :attributes=, :multi_version
24
- end
25
- end
26
-
27
- private
28
- # We need method_missing in forms, normal access in templates should be made
29
- # through 'node.version.xxxx', not 'node.xxxx'.
30
- def method_missing(meth, *args)
31
- method = meth.to_s
32
- if !args.empty? || method[-1..-1] == '?' || self.class.column_names.include?(method)
33
- super
34
- elsif version.respond_to?(meth)
35
- version.send(meth)
36
- else
37
- #version.prop[meth.to_s]
38
- super
39
- end
40
- end
41
-
42
- # Any attribute starting with 'v_' belongs to the 'version' or 'redaction'
43
- # Any attribute starting with 'c_' belongs to the 'version' or 'redaction' content
44
- # FIXME: performance: create methods on the fly so that next calls will not pass through 'method_missing'. #189.
45
- # FIXME: this should not be used anymore. Remove.
46
- # def method_missing(meth, *args)
47
- # if meth.to_s =~ /^(v_|c_|d_)(([\w_\?]+)(=?))$/
48
- # target = $1
49
- # method = $2
50
- # value = $3
51
- # mode = $4
52
- # if mode == '='
53
- # begin
54
- # # set
55
- # unless recipient = redaction
56
- # # remove trailing '='
57
- # redaction_error(meth.to_s[0..-2], "could not be set (no redaction)")
58
- # return
59
- # end
60
- #
61
- # case target
62
- # when 'c_'
63
- # if recipient.content_class && recipient = recipient.redaction_content
64
- # recipient.send(method,*args)
65
- # else
66
- # redaction_error(meth.to_s[0..-2], "cannot be set") # remove trailing '='
67
- # end
68
- # when 'd_'
69
- # recipient.prop[method[0..-2]] = args[0]
70
- # else
71
- # recipient.send(method,*args)
72
- # end
73
- # rescue NoMethodError
74
- # # bad attribute, just ignore
75
- # end
76
- # else
77
- # # read
78
- # recipient = version
79
- # if target == 'd_'
80
- # version.prop[method]
81
- # else
82
- # recipient = recipient.content if target == 'c_'
83
- # return nil unless recipient
84
- # begin
85
- # recipient.send(method,*args)
86
- # rescue NoMethodError
87
- # # bad attribute
88
- # return nil
89
- # end
90
- # end
91
- # end
92
- # else
93
- # super
94
- # end
95
- # end
96
- end
97
- end
98
- end