version_record 0.0.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f5a7c0f67974ddb29bdaa5380a828cdd62eb777
4
- data.tar.gz: bfdfa219024a406b781da423528bcd82b1e49d5d
3
+ metadata.gz: c085b6ca09746df9c01cb7895ed0f8cf7010a422
4
+ data.tar.gz: c468a447fe0c4046698c8281e90435b79e389c60
5
5
  SHA512:
6
- metadata.gz: 8419cfdc04e5227f12d0ff10babd47f16ab7bc2ef769b572adac6c0b3d1bbd41010e6e1495fce989d89599fc73ad30b9099b63db8ec92e1bd165987587cc439a
7
- data.tar.gz: 8b1fe56433425a4089708445e3633baf54e9c78aeddc41cbcc86aea8577ea3fd9093e60ffe58dd81a1e005bd827341454e2e1cd5c5a761151b8443c6489f32a4
6
+ metadata.gz: 96e263bf3b4cad106f37cec3ba5167ca533e6783413734097566f1b27df8c466f00ec15b454a7f1773990a12620b98a993f1c6022ebd76686ea607c29b610fa8
7
+ data.tar.gz: 9de8c9c1c270d4999112e8bcf9b129a9bdb9224d3787a8a3c412711fab40522fcf5520dd051b09a521040cc2341113ba92d6ee5e1f1f43daef7bed53e52b0a3c
@@ -0,0 +1,8 @@
1
+ require 'version_record/finder'
2
+ require 'version_record/macros'
3
+ require 'version_record/prerelease'
4
+ require 'version_record/version'
5
+ require 'version_record/core_ext/string'
6
+ require 'version_record/macros/versioned'
7
+ require 'version_record/sorting/simple'
8
+ require 'version_record/type/version'
@@ -0,0 +1,5 @@
1
+ class String
2
+ def to_version
3
+ VersionRecord::Version.new(to_s)
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ module VersionRecord
2
+ class Finder
3
+ def initialize(klass, version_column)
4
+ @klass = klass
5
+ @version_column = version_column
6
+ end
7
+
8
+ def find_latest
9
+ by_version.last
10
+ end
11
+
12
+ private
13
+
14
+ def by_version
15
+ @klass.public_send("by_#{@version_column}")
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module VersionRecord
2
- VERSION = '0.0.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -0,0 +1,17 @@
1
+ module VersionRecord
2
+ module Macros
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def versioned(column_name: :version)
7
+ VersionRecord::Macros::Versioned.def_versioned(self, column_name)
8
+ VersionRecord::Macros::Versioned.def_by_version(self, column_name)
9
+ VersionRecord::Macros::Versioned.def_latest_version(self, column_name)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ ActiveSupport.on_load :active_record do
16
+ include VersionRecord::Macros
17
+ end
@@ -0,0 +1,25 @@
1
+ module VersionRecord
2
+ module Macros
3
+ class Versioned
4
+
5
+ class << self
6
+
7
+ def def_versioned(klass, version_column)
8
+ klass.attribute version_column, :version
9
+ end
10
+
11
+ def def_by_version(klass, version_column)
12
+ klass.define_singleton_method("by_#{version_column}") do |direction = :asc|
13
+ Sorting::Simple.new(klass, version_column).by_version(direction)
14
+ end
15
+ end
16
+
17
+ def def_latest_version(klass, version_column)
18
+ klass.define_singleton_method("latest_#{version_column}") do
19
+ Finder.new(klass, version_column).find_latest
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,69 @@
1
+ module VersionRecord
2
+ class Prerelease
3
+ include Comparable
4
+
5
+ def self.build(string)
6
+ new(".#{string}") if string.present?
7
+ end
8
+
9
+ def initialize(string)
10
+ @string = string
11
+ end
12
+
13
+ def to_s
14
+ @string
15
+ end
16
+
17
+ def name
18
+ @string[1..-1]
19
+ end
20
+
21
+ def tail
22
+ @tail ||= self.class.build(at(1..-1).join('.'))
23
+ end
24
+
25
+ def first_segment
26
+ @first_segment ||= at(0)
27
+ end
28
+
29
+ def <=>(other)
30
+ return unless other.is_a?(Prerelease)
31
+
32
+ if first_segment == other.first_segment
33
+ compare_by_tail(other)
34
+ else
35
+ compare_by_first_segment(other)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def at(index)
42
+ to_a[index]
43
+ end
44
+
45
+ def to_a
46
+ name.split('.')
47
+ end
48
+
49
+ def compare_by_tail(other)
50
+ return 1 if self.tail && other.tail.nil?
51
+ return -1 if self.tail.nil? && other.tail
52
+ self.tail <=> other.tail
53
+ end
54
+
55
+ def compare_by_first_segment(other)
56
+ if int?(first_segment) && int?(other.first_segment)
57
+ first_segment.to_i <=> other.first_segment.to_i
58
+ else
59
+ first_segment <=> other.first_segment
60
+ end
61
+ end
62
+
63
+ def int?(str)
64
+ !!Integer(str)
65
+ rescue ArgumentError, TypeError
66
+ false
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,34 @@
1
+ module VersionRecord
2
+ module Sorting
3
+
4
+ # Handles sorting a collection of AR objects.
5
+ #
6
+ # This implementation uses in-memory sorting.
7
+ #
8
+ class Simple
9
+ def initialize(klass, version_column)
10
+ @klass = klass
11
+ @version_column = version_column
12
+ end
13
+
14
+ def by_version(direction = :asc)
15
+ collection = @klass.all
16
+ direction.to_sym == :asc ? sort_asc(collection) : sort_desc(collection)
17
+ end
18
+
19
+ private
20
+
21
+ def sort_asc(collection)
22
+ collection.sort_by { |object| version(object) }
23
+ end
24
+
25
+ def sort_desc(collection)
26
+ collection.sort { |obj_1, obj_2| version(obj_2) <=> version(obj_1) }
27
+ end
28
+
29
+ def version(object)
30
+ object.public_send(@version_column)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ module VersionRecord
2
+ module Type
3
+ class Version < ActiveRecord::Type::String
4
+ def cast(value)
5
+ value.nil? ? super : value.to_version
6
+ end
7
+
8
+ def serialize(value)
9
+ value.nil? ? super : super(value.to_s)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ ActiveRecord::Type.register(:version, VersionRecord::Type::Version)
@@ -0,0 +1,108 @@
1
+ module VersionRecord
2
+ class Version
3
+ include Comparable
4
+
5
+ attr_accessor :major, :minor, :patch
6
+
7
+ def initialize(version)
8
+ @major, @minor, @patch, @prerelease = Parser.new(version.to_s).parse
9
+ end
10
+
11
+ def to_s
12
+ "#{@major}.#{@minor}.#{@patch}#{@prerelease}"
13
+ end
14
+
15
+ def to_version
16
+ self
17
+ end
18
+
19
+ def bump(segment = :minor)
20
+ send("bump_#{segment}") if [:major, :minor, :patch].include?(segment)
21
+ self
22
+ end
23
+
24
+ def prerelease
25
+ @prerelease.name if @prerelease
26
+ end
27
+
28
+ def <=>(other)
29
+ return unless other.respond_to?(:to_version)
30
+ other = other.to_version
31
+
32
+ if same_segments?(other, :major, :minor, :patch, :prerelease)
33
+ 0
34
+ elsif same_segments?(other, :major, :minor, :patch)
35
+ compare_by(other, :prerelease)
36
+ elsif same_segments?(other, :major, :minor)
37
+ compare_by(other, :patch)
38
+ elsif same_segments?(other, :major)
39
+ compare_by(other, :minor)
40
+ else
41
+ compare_by(other, :major)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def bump_major
48
+ @major += 1
49
+ @minor = @patch = 0
50
+ end
51
+
52
+ def bump_minor
53
+ @minor += 1
54
+ @patch = 0
55
+ end
56
+
57
+ def bump_patch
58
+ @patch += 1
59
+ end
60
+
61
+ # Checks if all the segments in other are equal to the ones passed as a
62
+ # parameter.
63
+ #
64
+ # For example, calling same_segments?(other, :major, :minor) will check:
65
+ #
66
+ # self.major == other.major && self.minor == other.minor
67
+ #
68
+ def same_segments?(other, *segments)
69
+ segments.all? { |segment| send(segment) == other.send(segment) }
70
+ end
71
+
72
+ def compare_by(other, segment)
73
+ if [:major, :minor, :patch].include?(segment)
74
+ send(segment) <=> other.send(segment)
75
+ else
76
+ compare_by_prerelease(other)
77
+ end
78
+ end
79
+
80
+ def compare_by_prerelease(other)
81
+ return 1 if @prerelease.nil? && other.prerelease
82
+ return -1 if @prerelease && other.prerelease.nil?
83
+
84
+ @prerelease <=> Prerelease.build(other.prerelease)
85
+ end
86
+
87
+ class Parser
88
+ VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)(([\.-][0-9A-Za-z-]+)*)$/
89
+
90
+ def initialize(version)
91
+ @version = version
92
+ end
93
+
94
+ def parse
95
+ error! unless match = @version.match(VERSION_PATTERN)
96
+ prerelease = Prerelease.new(match[4]) if match[4].present?
97
+
98
+ [match[1].to_i, match[2].to_i, match[3].to_i, prerelease]
99
+ end
100
+
101
+ private
102
+
103
+ def error!
104
+ raise ArgumentError, "Malformed version number string #{@version}"
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+ require 'rspec/rails'
3
+
4
+ RSpec.configure do |config|
5
+ config.infer_spec_type_from_file_location!
6
+ config.filter_rails_from_backtrace!
7
+ end
@@ -0,0 +1,37 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+ ENV['DATABASE_URL'] = 'sqlite3://localhost/tmp/version_record_test'
3
+
4
+ require 'bundler/setup'
5
+ require 'rails'
6
+ require 'byebug'
7
+ if Rails.version.start_with?('5.0')
8
+ require 'support/apps/rails5_0'
9
+ elsif Rails.version.start_with?('5.1')
10
+ require 'support/apps/rails5_1'
11
+ end
12
+ require 'support/model_spawner'
13
+ require 'version_record'
14
+
15
+ RSpec.configure do |config|
16
+ config.expect_with :rspec do |expectations|
17
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
18
+ end
19
+
20
+ config.mock_with :rspec do |mocks|
21
+ mocks.verify_partial_doubles = true
22
+ end
23
+
24
+ config.shared_context_metadata_behavior = :apply_to_host_groups
25
+
26
+ config.include VersionRecord::Testing::ModelSpawner
27
+
28
+ config.before :each do
29
+ @spawned_models = []
30
+ end
31
+
32
+ config.after :each do
33
+ @spawned_models.each do |model|
34
+ Object.instance_eval { remove_const model } if Object.const_defined?(model)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ require 'rails/all'
2
+
3
+ module Rails50
4
+ class Application < Rails::Application
5
+ # The test environment is used exclusively to run your application's
6
+ # test suite. You never need to work with it otherwise. Remember that
7
+ # your test database is "scratch space" for the test suite and is wiped
8
+ # and recreated between test runs. Don't rely on the data there!
9
+ config.cache_classes = true
10
+
11
+ # Do not eager load code on boot. This avoids loading your whole application
12
+ # just for the purpose of running a single test. If you are using a tool that
13
+ # preloads Rails for running tests, you may have to set it to true.
14
+ config.eager_load = false
15
+
16
+ # Configure public file server for tests with Cache-Control for performance.
17
+ config.public_file_server.enabled = true
18
+ config.public_file_server.headers = {
19
+ 'Cache-Control' => 'public, max-age=3600'
20
+ }
21
+
22
+ # Show full error reports and disable caching.
23
+ config.consider_all_requests_local = true
24
+ config.action_controller.perform_caching = false
25
+
26
+ # Raise exceptions instead of rendering exception templates.
27
+ config.action_dispatch.show_exceptions = false
28
+
29
+ # Disable request forgery protection in test environment.
30
+ config.action_controller.allow_forgery_protection = false
31
+ config.action_mailer.perform_caching = false
32
+
33
+ # Tell Action Mailer not to deliver emails to the real world.
34
+ # The :test delivery method accumulates sent emails in the
35
+ # ActionMailer::Base.deliveries array.
36
+ config.action_mailer.delivery_method = :test
37
+
38
+ # Print deprecation notices to the stderr.
39
+ config.active_support.deprecation = :stderr
40
+
41
+ # Raises error for missing translations
42
+ # config.action_view.raise_on_missing_translations = true
43
+
44
+ config.secret_key_base = '49837489qkuweoiuoqwehisuakshdjksadhaisdy78o34y138974xyqp9rmye8yrpiokeuioqwzyoiuxftoyqiuxrhm3iou1hrzmjk'
45
+ end
46
+ end
47
+
48
+ Rails.application.initialize!
@@ -0,0 +1,50 @@
1
+ require 'rails/all'
2
+
3
+ module Rails51
4
+ class Application < Rails::Application
5
+ # Settings specified here will take precedence over those in config/application.rb.
6
+
7
+ # The test environment is used exclusively to run your application's
8
+ # test suite. You never need to work with it otherwise. Remember that
9
+ # your test database is "scratch space" for the test suite and is wiped
10
+ # and recreated between test runs. Don't rely on the data there!
11
+ config.cache_classes = true
12
+
13
+ # Do not eager load code on boot. This avoids loading your whole application
14
+ # just for the purpose of running a single test. If you are using a tool that
15
+ # preloads Rails for running tests, you may have to set it to true.
16
+ config.eager_load = false
17
+
18
+ # Configure public file server for tests with Cache-Control for performance.
19
+ config.public_file_server.enabled = true
20
+ config.public_file_server.headers = {
21
+ 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}"
22
+ }
23
+
24
+ # Show full error reports and disable caching.
25
+ config.consider_all_requests_local = true
26
+ config.action_controller.perform_caching = false
27
+
28
+ # Raise exceptions instead of rendering exception templates.
29
+ config.action_dispatch.show_exceptions = false
30
+
31
+ # Disable request forgery protection in test environment.
32
+ config.action_controller.allow_forgery_protection = false
33
+ config.action_mailer.perform_caching = false
34
+
35
+ # Tell Action Mailer not to deliver emails to the real world.
36
+ # The :test delivery method accumulates sent emails in the
37
+ # ActionMailer::Base.deliveries array.
38
+ config.action_mailer.delivery_method = :test
39
+
40
+ # Print deprecation notices to the stderr.
41
+ config.active_support.deprecation = :stderr
42
+
43
+ # Raises error for missing translations
44
+ # config.action_view.raise_on_missing_translations = true
45
+
46
+ config.secret_key_base = '49837489qkuweoiuoqwehisuakshdjksadhaisdy78o34y138974xyqp9rmye8yrpiokeuioqwzyoiuxftoyqiuxrhm3iou1hrzmjk'
47
+ end
48
+ end
49
+
50
+ Rails.application.initialize!
@@ -0,0 +1,22 @@
1
+ module VersionRecord
2
+ module Testing
3
+ module ModelSpawner
4
+ def spawn_model(klass, &block)
5
+ Object.instance_eval { remove_const klass } if Object.const_defined?(klass)
6
+ Object.const_set klass, Class.new(ActiveRecord::Base)
7
+ Object.const_get(klass).class_eval(&block) if block_given?
8
+ @spawned_models << klass.to_sym
9
+ end
10
+
11
+ def create_table(table_name)
12
+ ActiveRecord::Migration.suppress_messages do
13
+ ActiveRecord::Schema.define do
14
+ create_table table_name, force: true do |t|
15
+ yield(t)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ describe VersionRecord::Finder do
2
+ describe "#find_latest" do
3
+ subject(:finder) { VersionRecord::Finder.new(Document, :version) }
4
+
5
+ before do
6
+ create_table(:documents) do |t|
7
+ t.string :title
8
+ t.string :version
9
+ end
10
+
11
+ spawn_model(:Document) do
12
+ versioned
13
+ end
14
+ end
15
+
16
+ context 'when data exists' do
17
+ before do
18
+ @document_1 = Document.create!(title: 'Title 1', version: '1.1.0')
19
+ @document_2 = Document.create!(title: 'Title 2', version: '1.0.0')
20
+ @document_3 = Document.create!(title: 'Title 3', version: '1.1.1')
21
+ end
22
+
23
+ it { expect(finder.find_latest).to eq @document_3 }
24
+ end
25
+
26
+ context 'with no data' do
27
+ it { expect(finder.find_latest).to be_nil }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,230 @@
1
+ describe VersionRecord::Macros::Versioned do
2
+
3
+ describe '#versioned' do
4
+ context 'with default options' do
5
+ before do
6
+ create_table(:documents) do |t|
7
+ t.string :title
8
+ t.string :version
9
+ end
10
+
11
+ spawn_model(:Document) do
12
+ versioned
13
+ end
14
+ end
15
+
16
+ context 'reading attribute' do
17
+ context 'when version column has values' do
18
+ let(:document) { Document.create!(title: 'Terms of service', version: '1.0.0') }
19
+ it { expect(document.version).to eq VersionRecord::Version.new('1.0.0') }
20
+ end
21
+
22
+ context 'when version column is nil' do
23
+ let(:document) { Document.create!(title: 'Terms of service', version: nil) }
24
+ it { expect(document.version).to be_nil }
25
+ end
26
+ end
27
+
28
+ context 'setting new values' do
29
+ context 'when version column has values' do
30
+ let(:document) { Document.create!(title: 'Terms of service', version: '1.0.0') }
31
+
32
+ it 'should accept a string' do
33
+ document.version = '1.1.0'
34
+ expect(document.version).to eq VersionRecord::Version.new('1.1.0')
35
+ end
36
+
37
+ it 'should accept a Version object' do
38
+ document.version = VersionRecord::Version.new('1.1.0')
39
+ expect(document.version).to eq VersionRecord::Version.new('1.1.0')
40
+ end
41
+
42
+ it 'should save to db with a string' do
43
+ document.update(version: '1.1.0')
44
+ expect(document.reload.version).to eq VersionRecord::Version.new('1.1.0')
45
+ end
46
+
47
+ it 'should save to db with a Version object' do
48
+ document.update(version: VersionRecord::Version.new('1.1.0'))
49
+ expect(document.reload.version).to eq VersionRecord::Version.new('1.1.0')
50
+ end
51
+
52
+ it 'should raise if malformed version' do
53
+ expect { document.update(version: '1.1.1.') }.to raise_error(ArgumentError)
54
+ expect(document.reload.version).to eq VersionRecord::Version.new('1.0.0')
55
+ end
56
+
57
+ it 'should allow setting the version to nil' do
58
+ document.version = nil
59
+ document.save!
60
+ expect(document.reload.version).to be_nil
61
+ end
62
+ end
63
+
64
+ context 'when version column is nil' do
65
+ let(:document) { Document.create!(title: 'Terms of service', version: nil) }
66
+
67
+ it 'should accept a string' do
68
+ document.version = '1.1.0'
69
+ expect(document.version).to eq VersionRecord::Version.new('1.1.0')
70
+ end
71
+
72
+ it 'should accept a Version object' do
73
+ document.version = VersionRecord::Version.new('1.1.0')
74
+ expect(document.version).to eq VersionRecord::Version.new('1.1.0')
75
+ end
76
+
77
+ it 'should save to db with a string' do
78
+ document.update(version: '1.1.0')
79
+ expect(document.reload.version).to eq VersionRecord::Version.new('1.1.0')
80
+ end
81
+
82
+ it 'should save to db with a Version object' do
83
+ document.update(version: VersionRecord::Version.new('1.1.0'))
84
+ expect(document.reload.version).to eq VersionRecord::Version.new('1.1.0')
85
+ end
86
+
87
+ it 'should raise if malformed version' do
88
+ expect { document.update(version: '1.1.1.') }.to raise_error(ArgumentError)
89
+ expect(document.reload.version).to be_nil
90
+ end
91
+
92
+ it 'should allow setting the version to nil' do
93
+ document.version = nil
94
+ document.save!
95
+ expect(document.reload.version).to be_nil
96
+ end
97
+ end
98
+ end
99
+
100
+ context 'querying' do
101
+ let!(:document_1) { Document.create!(title: 'Terms of service', version: '1.0.0') }
102
+ let!(:document_2) { Document.create!(title: 'Terms of service', version: '1.1.0') }
103
+ let!(:document_3) { Document.create!(title: 'Terms of service', version: '1.1.1') }
104
+ let!(:document_4) { Document.create!(title: 'Terms of service', version: nil) }
105
+
106
+ it { expect(Document.where(version: '1.1.1')).to eq [document_3] }
107
+ it { expect(Document.where(version: VersionRecord::Version.new('1.1.1'))).to eq [document_3] }
108
+ it { expect(Document.where(version: nil)).to eq [document_4] }
109
+ end
110
+ end
111
+
112
+ context 'with custom options' do
113
+ before do
114
+ create_table(:releases) do |t|
115
+ t.string :tag
116
+ t.string :semantic_version
117
+ end
118
+
119
+ spawn_model(:Release) do
120
+ versioned column_name: :semantic_version
121
+ end
122
+ end
123
+
124
+ context 'reading attribute' do
125
+ let(:release) { Release.create!(tag: 'jb23hj412', semantic_version: '1.0.0') }
126
+ it { expect(release.semantic_version).to eq VersionRecord::Version.new('1.0.0') }
127
+ end
128
+
129
+ context 'setting new values' do
130
+ context 'when version column has values' do
131
+ let(:release) { Release.create!(tag: 'jb23hj412', semantic_version: '1.0.0') }
132
+
133
+ it 'should save to db with a Version object' do
134
+ release.update(semantic_version: VersionRecord::Version.new('1.1.0'))
135
+ expect(release.reload.semantic_version).to eq VersionRecord::Version.new('1.1.0')
136
+ end
137
+
138
+ it 'should allow setting the version to nil' do
139
+ release.semantic_version = nil
140
+ release.save!
141
+ expect(release.reload.semantic_version).to be_nil
142
+ end
143
+ end
144
+ end
145
+
146
+ context 'querying' do
147
+ let!(:release_1) { Release.create!(tag: 'jb23hj412', semantic_version: '1.0.0') }
148
+ let!(:release_2) { Release.create!(tag: 'jb23hj412', semantic_version: '1.1.0') }
149
+ let!(:release_3) { Release.create!(tag: 'jb23hj412', semantic_version: '1.1.1') }
150
+ let!(:release_4) { Release.create!(tag: 'jb23hj412', semantic_version: nil) }
151
+
152
+ it { expect(Release.where(semantic_version: '1.1.1')).to eq [release_3] }
153
+ it { expect(Release.where(semantic_version: VersionRecord::Version.new('1.1.1'))).to eq [release_3] }
154
+ it { expect(Release.where(semantic_version: nil)).to eq [release_4] }
155
+ end
156
+ end
157
+ end
158
+
159
+ describe '#by_version' do
160
+ context 'with default options' do
161
+ before do
162
+ create_table(:documents) do |t|
163
+ t.string :title
164
+ t.string :version
165
+ end
166
+
167
+ spawn_model(:Document) do
168
+ versioned
169
+ end
170
+
171
+ @document = Document.create!(title: 'Title 1', version: '1.1.0')
172
+ end
173
+
174
+ it { expect(Document.by_version).to eq [@document] }
175
+ end
176
+
177
+ context 'with custom options' do
178
+ before do
179
+ create_table(:releases) do |t|
180
+ t.string :tag
181
+ t.string :semantic_version
182
+ end
183
+
184
+ spawn_model(:Release) do
185
+ versioned column_name: :semantic_version
186
+ end
187
+
188
+ @release = Release.create!(tag: 'jb23hj412', semantic_version: '1.0.0')
189
+ end
190
+
191
+ it { expect(Release.by_semantic_version).to eq [@release] }
192
+ end
193
+ end
194
+
195
+ describe '#latest_version' do
196
+ context 'with default options' do
197
+ before do
198
+ create_table(:documents) do |t|
199
+ t.string :title
200
+ t.string :version
201
+ end
202
+
203
+ spawn_model(:Document) do
204
+ versioned
205
+ end
206
+
207
+ @document = Document.create!(title: 'Title 1', version: '1.1.0')
208
+ end
209
+
210
+ it { expect(Document.latest_version).to eq @document }
211
+ end
212
+
213
+ context 'with custom options' do
214
+ before do
215
+ create_table(:releases) do |t|
216
+ t.string :tag
217
+ t.string :semantic_version
218
+ end
219
+
220
+ spawn_model(:Release) do
221
+ versioned column_name: :semantic_version
222
+ end
223
+
224
+ @release = Release.create!(tag: 'jb23hj412', semantic_version: '1.0.0')
225
+ end
226
+
227
+ it { expect(Release.latest_semantic_version).to eq @release }
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,49 @@
1
+ describe VersionRecord::Prerelease do
2
+
3
+ subject(:prerelease) { VersionRecord::Prerelease.new('.beta') }
4
+
5
+ describe '.build' do
6
+ it { expect(VersionRecord::Prerelease.build('beta')).to eq VersionRecord::Prerelease.new('.beta') }
7
+ end
8
+
9
+ describe '#to_s' do
10
+ it { expect(prerelease.to_s).to eq '.beta' }
11
+ end
12
+
13
+ describe '#name' do
14
+ it { expect(prerelease.name).to eq 'beta' }
15
+ end
16
+
17
+ describe '#tail' do
18
+ it { expect(VersionRecord::Prerelease.new('.beta.1.alpha').tail).to eq VersionRecord::Prerelease.new('.1.alpha') }
19
+ it { expect(VersionRecord::Prerelease.new('.1.alpha').tail).to eq VersionRecord::Prerelease.new('.alpha') }
20
+ it { expect(VersionRecord::Prerelease.new('.alpha').tail).to be_nil }
21
+ end
22
+
23
+ describe '#first_segment' do
24
+ it { expect(VersionRecord::Prerelease.new('.beta.1.alpha').first_segment).to eq 'beta' }
25
+ it { expect(VersionRecord::Prerelease.new('.1.alpha').first_segment).to eq '1' }
26
+ it { expect(VersionRecord::Prerelease.new('.alpha').first_segment).to eq 'alpha' }
27
+ end
28
+
29
+ describe '#<=>' do
30
+ it { expect(VersionRecord::Prerelease.new('-alpha')).to be < VersionRecord::Prerelease.new('-alpha.1') }
31
+ it { expect(VersionRecord::Prerelease.new('-alpha.1')).to be < VersionRecord::Prerelease.new('-alpha.beta') }
32
+ it { expect(VersionRecord::Prerelease.new('-alpha.beta')).to be < VersionRecord::Prerelease.new('-beta') }
33
+ it { expect(VersionRecord::Prerelease.new('-beta')).to be < VersionRecord::Prerelease.new('-beta.2') }
34
+ it { expect(VersionRecord::Prerelease.new('-beta.2')).to be < VersionRecord::Prerelease.new('-beta.11') }
35
+ it { expect(VersionRecord::Prerelease.new('-beta.11')).to be < VersionRecord::Prerelease.new('-rc.1') }
36
+ it { expect(VersionRecord::Prerelease.new('.beta.9')).to be < VersionRecord::Prerelease.new('.beta.10') }
37
+ it { expect(VersionRecord::Prerelease.new('.beta')).to be < VersionRecord::Prerelease.new('.beta.1') }
38
+ it { expect(VersionRecord::Prerelease.new('.beta')).to eq VersionRecord::Prerelease.new('.beta') }
39
+ it { expect(VersionRecord::Prerelease.new('.beta.1')).to be > VersionRecord::Prerelease.new('.beta') }
40
+ it { expect(VersionRecord::Prerelease.new('.beta.10')).to be > VersionRecord::Prerelease.new('.beta.9') }
41
+ it { expect(VersionRecord::Prerelease.new('-rc.1')).to be > VersionRecord::Prerelease.new('-beta.11') }
42
+ it { expect(VersionRecord::Prerelease.new('-beta.11')).to be > VersionRecord::Prerelease.new('-beta.2') }
43
+ it { expect(VersionRecord::Prerelease.new('-beta.2')).to be > VersionRecord::Prerelease.new('-beta') }
44
+ it { expect(VersionRecord::Prerelease.new('-beta')).to be > VersionRecord::Prerelease.new('-alpha.beta') }
45
+ it { expect(VersionRecord::Prerelease.new('-alpha.beta')).to be > VersionRecord::Prerelease.new('-alpha.1') }
46
+ it { expect(VersionRecord::Prerelease.new('-alpha.1')).to be > VersionRecord::Prerelease.new('-alpha') }
47
+ it { expect(VersionRecord::Prerelease.new('-alpha.1')).not_to eq nil }
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ describe VersionRecord::Sorting::Simple do
2
+ describe "#by_version" do
3
+ subject(:sorting_strategy) { VersionRecord::Sorting::Simple.new(Document, :version) }
4
+
5
+ before do
6
+ create_table(:documents) do |t|
7
+ t.string :title
8
+ t.string :version
9
+ end
10
+
11
+ spawn_model(:Document) do
12
+ versioned
13
+ end
14
+
15
+ @document_1 = Document.create!(title: 'Title 1', version: '1.1.0')
16
+ @document_2 = Document.create!(title: 'Title 2', version: '1.0.0')
17
+ @document_3 = Document.create!(title: 'Title 3', version: '1.1.1')
18
+ end
19
+
20
+ it { expect(sorting_strategy.by_version).to eq [@document_2, @document_1, @document_3] }
21
+ it { expect(sorting_strategy.by_version(:asc)).to eq [@document_2, @document_1, @document_3] }
22
+ it { expect(sorting_strategy.by_version(:desc)).to eq [@document_3, @document_1, @document_2] }
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ describe String do
2
+ describe '#to_version' do
3
+ it { expect('2.2.1'.to_version).to eq VersionRecord::Version.new('2.2.1') }
4
+ it { expect('2.2.1.beta'.to_version).to eq VersionRecord::Version.new('2.2.1.beta') }
5
+ it { expect { 'invalid_version'.to_version }.to raise_error(ArgumentError) }
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ describe VersionRecord::Type::Version do
2
+ subject(:version_type) { VersionRecord::Type::Version.new }
3
+
4
+ it { is_expected.to be_an ActiveRecord::Type::String }
5
+
6
+ describe '#cast' do
7
+ it { expect(version_type.cast('1.1.2')).to eq VersionRecord::Version.new('1.1.2') }
8
+ it { expect { version_type.cast('1.1.2.') }.to raise_error ArgumentError }
9
+ end
10
+
11
+ describe '#serialize' do
12
+ it { expect(version_type.serialize(VersionRecord::Version.new('1.1.2'))).to eq '1.1.2' }
13
+ end
14
+ end
@@ -0,0 +1,157 @@
1
+ describe VersionRecord::Version do
2
+
3
+ subject(:version) { VersionRecord::Version.new('1.1.2') }
4
+
5
+ describe '.new' do
6
+ context '2.3.1' do
7
+ let(:version) { VersionRecord::Version.new('2.3.1') }
8
+
9
+ it { expect(version.major).to eq 2 }
10
+ it { expect(version.minor).to eq 3 }
11
+ it { expect(version.patch).to eq 1 }
12
+ it { expect(version.prerelease).to be_nil }
13
+ end
14
+
15
+ context '1.0.0-alpha' do
16
+ let (:version) { VersionRecord::Version.new('1.0.0-alpha') }
17
+
18
+ it { expect(version.major).to eq 1 }
19
+ it { expect(version.minor).to eq 0 }
20
+ it { expect(version.patch).to eq 0 }
21
+ it { expect(version.prerelease).to eq 'alpha' }
22
+ end
23
+
24
+ context '1.0.0.alpha' do
25
+ let (:version) { VersionRecord::Version.new('1.0.0.alpha') }
26
+
27
+ it { expect(version.major).to eq 1 }
28
+ it { expect(version.minor).to eq 0 }
29
+ it { expect(version.patch).to eq 0 }
30
+ it { expect(version.prerelease).to eq 'alpha' }
31
+ end
32
+
33
+ context '1.0.0-alpha.1' do
34
+ let (:version) { VersionRecord::Version.new('1.0.0-alpha.1') }
35
+
36
+ it { expect(version.major).to eq 1 }
37
+ it { expect(version.minor).to eq 0 }
38
+ it { expect(version.patch).to eq 0 }
39
+ it { expect(version.prerelease).to eq 'alpha.1' }
40
+ end
41
+
42
+ context '1.0.0.alpha.1' do
43
+ let (:version) { VersionRecord::Version.new('1.0.0.alpha.1') }
44
+
45
+ it { expect(version.major).to eq 1 }
46
+ it { expect(version.minor).to eq 0 }
47
+ it { expect(version.patch).to eq 0 }
48
+ it { expect(version.prerelease).to eq 'alpha.1' }
49
+ end
50
+
51
+ context '1.0.0-alpha.beta' do
52
+ let (:version) { VersionRecord::Version.new('1.0.0-alpha.beta') }
53
+
54
+ it { expect(version.major).to eq 1 }
55
+ it { expect(version.minor).to eq 0 }
56
+ it { expect(version.patch).to eq 0 }
57
+ it { expect(version.prerelease).to eq 'alpha.beta' }
58
+ end
59
+
60
+ context '1.0.0.alpha.beta' do
61
+ let (:version) { VersionRecord::Version.new('1.0.0.alpha.beta') }
62
+
63
+ it { expect(version.major).to eq 1 }
64
+ it { expect(version.minor).to eq 0 }
65
+ it { expect(version.patch).to eq 0 }
66
+ it { expect(version.prerelease).to eq 'alpha.beta' }
67
+ end
68
+
69
+ context '1.0.0-beta.11' do
70
+ let (:version) { VersionRecord::Version.new('1.0.0-beta.11') }
71
+
72
+ it { expect(version.major).to eq 1 }
73
+ it { expect(version.minor).to eq 0 }
74
+ it { expect(version.patch).to eq 0 }
75
+ it { expect(version.prerelease).to eq 'beta.11' }
76
+ end
77
+
78
+ context '1.0.0.beta.11' do
79
+ let (:version) { VersionRecord::Version.new('1.0.0.beta.11') }
80
+
81
+ it { expect(version.major).to eq 1 }
82
+ it { expect(version.minor).to eq 0 }
83
+ it { expect(version.patch).to eq 0 }
84
+ it { expect(version.prerelease).to eq 'beta.11' }
85
+ end
86
+
87
+ context 'unsupported format' do
88
+ it { expect { VersionRecord::Version.new('2') }.to raise_error(ArgumentError) }
89
+ it { expect { VersionRecord::Version.new('2.1') }.to raise_error(ArgumentError) }
90
+ it { expect { VersionRecord::Version.new('2..1') }.to raise_error(ArgumentError) }
91
+ it { expect { VersionRecord::Version.new('2.') }.to raise_error(ArgumentError) }
92
+ it { expect { VersionRecord::Version.new('.1') }.to raise_error(ArgumentError) }
93
+ it { expect { VersionRecord::Version.new('fdsdfa') }.to raise_error(ArgumentError) }
94
+ it { expect { VersionRecord::Version.new('2.x.1') }.to raise_error(ArgumentError) }
95
+ it { expect { VersionRecord::Version.new('a.b.c') }.to raise_error(ArgumentError) }
96
+ end
97
+ end
98
+
99
+ describe '#to_s' do
100
+ context 'without prerelease' do
101
+ let(:version) { VersionRecord::Version.new('1.1.0') }
102
+ it { expect(version.to_s).to eq '1.1.0' }
103
+ end
104
+
105
+ context 'with prerelease' do
106
+ let(:version) { VersionRecord::Version.new('1.1.0.rc1') }
107
+ it { expect(version.to_s).to eq '1.1.0.rc1' }
108
+ end
109
+ end
110
+
111
+ describe '#bump' do
112
+ context 'bump major' do
113
+ it { expect(version.bump(:major).to_s).to eq '2.0.0' }
114
+ end
115
+
116
+ context 'bump minor' do
117
+ it { expect(version.bump(:minor).to_s).to eq '1.2.0' }
118
+ it { expect(version.bump.to_s).to eq '1.2.0' }
119
+ end
120
+
121
+ context 'bump patch' do
122
+ it { expect(version.bump(:patch).to_s).to eq '1.1.3' }
123
+ end
124
+ end
125
+
126
+ describe '#<=>' do
127
+ it { expect(VersionRecord::Version.new('1.0.0')).to be < VersionRecord::Version.new('2.0.0') }
128
+ it { expect(VersionRecord::Version.new('2.0.0')).to be < VersionRecord::Version.new('2.1.0') }
129
+ it { expect(VersionRecord::Version.new('2.1.0')).to be < VersionRecord::Version.new('2.1.1') }
130
+ it { expect(VersionRecord::Version.new('2.1.1')).to eq VersionRecord::Version.new('2.1.1') }
131
+ it { expect(VersionRecord::Version.new('2.1.1')).to be > VersionRecord::Version.new('2.1.0') }
132
+ it { expect(VersionRecord::Version.new('2.1.0')).to be > VersionRecord::Version.new('2.0.0') }
133
+ it { expect(VersionRecord::Version.new('2.0.0')).to be > VersionRecord::Version.new('1.0.0') }
134
+ it { expect(VersionRecord::Version.new('3.1.0')).not_to eq nil }
135
+ it { expect(VersionRecord::Version.new('1.0.9')).to be < VersionRecord::Version.new('1.0.10') }
136
+ it { expect(VersionRecord::Version.new('1.9.0')).to be < VersionRecord::Version.new('1.10.0') }
137
+ it { expect(VersionRecord::Version.new('9.0.0')).to be < VersionRecord::Version.new('10.0.0') }
138
+
139
+ context 'with prerelease' do
140
+ it { expect(VersionRecord::Version.new('1.0.0-alpha')).to be < VersionRecord::Version.new('1.0.0-alpha.1') }
141
+ it { expect(VersionRecord::Version.new('1.0.0-alpha.1')).to be < VersionRecord::Version.new('1.0.0-alpha.beta') }
142
+ it { expect(VersionRecord::Version.new('1.0.0-alpha.beta')).to be < VersionRecord::Version.new('1.0.0-beta') }
143
+ it { expect(VersionRecord::Version.new('1.0.0-beta')).to be < VersionRecord::Version.new('1.0.0-beta.2') }
144
+ it { expect(VersionRecord::Version.new('1.0.0-beta.2')).to be < VersionRecord::Version.new('1.0.0-beta.11') }
145
+ it { expect(VersionRecord::Version.new('1.0.0-beta.11')).to be < VersionRecord::Version.new('1.0.0-rc.1') }
146
+ it { expect(VersionRecord::Version.new('1.0.0-rc.1')).to be < VersionRecord::Version.new('1.0.0') }
147
+ it { expect(VersionRecord::Version.new('1.0.0.beta.9')).to be < VersionRecord::Version.new('1.0.0.beta.10') }
148
+ it { expect(VersionRecord::Version.new('1.0.0.beta.1')).to be > VersionRecord::Version.new('1.0.0.beta') }
149
+ it { expect(VersionRecord::Version.new('1.0.0.beta')).to be < VersionRecord::Version.new('1.0.0.beta.1') }
150
+ it { expect(VersionRecord::Version.new('1.0.0.beta')).to eq VersionRecord::Version.new('1.0.0.beta') }
151
+ end
152
+ end
153
+
154
+ describe '#to_version' do
155
+ it { expect(version.to_version).to eq version }
156
+ end
157
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: version_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcelo Casiraghi
@@ -10,6 +10,34 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2017-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +52,76 @@ dependencies:
24
52
  - - "~>"
