versioned_record 0.2.0 → 0.2.1

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.
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