chrono_model 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,20 +6,36 @@ namespace :db do
6
6
  task :dump => :environment do
7
7
  config = PG.config!
8
8
  target = ENV['DB_STRUCTURE'] || Rails.root.join('db', 'structure.sql')
9
- schema = config[:schema_search_path] || 'public'
9
+ schema_search_path = config[:schema_search_path]
10
10
 
11
- PG.make_dump target, *config.values_at(:username, :database),
12
- '-s', '-O', '-n', schema,
13
- '-n', ChronoModel::Adapter::TEMPORAL_SCHEMA,
14
- '-n', ChronoModel::Adapter::HISTORY_SCHEMA
11
+ unless schema_search_path.blank?
12
+ # add in chronomodel schemas
13
+ schema_search_path << ",#{ChronoModel::Adapter::TEMPORAL_SCHEMA}"
14
+ schema_search_path << ",#{ChronoModel::Adapter::HISTORY_SCHEMA}"
15
+
16
+ # convert to command line arguments
17
+ schema_search_path = schema_search_path.split(",").map{|part| "--schema=#{part.strip}" }.join(" ")
18
+ end
19
+
20
+ PG.make_dump target,
21
+ *config.values_at(:username, :database),
22
+ '-i', '-x', '-s', '-O', schema_search_path
15
23
 
16
24
  # Add migration information, after resetting the schema to the default one
17
25
  File.open(target, 'a') do |f|
18
- f.puts "SET search_path = #{schema}, pg_catalog;"
26
+ f.puts "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n"
19
27
  f.puts ActiveRecord::Base.connection.dump_schema_information
20
28
  end
21
- end
22
29
 
30
+ # the structure.sql file will contain CREATE SCHEMA statements
31
+ # but chronomodel creates the temporal and history schemas
32
+ # when the connection is established, so a db:structure:load fails
33
+ # fix up create schema statements to include the IF NOT EXISTS directive
34
+
35
+ sql = File.read(target)
36
+ sql.gsub!(/CREATE SCHEMA /, 'CREATE SCHEMA IF NOT EXISTS ')
37
+ File.open(target, "w") { |file| file << sql }
38
+ end
23
39
 
24
40
  desc "Load structure.sql file into the current environment's database"
25
41
  task :load => :environment do
@@ -24,11 +24,12 @@ module PG
24
24
  ENV['PGHOST'] = config[:host].to_s if config.key?(:host)
25
25
  ENV['PGPORT'] = config[:port].to_s if config.key?(:port)
26
26
  ENV['PGPASSWORD'] = config[:password].to_s if config.key?(:password)
27
+ ENV['PGUSER'] = config[:username].to_s if config.key?(:username)
27
28
  end
28
29
  end
29
30
 
30
31
  def make_dump(target, username, database, *options)
31
- exec 'pg_dump', '-f', target, '-U', username, database, *options
32
+ exec 'pg_dump', '-f', target, '-U', username, '-d', database, *options
32
33
  end
33
34
 
34
35
  def load_dump(source, username, database, *options)
@@ -8,22 +8,14 @@ module ChronoModel
8
8
 
9
9
  module ClassMethods
10
10
  def as_of(time)
