chrono_model 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,7 +3,6 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
7
6
  InstalledFiles
8
7
  _yardoc
9
8
  coverage
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :development do
7
- gem 'ruby-debug19'
7
+ gem 'debugger'
8
8
  gem 'pry'
9
9
  gem 'rspec'
10
10
  end
data/Gemfile.lock ADDED
@@ -0,0 +1,60 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ chrono_model (0.3.0)
5
+ activerecord (~> 3.2)
6
+ pg
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (3.2.8)
12
+ activesupport (= 3.2.8)
13
+ builder (~> 3.0.0)
14
+ activerecord (3.2.8)
15
+ activemodel (= 3.2.8)
16
+ activesupport (= 3.2.8)
17
+ arel (~> 3.0.2)
18
+ tzinfo (~> 0.3.29)
19
+ activesupport (3.2.8)
20
+ i18n (~> 0.6)
21
+ multi_json (~> 1.0)
22
+ arel (3.0.2)
23
+ builder (3.0.4)
24
+ coderay (1.0.8)
25
+ columnize (0.3.6)
26
+ debugger (1.2.1)
27
+ columnize (>= 0.3.1)
28
+ debugger-linecache (~> 1.1.1)
29
+ debugger-ruby_core_source (~> 1.1.4)
30
+ debugger-linecache (1.1.2)
31
+ debugger-ruby_core_source (>= 1.1.1)
32
+ debugger-ruby_core_source (1.1.4)
33
+ diff-lcs (1.1.3)
34
+ i18n (0.6.1)
35
+ method_source (0.8.1)
36
+ multi_json (1.3.6)
37
+ pg (0.14.1)
38
+ pry (0.9.10)
39
+ coderay (~> 1.0.5)
40
+ method_source (~> 0.8)
41
+ slop (~> 3.3.1)
42
+ rspec (2.11.0)
43
+ rspec-core (~> 2.11.0)
44
+ rspec-expectations (~> 2.11.0)
45
+ rspec-mocks (~> 2.11.0)
46
+ rspec-core (2.11.1)
47
+ rspec-expectations (2.11.3)
48
+ diff-lcs (~> 1.1.3)
49
+ rspec-mocks (2.11.3)
50
+ slop (3.3.3)
51
+ tzinfo (0.3.33)
52
+
53
+ PLATFORMS
54
+ ruby
55
+
56
+ DEPENDENCIES
57
+ chrono_model!
58
+ debugger
59
+ pry
60
+ rspec
data/README.md CHANGED
@@ -85,6 +85,23 @@ by the other schema statements. E.g.:
85
85
  * `remove_index` - removes the index from the history table as well
86
86
 
87
87
 
88
+ ## Adding Temporal extensions to an existing table
89
+
90
+ Use `change_table`:
91
+
92
+ change_table :your_table, :temporal => true
93
+
94
+ If you want to also set up the history from your current data:
95
+
96
+ change_table :your_table, :temporal => true, :copy_data => true
97
+
98
+ This will create an history record for each record in your table, setting its
99
+ validity from midnight, January 1st, 1 CE. You can set a specific validity
100
+ with the `:validity` option:
101
+
102
+ change_table :your_table, :temporal => true, :copy_data => true, :validity => '1977-01-01'
103
+
104
+
88
105
  ## Data querying
89
106
 
90
107
  A model backed by a temporal view will behave like any other model backed by a
@@ -97,7 +114,8 @@ plain table. If you want to do as-of-date queries, you need to include the
97
114
  has_many :compositions
98
115
  end
99
116
 
100
- This will make an `as_of` class method available to your model. E.g.:
117
+ This will create a `Country::History` model inherited from `Country`, and it
118
+ will make an `as_of` class method available to your model. E.g.:
101
119
 
102
120
  Country.as_of(1.year.ago)
103
121
 
@@ -8,9 +8,14 @@ module ChronoModel
8
8
  # adapter for a clean override of its methods using super.
9
9
  #
10
10
  class Adapter < ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
11
- TEMPORAL_SCHEMA = 'temporal' # The schema holding current data
12
- HISTORY_SCHEMA = 'history' # The schema holding historical data
11
+ # The schema holding current data
12
+ TEMPORAL_SCHEMA = 'temporal'
13
13
 