25
53
  - !ruby/object:Gem::Version
26
54
  version: '1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '9'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: appraisal
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2'
27
125
  description:
28
126
  email: marcelo@cedarcode.com
29
127
  executables: []
@@ -31,7 +129,27 @@ extensions: []
31
129
  extra_rdoc_files: []
32
130
  files:
33
131
  - lib/version_record.rb
132
+ - lib/version_record/core_ext/string.rb
133
+ - lib/version_record/finder.rb
34
134
  - lib/version_record/gem_version.rb
135
+ - lib/version_record/macros.rb
136
+ - lib/version_record/macros/versioned.rb
137
+ - lib/version_record/prerelease.rb
138
+ - lib/version_record/sorting/simple.rb
139
+ - lib/version_record/type/version.rb
140
+ - lib/version_record/version.rb
141
+ - spec/rails_helper.rb
142
+ - spec/spec_helper.rb
143
+ - spec/support/apps/rails5_0.rb
144
+ - spec/support/apps/rails5_1.rb
145
+ - spec/support/model_spawner.rb
146
+ - spec/version_record/finder_spec.rb
147
+ - spec/version_record/macros/versioned_spec.rb
148
+ - spec/version_record/prerelease_spec.rb
149
+ - spec/version_record/sorting/simple_spec.rb
150
+ - spec/version_record/string_spec.rb
151
+ - spec/version_record/type/version_spec.rb
152
+ - spec/version_record/version_spec.rb
35
153
  homepage: https://github.com/cedarcode/version_record
36
154
  licenses:
37
155
  - MIT
@@ -55,5 +173,17 @@ rubyforge_project:
55
173
  rubygems_version: 2.6.10
56
174
  signing_key:
57
175
  specification_version: 4
58
- summary: Tools for managing versioned records in ActiveRecord
59
- test_files: []
176
+ summary: Semantic version management for database records using ActiveRecord
177
+ test_files:
178
+ - spec/rails_helper.rb
179
+ - spec/spec_helper.rb
180
+ - spec/support/apps/rails5_0.rb
181
+ - spec/support/apps/rails5_1.rb
182
+ - spec/support/model_spawner.rb
183
+ - spec/version_record/finder_spec.rb
184
+ - spec/version_record/macros/versioned_spec.rb
185
+ - spec/version_record/prerelease_spec.rb
186
+ - spec/version_record/sorting/simple_spec.rb
187
+ - spec/version_record/string_spec.rb
188
+ - spec/version_record/type/version_spec.rb
189
+ - spec/version_record/version_spec.rb