chrono_model 0.8.2 → 0.9.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.
@@ -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'