14
+ # The schema holding historical data
15
+ HISTORY_SCHEMA = 'history'
16
+
17
+ # Chronomodel is supported starting with PostgreSQL >= 9.0
18
+ #
14
19
  def chrono_supported?
15
20
  postgresql_version >= 90000
16
21
  end
@@ -87,6 +92,24 @@ module ChronoModel
87
92
  chrono_create_view_for(table_name)
88
93
 
89
94
  TableCache.add! table_name
95
+
96
+ # Optionally copy the plain table data, setting up history
97
+ # retroactively.
98
+ #
99
+ if options[:copy_data]
100
+ seq = _on_history_schema { serial_sequence(table_name, primary_key(table_name)) }
101
+ from = options[:validity] || '0001-01-01 00:00:00'
102
+
103
+ execute %[
104
+ INSERT INTO #{HISTORY_SCHEMA}.#{table_name}
105
+ SELECT *,
106
+ nextval('#{seq}') AS hid,
107
+ timestamp '#{from}' AS valid_from,
108
+ timestamp '9999-12-31 00:00:00' AS valid_to,
109
+ timezone('UTC', now()) AS recorded_at
110
+ FROM #{TEMPORAL_SCHEMA}.#{table_name}
111
+ ]
112
+ end
90
113
  end
91
114
 
92
115
  chrono_alter(table_name) { super table_name, options, &block }
@@ -353,16 +376,32 @@ module ChronoModel
353
376
 
354
377
  # INSERT - inert data both in the temporal table and in the history one.
355
378
  #
356
- execute <<-SQL
357
- CREATE OR REPLACE RULE #{table}_ins AS ON INSERT TO #{table} DO INSTEAD (
379
+ if sequence.present?
380
+ execute <<-SQL
381
+ CREATE OR REPLACE RULE #{table}_ins AS ON INSERT TO #{table} DO INSTEAD (
358
382
 
359
- INSERT INTO #{current} ( #{fields} ) VALUES ( #{values} );
383
+ INSERT INTO #{current} ( #{fields} ) VALUES ( #{values} );
360
384
 
361
- INSERT INTO #{history} ( #{pk}, #{fields}, valid_from )
362
- VALUES ( currval('#{sequence}'), #{values}, timezone('UTC', now()) )
363
- RETURNING #{pk}, #{fields}
364
- )
365
- SQL
385
+ INSERT INTO #{history} ( #{pk}, #{fields}, valid_from )
386
+ VALUES ( currval('#{sequence}'), #{values}, timezone('UTC', now()) )
387
+ RETURNING #{pk}, #{fields}
388
+ )
389
+ SQL
390
+ else
391
+ fields_with_pk = "#{pk}, " << fields
392
+ values_with_pk = "new.#{pk}, " << values
393
+
394
+ execute <<-SQL
395
+ CREATE OR REPLACE RULE #{table}_ins AS ON INSERT TO #{table} DO INSTEAD (
396
+
397
+ INSERT INTO #{current} ( #{fields_with_pk} ) VALUES ( #{values_with_pk} );
398
+
399
+ INSERT INTO #{history} ( #{fields_with_pk}, valid_from )
400
+ VALUES ( #{values_with_pk}, timezone('UTC', now()) )
401
+ RETURNING #{fields_with_pk}
402
+ )
403
+ SQL
404
+ end
366
405
 
367
406
  # UPDATE - set the last history entry validity to now, save the current data
368
407
  # in a new history entry and update the temporal table with the new data.
@@ -11,6 +11,7 @@ module ChronoModel
11
11
  end
12
12
 
13
13
  task 'db:schema:load' => 'db:chrono:create_schemas'
14
+ task 'db:migrate' => 'db:chrono:create_schemas'
14
15
  end
15
16
 
16
17
  class SchemaDumper < ::ActiveRecord::SchemaDumper
@@ -11,122 +11,205 @@ module ChronoModel
11
11
  "Currently, only PostgreSQL >= 9.0 is supported."
12
12
  end
13
13
 
14
- unless chrono?
14
+ if table_exists? && !chrono?
15
15
  raise Error, "#{table_name} is not a temporal table. " \
16
16
  "Please use change_table :#{table_name}, :temporal => true"
17
17
  end
18
18
 