11
- time = Conversions.time_to_utc_string(time.utc) if time.kind_of? Time
12
-
13
- virtual_table = select(%[
14
- #{quoted_table_name}.*, #{connection.quote(time)}::timestamp AS "as_of_time"]
15
- ).to_sql
16
-
17
- as_of = all.from("(#{virtual_table}) #{quoted_table_name}")
18
-
19
- as_of.instance_variable_set(:@temporal, time)
20
-
21
- return as_of
11
+ all.as_of_time!(time)
22
12
  end
23
13
 
24
14
  include TimeMachine::HistoryMethods::Timeline
25
15
  end
26
16
 
17
+ include Patches::AsOfTimeHolder
18
+
27
19
  def as_of(time)
28
20
  self.class.as_of(time).where(:id => self.id).first!
29
21
  end
@@ -5,6 +5,8 @@ module ChronoModel
5
5
  module TimeMachine
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ include Patches::AsOfTimeHolder
9
+
8
10
  included do
9
11
  if table_exists? && !chrono?
10
12
  puts "WARNING: #{table_name} is not a temporal table. " \
@@ -14,16 +16,28 @@ module ChronoModel
14
16
  history = TimeMachine.define_history_model_for(self)
15
17
  TimeMachine.chrono_models[table_name] = history
16
18
 
17
- # STI support. TODO: more thorough testing
18
- #
19
- def self.inherited(subclass)
20
- super
19
+ class << self
20
+ alias_method :direct_descendants_with_history, :direct_descendants
21
+ def direct_descendants
22
+ direct_descendants_with_history.reject(&:history?)
23
+ end
24
+
25
+ alias_method :descendants_with_history, :descendants
26
+ def descendants
27
+ descendants_with_history.reject(&:history?)
28
+ end
21
29
 
22
- # Do not smash stack: as the below method is defining a
23
- # new anonymous class, without this check this leads to
24
- # infinite recursion.
25
- unless subclass.name.nil?
26
- TimeMachine.define_inherited_history_model_for(subclass)
30
+ # STI support. TODO: more thorough testing
31
+ #
32
+ def inherited(subclass)
33
+ super
34
+
35
+ # Do not smash stack: as the below method is defining a
36
+ # new anonymous class, without this check this leads to
37
+ # infinite recursion.
38
+ unless subclass.name.nil?
39
+ TimeMachine.define_inherited_history_model_for(subclass)
40
+ end
27
41
  end
28
42
  end
29
43
  end
@@ -40,6 +54,8 @@ module ChronoModel
40
54
 
41
55
  extend TimeMachine::HistoryMethods
42
56
 
57
+ scope :chronological, -> { order(:recorded_at, :hid) }
58
+
43
59
  # The history id is `hid`, but this cannot set as primary key
44
60
  # or temporal assocations will break. Solutions are welcome.
45
61
  def id
@@ -148,6 +164,7 @@ module ChronoModel
148
164
  def valid_to
149
165
  validity.last
150
166
  end
167
+ alias as_of_time valid_to
151
168
 
152
169
  def recorded_at
153
170
  Conversions.string_to_utc_time attributes_before_type_cast['recorded_at']
@@ -212,7 +229,7 @@ module ChronoModel
212
229
  # Return the complete read-only history of this instance.
213
230
  #
214
231
  def history
215
- self.class.history.of(self)
232
+ self.class.history.chronological.of(self)
216
233
  end
217
234
 
218
235
  # Returns an Array of timestamps for which this instance has an history
@@ -225,13 +242,7 @@ module ChronoModel
225
242
  # Returns a boolean indicating whether this record is an history entry.
226
243
  #
227
244
  def historical?
228
- self.attributes.key?('as_of_time') || self.kind_of?(self.class.history)
229
- end
230
-
231
- # Read the virtual 'as_of_time' attribute and return it as an UTC timestamp.
232
- #
233
- def as_of_time
234
- Conversions.string_to_utc_time attributes_before_type_cast['as_of_time']
245
+ self.as_of_time.present? || self.kind_of?(self.class.history)
235
246
  end
236
247
 
237
248
  # Inhibit destroy of historical records
@@ -317,15 +328,21 @@ module ChronoModel
317
328
  end
318
329
 
319
330
  module ClassMethods
331
+ # Identify this class as the parent, non-history, class.
332
+ #
333
+ def history?
334
+ false
335
+ end
336
+
320
337
  # Returns an ActiveRecord::Relation on the history of this model as
321
338
  # it was +time+ ago.
322
339
  def as_of(time)
323
- history.as_of(time, current_scope)
340
+ history.as_of(time)
324
341
  end
325
342
 
326
343
  def attribute_names_for_history_changes
327
344
  @attribute_names_for_history_changes ||= attribute_names -
328
- %w( id hid validity recorded_at as_of_time )
345
+ %w( id hid validity recorded_at )
329
346
  end
330
347
 
331
348
  def has_timeline(options)
@@ -401,6 +418,10 @@ module ChronoModel
401
418
  def build_time_query_at(time, range)
402
419
  time = if time.kind_of?(Array)
403
420
  time.map! {|t| time_for_time_query(t, range)}
421
+
422
+ # If both edges of the range are the same the query fails using the '&&' operator.
423
+ # The correct solution is to use the <@ operator.
424
+ time.first == time.last ? time.first : time
404
425
  else
405
426
  time_for_time_query(time, range)
406
427
  end
@@ -446,34 +467,21 @@ module ChronoModel
446
467
  # superclass of Fruit::History, which is Fruit. So, we use
447
468
  # non_history_superclass instead. -npj
448
469
  def non_history_superclass(klass = self)
449
- if klass.superclass.respond_to?(:history?) && klass.superclass.history?
470
+ if klass.superclass.history?
450
471
  non_history_superclass(klass.superclass)
451
472
  else
452
473
  klass.superclass
453
474
  end
454
475
  end
455
476
 
477
+ def relation
478
+ super.as_of_time!(Time.now)
479
+ end
480
+
456
481
  # Fetches as of +time+ records.
457
482
  #
458
- def as_of(time, scope = nil)
459
- as_of = non_history_superclass.unscoped.from(virtual_table_at(time))
460
-
461
- # Add default scopes back if we're passed nil or a
462
- # specific scope, because we're .unscopeing above.
463
- #
464
- scopes = !scope.nil? ? [scope] : (
465
- superclass.default_scopes.map do |s|
466
- s.respond_to?(:call) ? s.call : s
467
- end)
468
-
469
- scopes.each do |s|
470
- s.order_values.each {|clause| as_of = as_of.order(clause)}
471
- s.where_values.each {|clause| as_of = as_of.where(clause)}
472
- end
473
-
474
- as_of.instance_variable_set(:@temporal, time)
475
-
476
- return as_of
483
+ def as_of(time)
484
+ non_history_superclass.from(virtual_table_at(time)).as_of_time!(time)
477
485
  end
478
486
 
479
487
  def virtual_table_at(time, name = nil)
@@ -486,11 +494,7 @@ module ChronoModel
486
494
  # Fetches history record at the given time
487
495
  #
488
496
  def at(time)
489
- time = Conversions.time_to_utc_string(time.utc) if time.kind_of?(Time) && !time.utc?
490
-
491
- unscoped.
492
- select("#{quoted_table_name}.*, #{connection.quote(time)}::timestamp AS as_of_time").
493
- time_query(:at, time)
497
+ time_query(:at, time).from(quoted_table_name).as_of_time!(time)
494
498
  end
495
499
 
496
500
  # Returns the history sorted by recorded_at
@@ -505,49 +509,15 @@ module ChronoModel
505
509
  # is maximum.
506
510
  #
507
511
  def of(object)
508
- where(:id => object).extend(HistorySelect)
512
+ where(:id => object)
509
513
  end
510
514
 
511
- # HACK FIXME. When querying history, ChronoModel does not add his
512
- # timestamps and sorting if there is an aggregate function in the
513
- # select list - as it is likely what you'll want. However, if you
514
- # have a query that performs an aggregate in a subquery, the code
515
- # below will do the wrong thing - and you'll have to forcibly add
516
- # back the history fields yourself.
515
+ # FIXME Remove, this was a workaround to a former design flaw.
517
516
  #
518
- # The obvious solution is to use a VIEW on the history containing
519
- # the added history fields, and remove all this crap from here...
520
- # but it is not easily feasible. So we're going with a workaround
521
- # for now.
522
- #
523
- # - vjt Wed Apr 2 19:56:35 CEST 2014
517
+ # - vjt Wed Oct 28 17:13:57 CET 2015
524
518
  #
525
519
  def force_history_fields
526
- select(HistorySelect::SELECT_VALUES).order(HistorySelect::ORDER_VALUES[quoted_table_name])
527
- end
528
-
529
- module HistorySelect #:nodoc:
530
- Aggregates = %r{(?:(?:bit|bool)_(?:and|or)|(?:array_|string_|xml)agg|count|every|m(?:in|ax)|sum|stddev|var(?:_pop|_samp|iance)|corr|covar_|regr_)\w*\s*\(}i
531
-
532
- SELECT_VALUES = "upper(validity) AS as_of_time"
533
- ORDER_VALUES = lambda {|tbl| %[#{tbl}."recorded_at", #{tbl}."hid"]}
534
-
535
- def build_arel
536
- has_aggregate = select_values.any? do |v|
537
- v.kind_of?(Arel::Nodes::Function) || # FIXME this is a bit ugly.
538
- v.to_s =~ Aggregates
539
- end
540
-
541
- return super if has_aggregate
542
-
543
- if order_values.blank?
544
- self.order_values += [ ORDER_VALUES[quoted_table_name] ]
545
- end
546
-
547
- super.tap do |rel|
548
- rel.project(SELECT_VALUES)
549
- end
550
- end
520
+ self
551
521
  end
552
522
 
553
523
  include(Timeline = Module.new do
@@ -568,7 +538,7 @@ module ChronoModel
568
538
 
569
539
  fields = models.inject([]) {|a,m| a.concat m.quoted_history_fields}
570
540
 
571
- relation = self.
541
+ relation = self.except(:order).
572
542
  select("DISTINCT UNNEST(ARRAY[#{fields.join(',')}]) AS ts")
573
543
 
574
544
  if assocs.present?
@@ -639,25 +609,6 @@ module ChronoModel
639
609
  end
640
610
  end
641
611
  end
642
-
643
- module QueryMethods
644
- def build_arel
645
- super.tap do |arel|
646
-
647
- arel.join_sources.each do |join|
648
- model = TimeMachine.chrono_models[join.left.table_name]
649
- next unless model
650
-
651
- join.left = Arel::Nodes::SqlLiteral.new(
652
- model.history.virtual_table_at(@temporal, join.left.table_alias || join.left.table_name)
653
- )
654
- end if @temporal
655
-
656
- end
657
- end
658
- end
659
- ActiveRecord::Relation.instance_eval { include QueryMethods }
660
-
661
612
  end
662
613
 
663
614
  end
@@ -1,3 +1,3 @@
1
1
  module ChronoModel
2
- VERSION = "0.8.2"
2
+ VERSION = "0.9.0"
3
3
  end
data/spec/adapter_spec.rb CHANGED
@@ -2,30 +2,30 @@ require 'spec_helper'
2
2
  require 'support/helpers'
3
3
 
4
4
  shared_examples_for 'temporal table' do
5
- it { adapter.is_chrono?(subject).should be_true }
5
+ it { expect(adapter.is_chrono?(subject)).to be(true) }
6
6
 
7
- it { should_not have_public_backing }
7
+ it { is_expected.to_not have_public_backing }
8
8
 
9
- it { should have_temporal_backing }
10
- it { should have_history_backing }
11
- it { should have_history_extra_columns }
12
- it { should have_public_interface }
9
+ it { is_expected.to have_temporal_backing }
10
+ it { is_expected.to have_history_backing }
11
+ it { is_expected.to have_history_extra_columns }
12
+ it { is_expected.to have_public_interface }
13
13
 
14
- it { should have_columns(columns) }
15
- it { should have_temporal_columns(columns) }
16
- it { should have_history_columns(columns) }
14
+ it { is_expected.to have_columns(columns) }
15
+ it { is_expected.to have_temporal_columns(columns) }
16
+ it { is_expected.to have_history_columns(columns) }
17
17
  end
18
18
 
19
19
  shared_examples_for 'plain table' do
20
- it { adapter.is_chrono?(subject).should be_false }
20
+ it { expect(adapter.is_chrono?(subject)).to be(false) }
21
21
 
22
- it { should have_public_backing }
22
+ it { is_expected.to have_public_backing }
23
23
 
24
- it { should_not have_temporal_backing }
25
- it { should_not have_history_backing }
26
- it { should_not have_public_interface }
24
+ it { is_expected.to_not have_temporal_backing }
25
+ it { is_expected.to_not have_history_backing }
26
+ it { is_expected.to_not have_public_interface }
27
27
 
28
- it { should have_columns(columns) }
28
+ it { is_expected.to have_columns(columns) }
29
29
  end
30
30
 
31
31
  describe ChronoModel::Adapter do
@@ -33,17 +33,21 @@ describe ChronoModel::Adapter do
33
33
 
34
34
  context do
35
35
  subject { adapter }
36
- it { should be_a_kind_of(ChronoModel::Adapter) }
37
- its(:adapter_name) { should == 'PostgreSQL' }
36
+ it { is_expected.to be_a_kind_of(ChronoModel::Adapter) }
38
37
 
39
38
  context do
40
- before { adapter.stub(:postgresql_version => 90300) }
41
- it { should be_chrono_supported }
39
+ subject { adapter.adapter_name }
40
+ it { is_expected.to eq 'PostgreSQL' }
42
41
  end
43
42
 
44
43
  context do
45
- before { adapter.stub(:postgresql_version => 90000) }
46
- it { should_not be_chrono_supported }
44
+ before { expect(adapter).to receive(:postgresql_version).and_return(90300) }
45
+ it { is_expected.to be_chrono_supported }
46
+ end
47
+
48
+ context do
49
+ before { expect(adapter).to receive(:postgresql_version).and_return(90000) }
50
+ it { is_expected.to_not be_chrono_supported }
47
51
  end
48
52
  end
49
53
 
@@ -52,7 +56,7 @@ describe ChronoModel::Adapter do
52
56
 
53
57
  columns do
54
58
  native = [
55
- ['test', 'character varying(255)'],
59
+ ['test', 'character varying'],
56
60
  ['foo', 'integer'],
57
61
  ['bar', 'double precision'],
58
62
  ['baz', 'text']
@@ -135,11 +139,11 @@ describe ChronoModel::Adapter do
135
139
  end
136
140
 
137
141
  it "copies plain index to history" do
138
- history_indexes.find {|i| i.columns == ['foo']}.should be_present
142
+ expect(history_indexes.find {|i| i.columns == ['foo']}).to be_present
139
143
  end
140
144
 
141
145
  it "copies unique index to history without uniqueness constraint" do
142
- history_indexes.find {|i| i.columns == ['bar'] && i.unique == false}.should be_present
146
+ expect(history_indexes.find {|i| i.columns == ['bar'] && i.unique == false}).to be_present
143
147
  end
144
148
  end
145
149
  end
@@ -151,10 +155,10 @@ describe ChronoModel::Adapter do
151
155
  adapter.drop_table table
152
156
  end
153
157
 
154
- it { should_not have_public_backing }
155
- it { should_not have_temporal_backing }
156
- it { should_not have_history_backing }
157
- it { should_not have_public_interface }
158
+ it { is_expected.to_not have_public_backing }
159
+ it { is_expected.to_not have_temporal_backing }
160
+ it { is_expected.to_not have_history_backing }
161
+ it { is_expected.to_not have_public_interface }
158
162
  end
159
163
 
160
164
  describe '.add_index' do
@@ -164,13 +168,13 @@ describe ChronoModel::Adapter do
164
168
  adapter.add_index table, [:test], :name => 'test_index'
165
169
  end
166
170
 
167
- it { should have_temporal_index 'foobar_index', %w( foo bar ) }
168
- it { should have_history_index 'foobar_index', %w( foo bar ) }
169
- it { should have_temporal_index 'test_index', %w( test ) }
170
- it { should have_history_index 'test_index', %w( test ) }
171
+ it { is_expected.to have_temporal_index 'foobar_index', %w( foo bar ) }
172
+ it { is_expected.to have_history_index 'foobar_index', %w( foo bar ) }
173
+ it { is_expected.to have_temporal_index 'test_index', %w( test ) }
174
+ it { is_expected.to have_history_index 'test_index', %w( test ) }
171
175
 
172
- it { should_not have_index 'foobar_index', %w( foo bar ) }
173
- it { should_not have_index 'test_index', %w( test ) }
176
+ it { is_expected.to_not have_index 'foobar_index', %w( foo bar ) }
177
+ it { is_expected.to_not have_index 'test_index', %w( test ) }
174
178
  end
175
179
 
176
180
  with_plain_table do
@@ -179,13 +183,13 @@ describe ChronoModel::Adapter do
179
183
  adapter.add_index table, [:test], :name => 'test_index'
180
184
  end
181
185
 
182
- it { should_not have_temporal_index 'foobar_index', %w( foo bar ) }
183
- it { should_not have_history_index 'foobar_index', %w( foo bar ) }
184
- it { should_not have_temporal_index 'test_index', %w( test ) }
185
- it { should_not have_history_index 'test_index', %w( test ) }
186
+ it { is_expected.to_not have_temporal_index 'foobar_index', %w( foo bar ) }
187
+ it { is_expected.to_not have_history_index 'foobar_index', %w( foo bar ) }
188
+ it { is_expected.to_not have_temporal_index 'test_index', %w( test ) }
189
+ it { is_expected.to_not have_history_index 'test_index', %w( test ) }
186
190
 
187
- it { should have_index 'foobar_index', %w( foo bar ) }
188
- it { should have_index 'test_index', %w( test ) }
191
+ it { is_expected.to have_index 'foobar_index', %w( foo bar ) }
192
+ it { is_expected.to have_index 'test_index', %w( test ) }
189
193
  end
190
194
  end
191
195
 
@@ -198,9 +202,9 @@ describe ChronoModel::Adapter do
198
202
  adapter.remove_index table, :name => 'test_index'
199
203
  end
200
204
 
201
- it { should_not have_temporal_index 'test_index', %w( test ) }
202
- it { should_not have_history_index 'test_index', %w( test ) }
203
- it { should_not have_index 'test_index', %w( test ) }
205
+ it { is_expected.to_not have_temporal_index 'test_index', %w( test ) }
206
+ it { is_expected.to_not have_history_index 'test_index', %w( test ) }
207
+ it { is_expected.to_not have_index 'test_index', %w( test ) }
204
208
  end
205
209
 
206
210
  with_plain_table do
@@ -211,9 +215,9 @@ describe ChronoModel::Adapter do
211
215
  adapter.remove_index table, :name => 'test_index'
212
216
  end
213
217
 
214
- it { should_not have_temporal_index 'test_index', %w( test ) }
215
- it { should_not have_history_index 'test_index', %w( test ) }
216
- it { should_not have_index 'test_index', %w( test ) }
218
+ it { is_expected.to_not have_temporal_index 'test_index', %w( test ) }
219
+ it { is_expected.to_not have_history_index 'test_index', %w( test ) }
220
+ it { is_expected.to_not have_index 'test_index', %w( test ) }
217
221
  end
218
222
  end
219
223
 
@@ -225,9 +229,9 @@ describe ChronoModel::Adapter do
225
229
  adapter.add_column table, :foobarbaz, :integer
226
230
  end
227
231
 
228
- it { should have_columns(extra_columns) }
229
- it { should have_temporal_columns(extra_columns) }
230
- it { should have_history_columns(extra_columns) }
232
+ it { is_expected.to have_columns(extra_columns) }
233
+ it { is_expected.to have_temporal_columns(extra_columns) }
234
+ it { is_expected.to have_history_columns(extra_columns) }
231
235
  end
232
236
 
233
237
  with_plain_table do
@@ -235,7 +239,7 @@ describe ChronoModel::Adapter do
235
239
  adapter.add_column table, :foobarbaz, :integer
236
240
  end
237
241
 
238
- it { should have_columns(extra_columns) }
242
+ it { is_expected.to have_columns(extra_columns) }
239
243
  end
240
244
  end
241
245
 
@@ -247,13 +251,13 @@ describe ChronoModel::Adapter do
247
251
  adapter.remove_column table, :foo
248
252
  end
249
253
 
250
- it { should have_columns(resulting_columns) }
251
- it { should have_temporal_columns(resulting_columns) }
252
- it { should have_history_columns(resulting_columns) }
254
+ it { is_expected.to have_columns(resulting_columns) }
255
+ it { is_expected.to have_temporal_columns(resulting_columns) }
256
+ it { is_expected.to have_history_columns(resulting_columns) }
253
257
 
254
- it { should_not have_columns([['foo', 'integer']]) }
255
- it { should_not have_temporal_columns([['foo', 'integer']]) }
256
- it { should_not have_history_columns([['foo', 'integer']]) }
258
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
259
+ it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
260
+ it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
257
261
  end
258
262
 
259
263
  with_plain_table do
@@ -261,8 +265,8 @@ describe ChronoModel::Adapter do
261
265
  adapter.remove_column table, :foo
262
266
  end
263
267
 
264
- it { should have_columns(resulting_columns) }
265
- it { should_not have_columns([['foo', 'integer']]) }
268
+ it { is_expected.to have_columns(resulting_columns) }
269
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
266
270
  end
267
271
  end
268
272
 
@@ -272,13 +276,13 @@ describe ChronoModel::Adapter do
272
276
  adapter.rename_column table, :foo, :taratapiatapioca
273
277
  end
274
278
 
275
- it { should_not have_columns([['foo', 'integer']]) }
276
- it { should_not have_temporal_columns([['foo', 'integer']]) }
277
- it { should_not have_history_columns([['foo', 'integer']]) }
279
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
280
+ it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
281
+ it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
278
282
 
279
- it { should have_columns([['taratapiatapioca', 'integer']]) }
280
- it { should have_temporal_columns([['taratapiatapioca', 'integer']]) }
281
- it { should have_history_columns([['taratapiatapioca', 'integer']]) }
283
+ it { is_expected.to have_columns([['taratapiatapioca', 'integer']]) }
284
+ it { is_expected.to have_temporal_columns([['taratapiatapioca', 'integer']]) }
285
+ it { is_expected.to have_history_columns([['taratapiatapioca', 'integer']]) }
282
286
  end
283
287
 
284
288
  with_plain_table do
@@ -286,8 +290,8 @@ describe ChronoModel::Adapter do
286
290
  adapter.rename_column table, :foo, :taratapiatapioca
287
291
  end
288
292
 
289
- it { should_not have_columns([['foo', 'integer']]) }
290
- it { should have_columns([['taratapiatapioca', 'integer']]) }
293
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
294
+ it { is_expected.to have_columns([['taratapiatapioca', 'integer']]) }
291
295
  end
292
296
  end
293
297
 
@@ -297,13 +301,13 @@ describe ChronoModel::Adapter do
297
301
  adapter.change_column table, :foo, :float
298
302
  end
299
303
 
300
- it { should_not have_columns([['foo', 'integer']]) }
301
- it { should_not have_temporal_columns([['foo', 'integer']]) }
302
- it { should_not have_history_columns([['foo', 'integer']]) }
304
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
305
+ it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
306
+ it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
303
307
 
304
- it { should have_columns([['foo', 'double precision']]) }
305
- it { should have_temporal_columns([['foo', 'double precision']]) }
306
- it { should have_history_columns([['foo', 'double precision']]) }
308
+ it { is_expected.to have_columns([['foo', 'double precision']]) }
309
+ it { is_expected.to have_temporal_columns([['foo', 'double precision']]) }
310
+ it { is_expected.to have_history_columns([['foo', 'double precision']]) }
307
311
  end
308
312
 
309
313
  with_plain_table do
@@ -311,8 +315,8 @@ describe ChronoModel::Adapter do
311
315
  adapter.change_column table, :foo, :float
312
316
  end
313
317
 
314
- it { should_not have_columns([['foo', 'integer']]) }
315
- it { should have_columns([['foo', 'double precision']]) }
318
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
319
+ it { is_expected.to have_columns([['foo', 'double precision']]) }
316
320
  end
317
321
  end
318
322
 
@@ -322,9 +326,9 @@ describe ChronoModel::Adapter do
322
326
  adapter.remove_column table, :foo
323
327
  end
324
328
 
325
- it { should_not have_columns([['foo', 'integer']]) }
326
- it { should_not have_temporal_columns([['foo', 'integer']]) }
327
- it { should_not have_history_columns([['foo', 'integer']]) }
329
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
330
+ it { is_expected.to_not have_temporal_columns([['foo', 'integer']]) }
331
+ it { is_expected.to_not have_history_columns([['foo', 'integer']]) }
328
332
  end
329
333
 
330
334
  with_plain_table do
@@ -332,7 +336,7 @@ describe ChronoModel::Adapter do
332
336
  adapter.remove_column table, :foo
333
337
  end
334
338
 
335
- it { should_not have_columns([['foo', 'integer']]) }
339
+ it { is_expected.to_not have_columns([['foo', 'integer']]) }
336
340
  end
337
341
  end
338
342
 
@@ -340,8 +344,8 @@ describe ChronoModel::Adapter do
340
344
  subject { adapter.column_definitions(table).map {|d| d.take(2)} }
341
345
 
342
346
  assert = proc do
343
- it { (subject & columns).should == columns }
344
- it { should include(['id', 'integer']) }
347
+ it { expect(subject & columns).to eq columns }
348
+ it { is_expected.to include(['id', 'integer']) }
345
349
  end
346
350
 
347
351
  with_temporal_table(&assert)
@@ -352,7 +356,7 @@ describe ChronoModel::Adapter do
352
356
  subject { adapter.primary_key(table) }
353
357
 
354
358
  assert = proc do
355
- it { should == 'id' }
359
+ it { is_expected.to eq 'id' }
356
360
  end
357
361
 
358
362
  with_temporal_table(&assert)
@@ -368,8 +372,8 @@ describe ChronoModel::Adapter do
368
372
  adapter.add_index table, [:bar, :baz], :name => 'bar_index'
369
373
  end
370
374
 
371
- it { subject.map(&:name).should =~ %w( foo_index bar_index ) }
372
- it { subject.map(&:columns).should =~ [['foo'], ['bar', 'baz']] }
375
+ it { expect(subject.map(&:name)).to match_array %w( foo_index bar_index ) }
376
+ it { expect(subject.map(&:columns)).to match_array [['foo'], ['bar', 'baz']] }
373
377
  end
374
378
 
375
379
  with_temporal_table(&assert)
@@ -384,32 +388,32 @@ describe ChronoModel::Adapter do
384
388
  context 'with nesting' do
385
389
 
386
390
  it 'saves the schema at each recursion' do
387
- should be_in_schema(:default)
391
+ is_expected.to be_in_schema(:default)
388
392
 
389
- adapter.on_schema('test_1') { should be_in_schema('test_1')
390
- adapter.on_schema('test_2') { should be_in_schema('test_2')
391
- adapter.on_schema('test_3') { should be_in_schema('test_3')
393
+ adapter.on_schema('test_1') { is_expected.to be_in_schema('test_1')
394
+ adapter.on_schema('test_2') { is_expected.to be_in_schema('test_2')
395
+ adapter.on_schema('test_3') { is_expected.to be_in_schema('test_3')
392
396
  }
393
- should be_in_schema('test_2')
397
+ is_expected.to be_in_schema('test_2')
394
398
  }
395
- should be_in_schema('test_1')
399
+ is_expected.to be_in_schema('test_1')
396
400
  }
397
401
 
398
- should be_in_schema(:default)
402
+ is_expected.to be_in_schema(:default)
399
403
  end
400
404
 
401
405
  end
402
406
 
403
407
  context 'without nesting' do
404
408
  it 'ignores recursive calls' do
405
- should be_in_schema(:default)
409
+ is_expected.to be_in_schema(:default)
406
410
 
407
- adapter.on_schema('test_1', false) { should be_in_schema('test_1')
408
- adapter.on_schema('test_2', false) { should be_in_schema('test_1')
409
- adapter.on_schema('test_3', false) { should be_in_schema('test_1')
411
+ adapter.on_schema('test_1', false) { is_expected.to be_in_schema('test_1')
412
+ adapter.on_schema('test_2', false) { is_expected.to be_in_schema('test_1')
413
+ adapter.on_schema('test_3', false) { is_expected.to be_in_schema('test_1')
410
414
  } } }
411
415
 
412
- should be_in_schema(:default)
416
+ is_expected.to be_in_schema(:default)
413
417
  end
414
418
  end
415
419
  end
@@ -445,8 +449,8 @@ describe ChronoModel::Adapter do
445
449
  end
446
450
 
447
451
  it { expect { insert }.to_not raise_error }
448
- it { count(current).should == 2 }
449
- it { count(history).should == 2 }
452
+ it { expect(count(current)).to eq 2 }
453
+ it { expect(count(history)).to eq 2 }
450
454
  end
451
455
 
452
456
  context 'when failing' do
@@ -458,9 +462,9 @@ describe ChronoModel::Adapter do
458
462
  SQL
459
463
  end
460
464
 
461
- it { expect { insert }.to raise_error }
462
- it { count(current).should == 2 } # Because the previous
463
- it { count(history).should == 2 } # records are preserved
465
+ it { expect { insert }.to raise_error(ActiveRecord::StatementInvalid) }
466
+ it { expect(count(current)).to eq 2 } # Because the previous
467
+ it { expect(count(history)).to eq 2 } # records are preserved
464
468
  end
465
469
 
466
470
  context 'after a failure' do
@@ -474,10 +478,10 @@ describe ChronoModel::Adapter do
474
478
 
475
479
  it { expect { insert }.to_not raise_error }
476
480
 
477
- it { count(current).should == 4 }
478
- it { count(history).should == 4 }
481
+ it { expect(count(current)).to eq 4 }
482
+ it { expect(count(history)).to eq 4 }
479
483
 
480
- it { ids(current).should == ids(history) }
484
+ it { expect(ids(current)).to eq ids(history) }
481
485
  end
482
486
  end
483
487
 
@@ -503,7 +507,7 @@ describe ChronoModel::Adapter do
503
507
  end
504
508
 
505
509
  it { expect { insert }.to_not raise_error }
506
- it { insert; select.uniq.should == ['default-value'] }
510
+ it { insert; expect(select.uniq).to eq ['default-value'] }
507
511
  end
508
512
 
509
513
  context 'redundant UPDATEs' do
@@ -528,8 +532,8 @@ describe ChronoModel::Adapter do
528
532
  adapter.drop_table table
529
533
  end
530
534
 
531
- it { count(current).should == 1 }
532
- it { count(history).should == 2 }
535
+ it { expect(count(current)).to eq 1 }
536
+ it { expect(count(history)).to eq 2 }
533
537
 
534
538
  end
535
539
 
@@ -537,7 +541,7 @@ describe ChronoModel::Adapter do
537
541
  before :all do
538
542
  adapter.create_table table, :temporal => true do |t|
539
543
  t.string 'test'
540
- t.timestamps
544
+ t.timestamps null: false
541
545
  end
542
546
 
543
547
  adapter.execute <<-SQL
@@ -563,8 +567,8 @@ describe ChronoModel::Adapter do
563
567
  adapter.drop_table table
564
568
  end
565
569
 
566
- it { count(current).should == 1 }
567
- it { count(history).should == 2 }
570
+ it { expect(count(current)).to eq 1 }
571
+ it { expect(count(history)).to eq 2 }
568
572
  end
569
573
 
570
574
  context 'selective journaled fields' do
@@ -593,8 +597,8 @@ describe ChronoModel::Adapter do
593
597
  adapter.drop_table table
594
598
  end
595
599
 
596
- it { count(current).should == 1 }
597
- it { count(history).should == 1 }
600
+ it { expect(count(current)).to eq 1 }
601
+ it { expect(count(history)).to eq 1 }
598
602
 
599
603
  it 'preserves options upon column change'
600
604
  it 'changes option upon table change'