chrono_model 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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