19
- TimeMachine.chrono_models[table_name] = self
19
+ history = TimeMachine.define_history_model_for(self)
20
+ TimeMachine.chrono_models[table_name] = history
20
21
  end
21
22
 
22
- # Returns an Hash keyed by table name of models that included
23
- # ChronoModel::TimeMachine
23
+ # Returns an Hash keyed by table name of ChronoModels
24
24
  #
25
25
  def self.chrono_models
26
26
  (@chrono_models ||= {})
27
27
  end
28
28
 
29
- # Returns a read-only representation of this record as it was +time+ ago.
30
- #
31
- def as_of(time)
32
- self.class.as_of(time).find(self)
33
- end
29
+ def self.define_history_model_for(model)
30
+ history = Class.new(model) do
31
+ self.table_name = [Adapter::HISTORY_SCHEMA, model.table_name].join('.')
34
32
 
35
- # Return the complete read-only history of this instance.
36
- #
37
- def history
38
- self.class.history_of(self)
39
- end
33
+ extend TimeMachine::HistoryMethods
40
34
 
41
- # Aborts the destroy if this is an historical record
42
- #
43
- def destroy
44
- if historical?
45
- raise ActiveRecord::ReadOnlyRecord, 'Cannot delete historical records'
46
- else
47
- super
35
+ # The history id is `hid`, but this cannot set as primary key
36
+ # or temporal assocations will break. Solutions are welcome.
37
+ def id
38
+ hid
39
+ end
40
+
41
+ # Referenced record ID
42
+ #
43
+ def rid
44
+ attributes[self.class.primary_key]
45
+ end
46
+
47
+ # HACK to make ActiveAdmin work properly. This will be surely
48
+ # better written in the future.
49
+ #
50
+ def self.find(*args)
51
+ old = self.primary_key
52
+ self.primary_key = :hid
53
+ super
54
+ ensure
55
+ self.primary_key = old
56
+ end
57
+
58
+ # SCD Type 2 validity from timestamp
59
+ #
60
+ def valid_from
61
+ utc_timestamp_from('valid_from')
62
+ end
63
+
64
+ # SCD Type 2 validity to timestamp
65
+ #
66
+ def valid_to
67
+ utc_timestamp_from('valid_to')
68
+ end
69
+
70
+ # History recording timestamp
71
+ #
72
+ def recorded_at
73
+ utc_timestamp_from('recorded_at')
74
+ end
75
+
76
+ # Virtual attribute used to pass around the
77
+ # current timestamp in association queries
78
+ #
79
+ def as_of_time
80
+ Conversions.string_to_utc_time attributes['as_of_time']
81
+ end
82
+
83
+ # Inhibit destroy of historical records
84
+ #
85
+ def destroy
86
+ raise ActiveRecord::ReadOnlyRecord, 'Cannot delete historical records'
87
+ end
88
+
89
+ private
90
+ # Hack around AR timezone support. These timestamps are recorded
91
+ # by the chrono rewrite rules in UTC, but AR reads them as they
92
+ # were stored in the local timezone - thus here we reset its
93
+ # assumption. TODO: OPTIMIZE.
94
+ #
95
+ if ActiveRecord::Base.default_timezone != :utc
96
+ def utc_timestamp_from(attr)
97
+ attributes[attr].utc + Time.now.utc_offset
98
+ end
99
+ else
100
+ def utc_timestamp_from(attr)
101
+ attributes[attr]
102
+ end
103
+ end
48
104
  end
49
- end
50
105
 
51
- # Returns true if this record was fetched from history
52
- #
53
- def historical?
54
- attributes.key?('hid')
55
- end
106
+ model.singleton_class.instance_eval do
107
+ define_method(:history) { history }
108
+ end
56
109
 
57
- HISTORY_ATTRIBUTES = %w( valid_from valid_to recorded_at as_of_time ).each do |attr|
58
- define_method(attr) { Conversions.string_to_utc_time(attributes[attr]) }
110
+ model.const_set :History, history
111
+
112
+ return history
59
113
  end
60
114
 
61
- # Strips the history timestamps when duplicating history records
115
+ # Returns a read-only representation of this record as it was +time+ ago.
62
116
  #
