versioned_record 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 949068fec057f339188cdb54a297c3237d806a3c
4
- data.tar.gz: 4408334975f62ef080055cc2f1351a4ef7730595
3
+ metadata.gz: 8d961aed7862c149a656c89584e5e39a36eab865
4
+ data.tar.gz: efac3b9ce83e333511c5dba682d0ca4b238384b3
5
5
  SHA512:
6
- metadata.gz: d8b8c37d7f54b9a978b8166a66908426ea6060f8ce5f799dd1948f318226cc78c349cb4aadce06c7708341a41acc08532eba13114405ebff36b85fe9ff7f060f
7
- data.tar.gz: ce25740a5627cffd3dcdec8cad7c76518819eaf7b279ba2b6009e1258a8227a5aa1fdf06ec7a6ce58ae4b5dec4e5609680d46f14bec23449f2be2dc02838bb2b
6
+ metadata.gz: 496b5f7cc0c2d15098bf17b5316253f128e1a7442bb1143c7f6bfdf781586f1b5abbbe1f42b827660d3b79424a4f483089bde85114bfe50de3387f39310ed56a
7
+ data.tar.gz: 3ff2e0d96885ea548736e846dd77e8385c9448850affd79d8967905ff227933b7400e53d5e85b83a3bc1ffd44bc5597af6f0d4f339b7f1df740e768a4aebfd0c
data/README.md CHANGED
@@ -70,7 +70,32 @@ Also, the `is_current_version` flag is unset for the old version and set for the
70
70
  product.is_current_version? => false
71
71
  product_v2.is_current_version? => true
72
72
 
73
- ## TODO: Associations
73
+ ## Associations
74
+
75
+ ### `belongs_to` A versioned model, but references the latest version (general case)
76
+
77
+ A simple `belongs_to` will work
78
+
79
+ ### `belongs_to` A versioned model but references a specific version
80
+
81
+ `belongs_to` must specify the `foreign_key` and primary key settings
82
+
83
+ class Contract < ActiveRecord::Base
84
+ include VersionedRecord
85
+ has_many :apprentices, :foreign_key => [:contract_id, :contract_version], primary_key: [:id, :version ]
86
+ end
87
+
88
+ class Apprentice < ActiveRecord::Base
89
+ belongs_to :contract, :foreign_key => [:contract_id, :contract_version], primary_key: [ :id, version ]
90
+ end
91
+
92
+ ### `belongs_to` a non-versioned model
93
+
94
+ Normal behaviour
95
+
96
+ ### `belongs_to` when this model is version
97
+
98
+ As per above cases (versioning here has no effect)
74
99
 
75
100
 
76
101
  ## Database Support
@@ -79,7 +104,9 @@ Right now, only PostgreSQL has been tested. MySQL may or may not work but if you
79
104
 
80
105
  ## Limitations
81
106
 
82
- Does not currently work with ActiveRecord 4.1+
107
+ * Does not currently work with ActiveRecord 4.1+
108
+ * Calling reload on a model will load the latest _version_ of that record, not the specific one. (This is because id will return just the id and not the id/version composite.)
109
+
83
110
 
84
111
  ## Author
85
112
 
@@ -7,102 +7,113 @@ require 'versioned_record/attribute_builder'
7
7
  require 'versioned_record/class_methods'
8
8
  require 'versioned_record/connection_adapters/postgresql'
9
9
  require 'versioned_record/version'
10
+ require 'versioned_record/composite_predicates'
11
+
12
+ ActiveRecord::Associations::AssociationScope.include(VersionedRecord::CompositePredicates)
10
13
 
11
14
  module VersionedRecord
12
15
  def self.included(model_class)
13
- model_class.extend ClassMethods
14
16
  model_class.primary_keys = :id, :version
15
17
  model_class.after_save :ensure_version_deprecation!, on: :create
18
+ model_class.send :alias_method, :id_with_version, :id
19
+ model_class.extend ClassMethods
20
+ model_class.include InstanceMethods
16
21
  end
17
22
 
18
- # Create a new version of the existing record
19
- # A new version can only be created once for a given record and subsequent
20
- # versions must be created by calling create_version! on the latest version
21
- #
22
- # Attributes that are not specified here will be copied to the new version from
23
- # the previous version
24
- #
25
- # This method will still fire ActiveRecord callbacks for save/create etc as per
26
- # normal record creation
27
- #
28
- # @example
29
- #
30
- # person_v1 = Person.create(name: 'Dan')
31
- # person_v2 = person_v1.create_version!(name: 'Daniel')
32
- #
33
- def create_version!(new_attrs = {})
34
- create_operation do
35
- self.class.create!(new_version_attrs(new_attrs))
23
+ module InstanceMethods
24
+ def id
25
+ id_with_version[0]
36
26
  end
