sequel_bitemporal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +2 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/lib/sequel/plugins/bitemporal.rb +121 -0
- data/lib/sequel_bitemporal.rb +1 -0
- data/sequel_bitemporal.gemspec +22 -0
- data/spec/bitemporal_spec.rb +183 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/bitemporal_matchers.rb +41 -0
- metadata +102 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
sequel_bitemporal
|
2
|
+
=================
|
3
|
+
|
4
|
+
Bitemporal versioning for sequel.
|
5
|
+
|
6
|
+
Dependencies
|
7
|
+
------------
|
8
|
+
|
9
|
+
* Ruby >= 1.9.2
|
10
|
+
* gem "sequel"
|
11
|
+
|
12
|
+
Usage
|
13
|
+
-----
|
14
|
+
|
15
|
+
* Declare bitemporality inside your model:
|
16
|
+
|
17
|
+
class HotelPrice < Sequel::Model
|
18
|
+
plugin :bitemporal
|
19
|
+
end
|
20
|
+
|
21
|
+
Build Status
|
22
|
+
------------
|
23
|
+
|
24
|
+
[](http://travis-ci.org/TalentBox/sequel_bitemporal)
|
25
|
+
|
26
|
+
License
|
27
|
+
-------
|
28
|
+
|
29
|
+
sequel_bitemporal is Copyright © 2011 TalentBox SA. It is free software, and may be redistributed under the terms specified in the LICENSE file.
|
data/Rakefile
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
module Bitemporal
|
4
|
+
def self.configure(master, opts = {})
|
5
|
+
version = opts[:version_class]
|
6
|
+
raise Error, "please specify version class to use for bitemporal plugin" unless version
|
7
|
+
required = [:master_id, :valid_from, :valid_to, :created_at, :expired_at]
|
8
|
+
missing = required - version.columns
|
9
|
+
raise Error, "bitemporal plugin requires the following missing column#{"s" if missing.size>1} on version class: #{missing.join(", ")}" unless missing.empty?
|
10
|
+
master.one_to_many :versions, class: version, key: :master_id
|
11
|
+
master.one_to_one :current_version, class: version, key: :master_id, conditions: ["created_at<=:now AND (expired_at IS NULL OR expired_at>:now) AND valid_from<=:now AND valid_to>:now", now: Time.now]
|
12
|
+
version.many_to_one :master, class: master, key: :master_id
|
13
|
+
version.class_eval do
|
14
|
+
def current?(now = Time.now)
|
15
|
+
!new? &&
|
16
|
+
created_at.to_time<=now &&
|
17
|
+
(expired_at.nil? || expired_at.to_time>now) &&
|
18
|
+
valid_from.to_time<=now &&
|
19
|
+
valid_to.to_time>now
|
20
|
+
end
|
21
|
+
end
|
22
|
+
master.instance_eval do
|
23
|
+
@version_class = version
|
24
|
+
end
|
25
|
+
end
|
26
|
+
module ClassMethods
|
27
|
+
attr_reader :version_class
|
28
|
+
end
|
29
|
+
module DatasetMethods
|
30
|
+
end
|
31
|
+
module InstanceMethods
|
32
|
+
attr_reader :pending_version
|
33
|
+
|
34
|
+
def validate
|
35
|
+
super
|
36
|
+
pending_version.errors.each do |key, key_errors|
|
37
|
+
key_errors.each{|error| errors.add key, error}
|
38
|
+
end if pending_version && !pending_version.valid?
|
39
|
+
end
|
40
|
+
|
41
|
+
def attributes
|
42
|
+
pending_version ? pending_version.values : {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def attributes=(attributes)
|
46
|
+
@pending_version ||= model.version_class.new
|
47
|
+
pending_version.set attributes
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_attributes(attributes={})
|
51
|
+
if attributes.delete(:partial_update) && current_version
|
52
|
+
current_attributes = current_version.values.dup
|
53
|
+
current_attributes.delete :id
|
54
|
+
attributes = current_attributes.merge attributes
|
55
|
+
end
|
56
|
+
self.attributes = attributes
|
57
|
+
save raise_on_failure: false
|
58
|
+
end
|
59
|
+
|
60
|
+
def after_create
|
61
|
+
super
|
62
|
+
if pending_version
|
63
|
+
prepare_pending_version
|
64
|
+
return false unless save_pending_version
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def before_update
|
69
|
+
if pending_version
|
70
|
+
lock!
|
71
|
+
prepare_pending_version
|
72
|
+
expire_previous_versions
|
73
|
+
return false unless save_pending_version
|
74
|
+
end
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def prepare_pending_version
|
81
|
+
point_in_time = Time.now
|
82
|
+
pending_version.created_at = point_in_time
|
83
|
+
pending_version.valid_from = point_in_time if !pending_version.valid_from || pending_version.valid_from.to_time<point_in_time
|
84
|
+
end
|
85
|
+
|
86
|
+
def save_pending_version
|
87
|
+
pending_version.valid_to ||= Time.utc 9999
|
88
|
+
success = add_version pending_version
|
89
|
+
@pending_version = nil if success
|
90
|
+
success
|
91
|
+
end
|
92
|
+
|
93
|
+
def expire_previous_versions
|
94
|
+
expired = versions_dataset.where expired_at: nil
|
95
|
+
expired = expired.exclude "valid_from=valid_to"
|
96
|
+
expired = expired.exclude "valid_to<=?", pending_version.valid_from
|
97
|
+
pending_version.valid_to ||= expired.where("valid_from>?", pending_version.valid_from).select("MIN(valid_from)").first
|
98
|
+
pending_version.valid_to ||= Time.utc 9999
|
99
|
+
expired = expired.exclude "valid_from>=?", pending_version.valid_to
|
100
|
+
expired = expired.all
|
101
|
+
expired.each do |expired_version|
|
102
|
+
if expired_version.valid_from<pending_version.valid_from && expired_version.valid_to>pending_version.valid_from
|
103
|
+
return false unless save_fossil expired_version, created_at: pending_version.created_at, valid_to: pending_version.valid_from
|
104
|
+
elsif expired_version.valid_from<pending_version.valid_to && expired_version.valid_to>pending_version.valid_to
|
105
|
+
return false unless save_fossil expired_version, created_at: pending_version.created_at, valid_from: pending_version.valid_to
|
106
|
+
end
|
107
|
+
end
|
108
|
+
versions_dataset.where(id: expired.collect(&:id)).update expired_at: pending_version.created_at
|
109
|
+
end
|
110
|
+
|
111
|
+
def save_fossil(expired, attributes={})
|
112
|
+
fossil = model.version_class.new
|
113
|
+
expired_attributes = expired.values.dup
|
114
|
+
expired_attributes.delete :id
|
115
|
+
fossil.send :set_values, expired_attributes.merge(attributes)
|
116
|
+
fossil.save validate: false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "sequel/plugins/bitemporal"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "sequel_bitemporal"
|
6
|
+
s.version = "0.1.0"
|
7
|
+
s.authors = ["Joseph HALTER"]
|
8
|
+
s.email = ["joseph.halter@thetalentbox.com"]
|
9
|
+
s.homepage = "https://github.com/TalentBox/sequel_bitemporal"
|
10
|
+
s.summary = "Bitemporal versioning for sequel."
|
11
|
+
s.description = "Bitemporal versioning for sequel, fully tested."
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_development_dependency "sqlite3"
|
19
|
+
s.add_development_dependency "rspec"
|
20
|
+
s.add_development_dependency "timecop"
|
21
|
+
s.add_runtime_dependency "sequel"
|
22
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Sequel::Plugins::Bitemporal" do
|
4
|
+
before :all do
|
5
|
+
DB.create_table! :rooms do
|
6
|
+
primary_key :id
|
7
|
+
end
|
8
|
+
DB.create_table! :room_versions do
|
9
|
+
primary_key :id
|
10
|
+
foreign_key :master_id, :rooms
|
11
|
+
String :name
|
12
|
+
Fixnum :price
|
13
|
+
Date :created_at
|
14
|
+
Date :expired_at
|
15
|
+
Date :valid_from
|
16
|
+
Date :valid_to
|
17
|
+
end
|
18
|
+
@version_class = Class.new Sequel::Model do
|
19
|
+
set_dataset :room_versions
|
20
|
+
def validate
|
21
|
+
super
|
22
|
+
errors.add(:name, "is required") unless name
|
23
|
+
errors.add(:price, "is required") unless price
|
24
|
+
end
|
25
|
+
end
|
26
|
+
closure = @version_class
|
27
|
+
@master_class = Class.new Sequel::Model do
|
28
|
+
set_dataset :rooms
|
29
|
+
plugin :bitemporal, version_class: closure
|
30
|
+
end
|
31
|
+
end
|
32
|
+
before do
|
33
|
+
Timecop.freeze 2009, 11, 28
|
34
|
+
end
|
35
|
+
after do
|
36
|
+
Timecop.return
|
37
|
+
@master_class.truncate
|
38
|
+
@version_class.truncate
|
39
|
+
end
|
40
|
+
it "checks version class is given" do
|
41
|
+
lambda{
|
42
|
+
@version_class.plugin :bitemporal
|
43
|
+
}.should raise_error Sequel::Error, "please specify version class to use for bitemporal plugin"
|
44
|
+
end
|
45
|
+
it "checks required columns are present" do
|
46
|
+
lambda{
|
47
|
+
@version_class.plugin :bitemporal, :version_class => @master_class
|
48
|
+
}.should raise_error Sequel::Error, "bitemporal plugin requires the following missing columns on version class: master_id, valid_from, valid_to, created_at, expired_at"
|
49
|
+
end
|
50
|
+
it "propagates errors from version to master" do
|
51
|
+
master = @master_class.new
|
52
|
+
master.should be_valid
|
53
|
+
master.attributes = {name: "Single Standard"}
|
54
|
+
master.should_not be_valid
|
55
|
+
master.errors.should == {price: ["is required"]}
|
56
|
+
end
|
57
|
+
it "#update_attributes returns false instead of raising errors" do
|
58
|
+
master = @master_class.new
|
59
|
+
master.update_attributes(name: "Single Standard").should be_false
|
60
|
+
master.should be_new
|
61
|
+
master.errors.should == {price: ["is required"]}
|
62
|
+
master.update_attributes(price: 98).should be_true
|
63
|
+
end
|
64
|
+
it "allows creating a master and its first version in one step" do
|
65
|
+
master = @master_class.new
|
66
|
+
master.update_attributes(name: "Single Standard", price: 98).should be_true
|
67
|
+
master.should_not be_new
|
68
|
+
master.should have_versions %Q{
|
69
|
+
| name | price | created_at | expired_at | valid_from | valid_to | current |
|
70
|
+
| Single Standard | 98 | 2009-11-28 | | 2009-11-28 | | true |
|
71
|
+
}
|
72
|
+
end
|
73
|
+
it "prevents creating a new version in the past" do
|
74
|
+
master = @master_class.new
|
75
|
+
master.update_attributes name: "Single Standard", price: 98, valid_from: Date.today-1
|
76
|
+
master.should have_versions %Q{
|
77
|
+
| name | price | created_at | expired_at | valid_from | valid_to | current |
|
78
|
+
| Single Standard | 98 | 2009-11-28 | | 2009-11-28 | | true |
|
79
|
+
}
|
80
|
+
end
|
81
|
+
it "allows creating a new version in the future" do
|
82
|
+
master = @master_class.new
|
83
|
+
master.update_attributes name: "Single Standard", price: 98, valid_from: Date.today+1
|
84
|
+
master.should have_versions %Q{
|
85
|
+
| name | price | created_at | expired_at | valid_from | valid_to | current |
|
86
|
+
| Single Standard | 98 | 2009-11-28 | | 2009-11-29 | | |
|
87
|
+
}
|
88
|
+
end
|
89
|
+
it "doesn't loose previous version in same-day update" do
|
90
|
+
master = @master_class.new
|
91
|
+
master.update_attributes name: "Single Standard", price: 98
|
92
|
+
master.update_attributes name: "Single Standard", price: 94
|
93
|
+
master.should have_versions %Q{
|
94
|
+
| name | price | created_at | expired_at | valid_from | valid_to | current |
|
95
|
+
| Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-28 | | |
|
96
|
+
| Single Standard | 94 | 2009-11-28 | | 2009-11-28 | | true |
|
97
|
+
}
|
98
|
+
end
|
99
|
+
it "allows partial updating based on current version" do
|
100
|
+
master = @master_class.new
|
101
|
+
master.update_attributes name: "Single Standard", price: 98
|
102
|
+
master.update_attributes price: 94, partial_update: true
|
103
|
+
master.update_attributes name: "King Size", partial_update: true
|
104
|
+
master.should have_versions %Q{
|
105
|
+
| name | price | created_at | expired_at | valid_from | valid_to | current |
|
106
|
+
| Single Standard | 98 | 2009-11-28 | 2009-11-28 | 2009-11-28 | | |
|
107
|
+
| Single Standard | 94 | 2009-11-28 | 2009-11-28 | 2009-11-28 | | |
|
108
|
+
| King Size | 94 | 2009-11-28 | | 2009-11-28 | | true |
|
109
|
+
}
|
110
|
+
end
|
111
|
+
it "expires previous version but keep it in history" do
|
112
|
+
master = @master_class.new
|
113
|
+
master.update_attributes name: "Single Standard", price: 98
|
114
|
+
Timecop.freeze Date.today+1
|
115
|
+
master.update_attributes price: 94, partial_update: true
|
116
|
+
master.should have_versions %Q{
|
117
|
+
| name | price | created_at | expired_at | valid_from | valid_to | current |
|
118
|
+
| Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | | |
|
119
|
+
| Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
|
120
|
+
| Single Standard | 94 | 2009-11-29 | | 2009-11-29 | | true |
|
121
|
+
}
|
122
|
+
end
|
123
|
+
it "doesn't expire no longer valid versions" do
|
124
|
+
master = @master_class.new
|
125
|
+
master.update_attributes name: "Single Standard", price: 98, valid_to: Date.today+1
|
126
|
+
Timecop.freeze Date.today+1
|
127
|
+
master.update_attributes(price: 94, partial_update: true).should be_false
|
128
|
+
master.update_attributes name: "Single Standard", price: 94
|
129
|
+
master.should have_versions %Q{
|
130
|
+
| name | price | created_at | expired_at | valid_from | valid_to | current |
|
131
|
+
| Single Standard | 98 | 2009-11-28 | | 2009-11-28 | 2009-11-29 | |
|
132
|
+
| Single Standard | 94 | 2009-11-29 | | 2009-11-29 | | true |
|
133
|
+
}
|
134
|
+
end
|
135
|
+
it "allows shortening validity (COULD BE IMPROVED!)" do
|
136
|
+
master = @master_class.new
|
137
|
+
master.update_attributes name: "Single Standard", price: 98
|
138
|
+
Timecop.freeze Date.today+1
|
139
|
+
master.update_attributes valid_to: Date.today+10, partial_update: true
|
140
|
+
master.should have_versions %Q{
|
141
|
+
| name | price | created_at | expired_at | valid_from | valid_to | current |
|
142
|
+
| Single Standard | 98 | 2009-11-28 | 2009-11-29 | 2009-11-28 | | |
|
143
|
+
| Single Standard | 98 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
|
144
|
+
| Single Standard | 98 | 2009-11-29 | | 2009-11-29 | 2009-12-09 | true |
|
145
|
+
}
|
146
|
+
end
|
147
|
+
# Timecop.freeze Date.today+1
|
148
|
+
# version.update_attributes valid_to: "2009-12-05"
|
149
|
+
# version.master.check_versions %Q{
|
150
|
+
# | name | price | created_at | expired_at | valid_from | valid_to | current |
|
151
|
+
# | Single Standard | 98.00 | 2009-11-28 | 2009-11-29 | 2009-11-28 | | |
|
152
|
+
# | Single Standard | 98.00 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
|
153
|
+
# | Single Standard | 94.00 | 2009-11-29 | 2009-11-30 | 2009-11-29 | | |
|
154
|
+
# | Single Standard | 94.00 | 2009-11-30 | | 2009-11-29 | 2009-12-05 | true |
|
155
|
+
# }
|
156
|
+
# Timecop.freeze Date.today+1
|
157
|
+
# version.update_attributes valid_from: "2009-12-02", valid_to: nil, price: 95
|
158
|
+
# version.master.check_versions %Q{
|
159
|
+
# | name | price | created_at | expired_at | valid_from | valid_to | current |
|
160
|
+
# | Single Standard | 98.00 | 2009-11-28 | 2009-11-29 | 2009-11-28 | | |
|
161
|
+
# | Single Standard | 98.00 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
|
162
|
+
# | Single Standard | 94.00 | 2009-11-29 | 2009-11-30 | 2009-11-29 | | |
|
163
|
+
# | Single Standard | 94.00 | 2009-11-30 | 2009-12-01 | 2009-11-29 | 2009-12-05 | |
|
164
|
+
# | Single Standard | 94.00 | 2009-12-01 | | 2009-11-29 | 2009-12-02 | true |
|
165
|
+
# | Single Standard | 95.00 | 2009-12-01 | | 2009-11-02 | | |
|
166
|
+
# }
|
167
|
+
# Timecop.freeze Date.today+1
|
168
|
+
# version.master.check_versions %Q{
|
169
|
+
# | name | price | created_at | expired_at | valid_from | valid_to | current |
|
170
|
+
# | Single Standard | 98.00 | 2009-11-28 | 2009-11-29 | 2009-11-28 | | |
|
171
|
+
# | Single Standard | 98.00 | 2009-11-29 | | 2009-11-28 | 2009-11-29 | |
|
172
|
+
# | Single Standard | 94.00 | 2009-11-29 | 2009-11-30 | 2009-11-29 | | |
|
173
|
+
# | Single Standard | 94.00 | 2009-11-30 | 2009-12-01 | 2009-11-29 | 2009-12-05 | |
|
174
|
+
# | Single Standard | 94.00 | 2009-12-01 | | 2009-11-29 | 2009-12-02 | |
|
175
|
+
# | Single Standard | 95.00 | 2009-12-01 | | 2009-11-02 | | true |
|
176
|
+
# }
|
177
|
+
# missing scenarios:
|
178
|
+
# - same date update
|
179
|
+
# - save unchanged
|
180
|
+
# - delete scheduled version
|
181
|
+
# - delete all versions
|
182
|
+
# - simultaneous updates
|
183
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
RSpec::Matchers.define :have_versions do |versions_str|
|
2
|
+
@table = have_versions_parse_table versions_str
|
3
|
+
@last_index = nil
|
4
|
+
@last_version = nil
|
5
|
+
match do |master|
|
6
|
+
versions = master.versions_dataset.order(:id).all
|
7
|
+
versions.size == @table.size && @table.each.with_index.all? do |version, index|
|
8
|
+
@last_index = index
|
9
|
+
@last_version = version
|
10
|
+
master_version = versions[index]
|
11
|
+
[:name, :price, :valid_from, :valid_to, :created_at, :expired_at, :current].all? do |column|
|
12
|
+
expected = version[column.to_s]
|
13
|
+
case column
|
14
|
+
when :valid_to
|
15
|
+
expected = "9999-01-01"
|
16
|
+
when :current
|
17
|
+
expected = "false"
|
18
|
+
end if expected==""
|
19
|
+
found = master_version.send(column == :current ? "current?" : column).to_s
|
20
|
+
equal = found == expected
|
21
|
+
puts "#{column}: #{found} != #{expected}" unless equal
|
22
|
+
equal
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
failure_message_for_should do |master|
|
27
|
+
versions = master.versions_dataset.order(:id).all
|
28
|
+
if versions.size != @table.size
|
29
|
+
"Expected #{master.class} to have #{@table.size} versions but found #{versions.size}"
|
30
|
+
else
|
31
|
+
"Expected row #{@last_index+1} to match #{@last_version.inspect} but found #{versions[@last_index].inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def have_versions_parse_table(str)
|
37
|
+
rows = str.strip.split("\n")
|
38
|
+
rows.collect!{|row| row[/^\s*\|(.+)\|\s*$/, 1].split("|").collect(&:strip)}
|
39
|
+
headers = rows.shift
|
40
|
+
rows.collect{|row| Hash[headers.zip row]}
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sequel_bitemporal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joseph HALTER
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-01 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sqlite3
|
16
|
+
requirement: &2162303500 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2162303500
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &2162303060 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2162303060
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: timecop
|
38
|
+
requirement: &2162330280 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2162330280
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sequel
|
49
|
+
requirement: &2162329860 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2162329860
|
58
|
+
description: Bitemporal versioning for sequel, fully tested.
|
59
|
+
email:
|
60
|
+
- joseph.halter@thetalentbox.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- Gemfile
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- lib/sequel/plugins/bitemporal.rb
|
70
|
+
- lib/sequel_bitemporal.rb
|
71
|
+
- sequel_bitemporal.gemspec
|
72
|
+
- spec/bitemporal_spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- spec/support/bitemporal_matchers.rb
|
75
|
+
homepage: https://github.com/TalentBox/sequel_bitemporal
|
76
|
+
licenses: []
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 1.8.6
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Bitemporal versioning for sequel.
|
99
|
+
test_files:
|
100
|
+
- spec/bitemporal_spec.rb
|
101
|
+
- spec/spec_helper.rb
|
102
|
+
- spec/support/bitemporal_matchers.rb
|