63
- def initialize_dup(other)
64
- super
117
+ def as_of(time)
118
+ self.class.as_of(time).where(:id => self.id).first!
119
+ end
65
120
 
66
- if historical?
67
- HISTORY_ATTRIBUTES.each {|attr| @attributes.delete(attr)}
68
- @attributes.delete 'hid'
69
- @readonly = false
70
- @new_record = true
71
- end
121
+ # Return the complete read-only history of this instance.
122
+ #
123
+ def history
124
+ self.class.history.of(self)
72
125
  end
73
126
 
74
127
  # Returns an Array of timestamps for which this instance has an history
75
128
  # record. Takes temporal associations into account.
76
129
  #
77
130
  def history_timestamps
78
- self.class.history_timestamps do |query|
131
+ self.class.history.timestamps do |query|
79
132
  query.where(:id => self)
80
133
  end
81
134
  end
82
135
 
136
+ def historical?
137
+ self.kind_of? self.class.history
138
+ end
139
+
83
140
  module ClassMethods
84
- # Fetches as of +time+ records.
85
- #
141
+ # Returns an ActiveRecord::Relation on the history of this model as
142
+ # it was +time+ ago.
86
143
  def as_of(time)
87
- time = Conversions.time_to_utc_string(time.utc)
144
+ history.as_of(time, current_scope)
145
+ end
146
+ end
88
147
 
89
- readonly.with(table_name, on_history(time)).tap do |relation|
90
- relation.instance_variable_set(:@temporal, time)
148
+ # Methods that make up the history interface of the companion History
149
+ # model build on each Model that includes TimeMachine
150
+ module HistoryMethods
151
+ # Fetches as of +time+ records.
152
+ #
153
+ def as_of(time, scope = nil)
154
+ time = Conversions.time_to_utc_string(time.utc) if time.kind_of? Time
155
+
156
+ as_of = superclass.unscoped.readonly.
157
+ with(superclass.table_name, at(time))
158
+
159
+ # Add default scopes back if we're passed nil or a
160
+ # specific scope, because we're .unscopeing above.
161
+ #
162
+ scopes = scope.present? ? [scope] : (
163
+ superclass.default_scopes.map do |s|
164
+ s.respond_to?(:call) ? s.call : s
165
+ end)
166
+
167
+ scopes.each do |scope|
168
+ scope.order_values.each {|clause| as_of = as_of.order(clause.to_sql)}
169
+ scope.where_values.each {|clause| as_of = as_of.where(clause.to_sql)}
91
170
  end
171
+
172
+ as_of.instance_variable_set(:@temporal, time)
173
+
174
+ return as_of
92
175
  end
93
176
 
94
- def on_history(time)
95
- unscoped.from(history_table_name).
96
- select("#{history_table_name}.*, '#{time}' AS as_of_time").
97
- where("'#{time}' >= valid_from AND '#{time}' < valid_to")
177
+ # Fetches history record at the given time
178
+ #
179
+ def at(time)
180
+ from, to = quoted_history_fields
181
+ unscoped.
182
+ select("#{quoted_table_name}.*, '#{time}' AS as_of_time").
183
+ where("'#{time}' >= #{from} AND '#{time}' < #{to}")
98
184
  end
99
185
 
100
186
  # Returns the whole history as read only.
101
187
  #
102
- def history
103
- readonly.from(history_table_name).order("#{history_table_name}.recorded_at")
188
+ def all
189
+ readonly.
190
+ order("#{table_name}.recorded_at, hid").all
104
191
  end
105
192
 
106
193
  # Fetches the given +object+ history, sorted by history record time.
107
194
  #
108
- def history_of(object)
109
- history.
110
- select("#{history_table_name}.*").
111
- select('LEAST(valid_to, now()::timestamp) AS as_of_time').
195
+ def of(object)
196
+ now = 'LEAST(valid_to, now()::timestamp)'
197
+ readonly.
198
+ select("#{table_name}.*, #{now} AS as_of_time").
199
+ order("#{table_name}.recorded_at, hid").
112
200
  where(:id => object)
113
201
  end
114
202
 
115
- # Returns this table name in the +Adapter::HISTORY_SCHEMA+
116
- #
117
- def history_table_name
118
- [Adapter::HISTORY_SCHEMA, table_name].join('.')
119
- end
120
203
 