37
- end
38
27
 
39
- # Same as #create_version! but will not raise if the record is invalid
40
- # @see VersionedRecord#create_version!
41
- #
42
- def create_version(new_attrs = {})
43
- create_operation do
44
- self.class.create(new_version_attrs(new_attrs))
28
+ # Create a new version of the existing record
29
+ # A new version can only be created once for a given record and subsequent
30
+ # versions must be created by calling create_version! on the latest version
31
+ #
32
+ # Attributes that are not specified here will be copied to the new version from
33
+ # the previous version
34
+ #
35
+ # This method will still fire ActiveRecord callbacks for save/create etc as per
36
+ # normal record creation
37
+ #
38
+ # @example
39
+ #
40
+ # person_v1 = Person.create(name: 'Dan')
41
+ # person_v2 = person_v1.create_version!(name: 'Daniel')
42
+ #
43
+ def create_version!(new_attrs = {})
44
+ create_operation do
45
+ self.class.create!(new_version_attrs(new_attrs))
46
+ end
45
47
  end
46
- end
47
48
 
48
- # Build (but do not save) a new version of the record
49
- # This allows you to use the object in forms etc
50
- # After the record is saved, all previous versions will be deprecated
51
- # and this record will be marked as current
52
- #
53
- # @example
54
- #
55
- # new_version = first_version.build_version
56
- # new_version.save
57
- #
58
- def build_version(new_attrs = {})
59
- new_version = self.class.new(new_version_attrs(new_attrs)).tap do |built|
60
- built.deprecate_old_versions_after_create!
49
+ # Same as #create_version! but will not raise if the record is invalid
50
+ # @see VersionedRecord#create_version!
51
+ #
52
+ def create_version(new_attrs = {})
53
+ create_operation do
54
+ self.class.create(new_version_attrs(new_attrs))
55
+ end
61
56
  end
62
- end
63
-
64
- # Retrieve all versions of this record
65
- # Can be chained with other scopes
66
- #
67
- # @example Versions ordered by version number
68
- #
69
- # person.versions.order(:version)
70
- #
71
- def versions
72
- self.class.where(id: self.id)
73
- end
74
57
 
75
- # Ensure that old versions are deprecated when we save
76
- # (only applies on create)
77
- def deprecate_old_versions_after_create!
78
- @deprecate_old_versions_after_create = true
79
- end
58
+ # Build (but do not save) a new version of the record
59
+ # This allows you to use the object in forms etc
60
+ # After the record is saved, all previous versions will be deprecated
61
+ # and this record will be marked as current
62
+ #
63
+ # @example
64
+ #
65
+ # new_version = first_version.build_version
66
+ # new_version.save
67
+ #
68
+ def build_version(new_attrs = {})
69
+ new_version = self.class.new(new_version_attrs(new_attrs)).tap do |built|
70
+ built.deprecate_old_versions_after_create!
71
+ end
72
+ end
80
73
 
81
- private
82
- def new_version_attrs(new_attrs)
83
- attr_builder = AttributeBuilder.new(self)
84
- attr_builder.attributes(new_attrs)
74
+ # Retrieve all versions of this record
75
+ # Can be chained with other scopes
76
+ #
77
+ # @example Versions ordered by version number
78
+ #
79
+ # person.versions.order(:version)
80
+ #
81
+ def versions
82
+ self.class.where(id: self.id)
85
83
  end
86
84
 
87
- def deprecate_old_versions(current_version)
88
- versions.exclude(current_version).update_all(is_current_version: false)
85
+ # Ensure that old versions are deprecated when we save
86
+ # (only applies on create)
87
+ def deprecate_old_versions_after_create!
88
+ @deprecate_old_versions_after_create = true
89
89
  end
90
90
 
91
- def create_operation
92
- self.class.transaction do
93
- yield.tap do |created|
94
- deprecate_old_versions(created) if created.persisted?
91
+ private
92
+ def new_version_attrs(new_attrs)
93
+ attr_builder = AttributeBuilder.new(self)
94
+ attr_builder.attributes(new_attrs)
95
+ end
96
+
97
+ def deprecate_old_versions(current_version)
98
+ versions.exclude(current_version).update_all(is_current_version: false)
99
+ end
100
+
101
+ def create_operation
102
+ self.class.transaction do
103
+ yield.tap do |created|
104
+ deprecate_old_versions(created) if created.persisted?
105
+ end
95
106
  end
