versions 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.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