121
204
  # Returns an Array of unique UTC timestamps for which at least an
122
205
  # history record exists. Takes temporal associations into account.
123
206
  #
124
- def history_timestamps
207
+ def timestamps
125
208
  assocs = reflect_on_all_associations.select {|a|
126
- [:has_one, :has_many].include?(a.macro) && a.klass.chrono?
209
+ [:belongs_to, :has_one, :has_many].include?(a.macro) && a.klass.chrono?
127
210
  }
128
211
 
129
- models = [self].concat(assocs.map(&:klass))
212
+ models = [self].concat(assocs.map {|a| a.klass.history})
130
213
  fields = models.inject([]) {|a,m| a.concat m.quoted_history_fields}
131
214
 
132
215
  relation = self.
@@ -140,14 +223,14 @@ module ChronoModel
140
223
  sql.gsub! 'INNER JOIN', 'LEFT OUTER JOIN'
141
224
 
142
225
  connection.on_schema(Adapter::HISTORY_SCHEMA) do
143
- connection.select_values(sql, "#{self.name} history periods").map! do |ts|
226
+ connection.select_values(sql, "#{self.name} periods").map! do |ts|
144
227
  Conversions.string_to_utc_time ts
145
228
  end
146
229
  end
147
230
  end
148
231
 
149
232
  def quoted_history_fields
150
- [:valid_from, :valid_to].map do |field|
233
+ @quoted_history_fields ||= [:valid_from, :valid_to].map do |field|
151
234
  [connection.quote_table_name(table_name),
152
235
  connection.quote_column_name(field)
153
236
  ].join('.')
@@ -162,7 +245,7 @@ module ChronoModel
162
245
  # Extract joined tables and add temporal WITH if appropriate
163
246
  arel.join_sources.map {|j| j.to_sql =~ /JOIN "(\w+)" ON/ && $1}.compact.each do |table|
164
247
  next unless (model = TimeMachine.chrono_models[table])
165
- with(table, model.on_history(@temporal))
248
+ with(table, model.history.at(@temporal))
166
249
  end if @temporal
167
250
 
168
251
  end
@@ -1,3 +1,3 @@
1
1
  module ChronoModel
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -64,6 +64,11 @@ module ChronoTest::Helpers
64
64
  t.string :name
65
65
  t.references :bar
66
66
  end
67
+
68
+ adapter.create_table 'defoos', :temporal => true do |t|
69
+ t.string :name
70
+ t.boolean :active
71
+ end
67
72
  end
68
73
 
69
74
  after(:all) do
@@ -90,6 +95,12 @@ module ChronoTest::Helpers
90
95
  class ::Baz < ActiveRecord::Base
91
96
  belongs_to :baz
92
97
  end
98
+
99
+ class ::Defoo < ActiveRecord::Base
100
+ include ChronoModel::TimeMachine
101
+
102
+ default_scope where(:active => true)
103
+ end
93
104
  }
94
105
 
95
106
  def define_models!
@@ -10,7 +10,7 @@ describe ChronoModel::TimeMachine do
10
10
  describe '.chrono_models' do
11
11
  subject { ChronoModel::TimeMachine.chrono_models }
12
12
 
13
- it { should == {'foos' => Foo, 'bars' => Bar} }
13
+ it { should == {'foos' => Foo::History, 'defoos' => Defoo::History, 'bars' => Bar::History} }
14
14
  end
15
15
 
16
16
 
@@ -88,6 +88,28 @@ describe ChronoModel::TimeMachine do
88
88
  it 'raises RecordNotFound when no history records are found' do
89
89
  expect { foo.as_of(1.minute.ago) }.to raise_error
90
90
  end