96
107
  end
97
- end
98
108
 
99
- def deprecate_old_versions_after_create?
100
- @deprecate_old_versions_after_create
101
- end
109
+ def deprecate_old_versions_after_create?
110
+ @deprecate_old_versions_after_create
111
+ end
102
112
 
103
- def ensure_version_deprecation!
104
- if deprecate_old_versions_after_create?
105
- deprecate_old_versions(self)
113
+ def ensure_version_deprecation!
114
+ if deprecate_old_versions_after_create?
115
+ deprecate_old_versions(self)
116
+ end
106
117
  end
107
- end
118
+ end
108
119
  end
@@ -14,10 +14,10 @@ module VersionedRecord
14
14
  #
15
15
  # @param [Hash] new_attrs are the attributes we are changing in this version
16
16
  def attributes(new_attrs)
17
- @new_attrs = new_attrs.stringify_keys
18
- @record.attributes.merge(@new_attrs.merge({
17
+ @new_attrs = new_attrs.symbolize_keys
18
+ attrs = @record.attributes.symbolize_keys.merge(@new_attrs.merge({
19
19
  is_current_version: true,
20
- id: @record.id,
20
+ id: @record.id_with_version,
21
21
  version: @record.version + 1
22
22
  }))
23
23
  end
@@ -0,0 +1,40 @@
1
+ module VersionedRecord
2
+ module CompositePredicates
3
+
4
+ # Lets say location belongs to a company and company is versioned
5
+ #
6
+ # class Company < ActiveRecord::Base
7
+ # include VersionedRecord
8
+ # has_many :locations
9
+ # end
10
+ #
11
+ # class Location < ActiveRecord::Base
12
+ # belongs_to :company
13
+ # end
14
+ #
15
+ # When we want to load the company from the location we do:
16
+ #
17
+ # location.company
18
+ #
19
+ # This will do:
20
+ #
21
+ # Company.where(id: location.company.id, is_current_version: true)
22
+ #
23
+ # @param [Arel::Table] association_table the associated_table (in the example, company)
24
+ # @param [Array,String] association_key the value of the pkey of associated_table (in the example, company)
25
+ # @param [Arel::Table] table (in the example, location)
26
+ # @param [Array,String] key reference to the association
27
+ #
28
+ def cpk_join_predicate(association_table, association_key, table, key)
29
+ fields = Array(key).map { |key| table[key] }
30
+ association_fields = Array(association_key).map { |key| association_table[key] }
31
+
32
+ if fields.size == 1
33
+ eq_predicates = [ association_fields[0].eq(fields[0]), association_table[:is_current_version].eq(true) ]
34
+ cpk_and_predicate(eq_predicates)
35
+ else
36
+ super
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,3 +1,3 @@
1
1
  module VersionedRecord
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -5,6 +5,8 @@ Coveralls.wear!
5
5
  require 'versioned_record'
6
6
  require 'database_cleaner'
7
7
 
8
+ require 'byebug'
9
+
8
10
  Dir[("./spec/support/**/*.rb")].each {|f| require f}
9
11
 
10
12
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
@@ -10,7 +10,7 @@ describe VersionedProduct do
10
10
 
11
11
  it 'unsets current version on first version' do
12
12
  perform
13
- expect(first_version.reload).to_not be_is_current_version
13
+ expect(VersionedProduct.find(first_version.id, 0)).to_not be_is_current_version
14
14
  end
15
15
 
16
16
  describe 'the new version' do
@@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "database_cleaner"
31
31
  spec.add_development_dependency "yard"
32
32
  spec.add_development_dependency "coveralls"
33
+ spec.add_development_dependency "byebug"
33
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: versioned_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Draper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-28 00:00:00.000000000 Z
11
+ date: 2014-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: byebug
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  description: Version ActiveRecord models using composite primary keys
140
154
  email:
141
155
  - daniel@codehire.com
@@ -155,6 +169,7 @@ files:
155
169
  - lib/versioned_record.rb
156
170
  - lib/versioned_record/attribute_builder.rb
157
171
  - lib/versioned_record/class_methods.rb
172
+ - lib/versioned_record/composite_predicates.rb
158
173
  - lib/versioned_record/connection_adapters/postgresql.rb
159
174
  - lib/versioned_record/version.rb
160
175
  - spec/spec_helper.rb