version_record 0.0.0 → 1.0.0

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