91
+
92
+ describe 'it honors default_scopes' do
93
+ let!(:active) {
94
+ active = ts_eval { Defoo.create! :name => 'active 1', :active => true }
95
+ ts_eval(active) { update_attributes! :name => 'active 2' }
96
+ }
97
+
98
+ let!(:hidden) {
99
+ hidden = ts_eval { Defoo.create! :name => 'hidden 1', :active => false }
100
+ ts_eval(hidden) { update_attributes! :name => 'hidden 2' }
101
+ }
102
+
103
+ it { Defoo.as_of(active.ts[0]).map(&:name).should == ['active 1'] }
104
+ it { Defoo.as_of(active.ts[1]).map(&:name).should == ['active 2'] }
105
+ it { Defoo.as_of(hidden.ts[0]).map(&:name).should == ['active 2'] }
106
+ it { Defoo.as_of(hidden.ts[1]).map(&:name).should == ['active 2'] }
107
+
108
+ it { Defoo.unscoped.as_of(active.ts[0]).map(&:name).should == ['active 1'] }
109
+ it { Defoo.unscoped.as_of(active.ts[1]).map(&:name).should == ['active 2'] }
110
+ it { Defoo.unscoped.as_of(hidden.ts[0]).map(&:name).should == ['active 2', 'hidden 1'] }
111
+ it { Defoo.unscoped.as_of(hidden.ts[1]).map(&:name).should == ['active 2', 'hidden 2'] }
112
+ end
91
113
  end
92
114
 
93
115
  describe '#history' do
@@ -194,21 +216,19 @@ describe ChronoModel::TimeMachine do
194
216
  describe 'on records having a :belongs_to relationship' do
195
217
  subject { bar.history_timestamps }
196
218
 
197
- describe 'returns timestamps of the record only' do
198
- its(:size) { should == bar.ts.size }
199
- it { should == timestamps_from.call(bar) }
219
+ describe 'returns timestamps of the record and its associations' do
220
+ its(:size) { should == foo.ts.size + bar.ts.size }
221
+ it { should == timestamps_from.call(foo, bar) }
200
222
  end
201
223
  end
202
224
  end
203
225
 
204
226
  context do
205
- history_attrs = ChronoModel::TimeMachine::HISTORY_ATTRIBUTES
206
-
207
227
  let!(:history) { foo.history.first }
208
228
  let!(:current) { foo }
209
229
 
210
- history_attrs.each do |attr|
211
- describe ['#', attr].join do
230
+ spec = lambda {|attr|
231
+ return lambda {|*|
212
232
  describe 'on history records' do
213
233
  subject { history.public_send(attr) }
214
234
 
@@ -219,23 +239,13 @@ describe ChronoModel::TimeMachine do
219
239
 
220
240
  describe 'on current records' do
221
241
  subject { current.public_send(attr) }
222
- it { should be_nil }
223
- end
224
- end
225
- end
226
-
227
- describe '#initialize_dup' do
228
- describe 'on history records' do
229
- subject { history.dup }
230
-
231
- history_attrs.each do |attr|
232
- its(attr) { should be_nil }
242
+ it { expect { subject }.to raise_error(NoMethodError) }
233
243
  end
244
+ }
245
+ }
234
246
 
235
- it { should_not be_readonly }
236
- it { should be_new_record }
237
-
238
- end
247
+ %w( valid_from valid_to recorded_at as_of_time ).each do |attr|
248
+ describe ['#', attr].join, &spec.call(attr)
239
249
  end
240
250
  end
241
251
 
@@ -291,8 +301,8 @@ describe ChronoModel::TimeMachine do
291
301
  ['bar', 'foo bar', 'bar bar', 'new bar', 'bar 0', 'bar 1']
292
302
  }
293
303
 
294
- it { Foo.history.map(&:name).should == foo_history }
295
- it { Bar.history.map(&:name).should == bar_history }
304
+ it { Foo.history.all.map(&:name).should == foo_history }
305
+ it { Bar.history.all.map(&:name).should == bar_history }
296
306
  end
297
307
  end
298
308
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chrono_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-22 00:00:00.000000000 Z
12
+ date: 2012-10-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
- requirement: &79509780 !ruby/object:Gem::Requirement
16
+ requirement: &70658630 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '3.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *79509780
24
+ version_requirements: *70658630
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: pg
27
- requirement: &79509350 !ruby/object:Gem::Requirement
27
+ requirement: &70658260 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *79509350
35
+ version_requirements: *70658260
36
36
  description: Give your models as-of date temporal extensions. Built entirely for PostgreSQL
37
37
  >= 9.0
38
38
  email:
@@ -44,6 +44,7 @@ files:
44
44
  - .gitignore
45
45
  - .rspec
46
46
  - Gemfile
47
+ - Gemfile.lock
47
48
  - LICENSE
48
49
  - README.md
49
50
  - README.sql