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.
@@ -14,11 +14,11 @@ describe 'JSON equality operator' do
14
14
  ChronoModel::Json.drop
15
15
  end
16
16
 
17
- it { adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":1}'::json ]).should == 't' }
18
- it { adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a" : 1}'::json ]).should == 't' }
19
- it { adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":2}'::json ]).should == 'f' }
20
- it { adapter.select_value(%[ SELECT '{"a":1,"b":2}'::json = '{"b":2,"a":1}'::json ]).should == 't' }
21
- it { adapter.select_value(%[ SELECT '{"a":1,"b":2,"x":{"c":4,"d":5}}'::json = '{"b":2, "x": { "d": 5, "c": 4}, "a":1}'::json ]).should == 't' }
17
+ it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":1}'::json ])).to eq 't' }
18
+ it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a" : 1}'::json ])).to eq 't' }
19
+ it { expect(adapter.select_value(%[ SELECT '{"a":1}'::json = '{"a":2}'::json ])).to eq 'f' }
20
+ it { expect(adapter.select_value(%[ SELECT '{"a":1,"b":2}'::json = '{"b":2,"a":1}'::json ])).to eq 't' }
21
+ it { expect(adapter.select_value(%[ SELECT '{"a":1,"b":2,"x":{"c":4,"d":5}}'::json = '{"b":2, "x": { "d": 5, "c": 4}, "a":1}'::json ])).to eq 't' }
22
22
 
23
23
  context 'on a temporal table' do
24
24
  before :all do
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
2
2
  #
3
+ require 'codeclimate-test-reporter'
4
+ CodeClimate::TestReporter.start
5
+
3
6
  require 'chrono_model'
4
7
 
5
8
  require 'support/connection'
@@ -9,8 +12,6 @@ require 'support/matchers/column'
9
12
  require 'support/matchers/index'
10
13
 
11
14
  RSpec.configure do |config|
12
- config.treat_symbols_as_metadata_keys_with_true_values = true
13
-
14
15
  config.include(ChronoTest::Matchers::Schema)
15
16
  config.include(ChronoTest::Matchers::Table)
16
17
  config.include(ChronoTest::Matchers::Column)
@@ -1,7 +1,7 @@
1
- require 'ostruct'
2
-
3
1
  module ChronoTest::Matchers
4
2
  class Base
3
+ include ActiveRecord::Sanitization::ClassMethods
4
+
5
5
  attr_reader :table
6
6
 
7
7
  def matches?(table)
@@ -15,6 +15,7 @@ module ChronoTest::Matchers
15
15
  end
16
16
 
17
17
  protected
18
+
18
19
  def connection
19
20
  ChronoTest.connection
20
21
  end
@@ -46,13 +47,9 @@ module ChronoTest::Matchers
46
47
  end
47
48
 
48
49
  private
49
- FooColumn = OpenStruct.new(:name => '')
50
-
51
50
  def exec_query(sql, binds, name)
52
- binds.map! do |col, val|
53
- val ? [col, val] : [FooColumn, col]
54
- end
55
- connection.exec_query(sql, name, binds)
51
+ sql = sanitize_sql_array([ sql, *Array.wrap(binds) ])
52
+ connection.exec_query(sql, name)
56
53
  end
57
54
  end
58
55
 
@@ -21,25 +21,38 @@ module ChronoTest::Matchers
21
21
  @matches.values.all?
22
22
  end
23
23
 
24
- def failure_message_for_should
25
- "expected #{@schema}.#{table} to have ".tap do |message|
26
- message << @matches.map do |(name, type), match|
27
- "a #{name}(#{type}) column" unless match
28
- end.compact.to_sentence
29
- end
24
+ def failure_message
25
+ message_matches("expected #{@schema}.#{table} to have")
26
+ end
27
+
28
+ def failure_message_when_negated
29
+ message_matches("expected #{@schema}.#{table} to not have")
30
30
  end
31
31
 
32
32
  protected
33
33
  def has_column?(name, type)
34
+ column_type(name) == [name, type]
35
+ end
36
+
37
+ def column_type(name)
34
38
  table = "#{@schema}.#{self.table}"
35
39
 
36
- select_rows(<<-SQL, [table, name], 'Check column').first == [name, type]
40
+ select_rows(<<-SQL, [table, name], 'Check column').first
37
41
  SELECT attname, FORMAT_TYPE(atttypid, atttypmod)
38
42
  FROM pg_attribute
39
- WHERE attrelid = $1::regclass::oid
40
- AND attname = $2
43
+ WHERE attrelid = ?::regclass::oid
44
+ AND attname = ?
41
45
  SQL
42
46
  end
47
+
48
+ private
49
+ def message_matches(message)
50
+ (message << ' ').tap do |message|
51
+ message << @matches.map do |(name, type), match|
52
+ "a #{name}(#{type}) column" unless match
53
+ end.compact.to_sentence
54
+ end
55
+ end
43
56
  end
44
57
 
45
58
  def have_columns(*args)
@@ -24,18 +24,22 @@ module ChronoTest::Matchers
24
24
  JOIN pg_class i ON i.oid = d.indexrelid
25
25
  JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(d.indkey)
26
26
  WHERE i.relkind = 'i'
27
- AND t.relname = $1
28
- AND i.relname = $2
27
+ AND t.relname = ?
28
+ AND i.relname = ?
29
29
  AND i.relnamespace = (
30
- SELECT oid FROM pg_namespace WHERE nspname = $3
30
+ SELECT oid FROM pg_namespace WHERE nspname = ?
31
31
  )
32
32
  ORDER BY a.attname
33
33
  SQL
34
34
  end
35
35
 
36
- def failure_message_for_should
36
+ def failure_message
37
37
  "expected #{schema}.#{table} to have a #{name} index on #{columns}"
38
38
  end
39
+
40
+ def failure_message_when_negated
41
+ "expected #{schema}.#{table} to not have a #{name} index on #{columns}"
42
+ end
39
43
  end
40
44
 
41
45
  def have_index(*args)
@@ -14,10 +14,14 @@ module ChronoTest::Matchers
14
14
  'be in schema'
15
15
  end
16
16
 
17
- def failure_message_for_should
17
+ def failure_message
18
18
  "expected to be in schema #@expected, but was in #@current"
19
19
  end
20
20
 
21
+ def failure_message_when_negated
22
+ "expected to be in schema #@current, but was in #@expected"
23
+ end
24
+
21
25
  def matches?(*)
22
26
  @current = select_value(<<-SQL, [], 'Current schema')
23
27
  SHOW search_path
@@ -18,8 +18,8 @@ module ChronoTest::Matchers
18
18
  FROM pg_class c
19
19
  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
20
20
  WHERE c.relkind = '#{kind}'
21
- AND c.relname = $1
22
- AND n.nspname = $2
21
+ AND c.relname = ?
22
+ AND n.nspname = ?
23
23
  )
24
24
  SQL
25
25
  end
@@ -39,9 +39,13 @@ module ChronoTest::Matchers
39
39
  'be in the public schema'
40
40
  end
41
41
 
42
- def failure_message_for_should
42
+ def failure_message
43
43
  "expected #{table} to exist in the #{public_schema} schema"
44
44
  end
45
+
46
+ def failure_message_when_negated
47
+ "expected #{table} to not exist in the #{public_schema} schema"
48
+ end
45
49
  end
46
50
 
47
51
  def have_public_backing
@@ -63,9 +67,13 @@ module ChronoTest::Matchers
63
67
  'be in the temporal schema'
64
68
  end
65
69
 
66
- def failure_message_for_should
70
+ def failure_message
67
71
  "expected #{table} to exist in the #{temporal_schema} schema"
68
72
  end
73
+
74
+ def failure_message_when_negated
75
+ "expected #{table} to not exist in the #{temporal_schema} schema"
76
+ end
69
77
  end
70
78
 
71
79
  def have_temporal_backing
@@ -91,7 +99,7 @@ module ChronoTest::Matchers
91
99
  'be in history schema'
92
100
  end
93
101
 
94
- def failure_message_for_should
102
+ def failure_message
95
103
  "expected #{table} ".tap do |message|
96
104
  message << [
97
105
  ("to exist in the #{history_schema} schema" unless @existance),
@@ -102,6 +110,17 @@ module ChronoTest::Matchers
102
110
  end
103
111
  end
104
112
 
113
+ def failure_message_when_negated
114
+ "expected #{table} ".tap do |message|
115
+ message << [
116
+ ("to not exist in the #{history_schema} schema" if @existance),
117
+ ("to not inherit from #{temporal_schema}.#{table}" if @inheritance),
118
+ ("to not have a timeline consistency constraint" if @constraint),
119
+ ("to not have history indexes" if @indexes)
120
+ ].compact.to_sentence
121
+ end
122
+ end
123
+
105
124
  private
106
125
  def table_exists?
107
126
  @existance = relation_exists? :in => history_schema
@@ -113,8 +132,8 @@ module ChronoTest::Matchers
113
132
  @inheritance = select_value(<<-SQL, binds, 'Check inheritance') == 't'
114
133
  SELECT EXISTS (
115
134
  SELECT 1 FROM pg_catalog.pg_inherits
116
- WHERE inhrelid = $1::regclass::oid
117
- AND inhparent = $2::regclass::oid
135
+ WHERE inhrelid = ?::regclass::oid
136
+ AND inhparent = ?::regclass::oid
118
137
  )
119
138
  SQL
120
139
  end
@@ -124,8 +143,8 @@ module ChronoTest::Matchers
124
143
 
125
144
  indexes = select_values(<<-SQL, binds, 'Check history indexes')
126
145
  SELECT indexdef FROM pg_indexes
127
- WHERE schemaname = $1
128
- AND tablename = $2
146
+ WHERE schemaname = ?
147
+ AND tablename = ?
129
148
  SQL
130
149
 
131
150
  fqtn = [history_schema, table].join('.')
@@ -146,26 +165,26 @@ module ChronoTest::Matchers
146
165
  end
147
166
 
148
167
  def has_consistency_constraint?
149
- binds = [
150
- connection.timeline_consistency_constraint_name(table), # conname
151
- history_schema, # connamespace
152
- [history_schema, table].join('.'), # conrelid, attrelid
153
- connection.primary_key(table) # attnum
154
- ]
168
+ binds = {
169
+ conname: connection.timeline_consistency_constraint_name(table),
170
+ connamespace: history_schema,
171
+ conrelid: [history_schema, table].join('.'),
172
+ attname: connection.primary_key(table)
173
+ }
155
174
 
156
175
  @constraint = select_value(<<-SQL, binds, 'Check Consistency Constraint') == 't'
157
176
  SELECT EXISTS (
158
177
  SELECT 1 FROM pg_catalog.pg_constraint
159
- WHERE conname = $1
178
+ WHERE conname = :conname
160
179
  AND contype = 'x'
161
- AND conrelid = $3::regclass
180
+ AND conrelid = :conrelid::regclass
162
181
  AND connamespace = (
163
- SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = $2
182
+ SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = :connamespace
164
183
  )
165
184
  AND conkey = (
166
185
  SELECT array_agg(attnum) FROM pg_catalog.pg_attribute
167
- WHERE attname IN ($4, 'validity')
168
- AND attrelid = $3::regclass
186
+ WHERE attname IN (:attname, 'validity')
187
+ AND attrelid = :conrelid::regclass
169
188
  )
170
189
  )
171
190
  SQL
@@ -192,7 +211,7 @@ module ChronoTest::Matchers
192
211
  'be an updatable view'
193
212
  end
194
213
 
195
- def failure_message_for_should
214
+ def failure_message
196
215
  "expected #{table} ".tap do |message|
197
216
  message << [
198
217
  ("to exist in the #{public_schema} schema" unless @existance ),
@@ -204,6 +223,18 @@ module ChronoTest::Matchers
204
223
  end
205
224
  end
206
225
 
226
+ def failure_message_when_negated
227
+ "expected #{table} ".tap do |message|
228
+ message << [
229
+ ("to not exist in the #{public_schema} schema" if @existance ),
230
+ ('to not be an updatable view' if @updatable ),
231
+ ('to not have an INSERT trigger' if @insert_trigger),
232
+ ('to not have an UPDATE trigger' if @update_trigger),
233
+ ('to not have a DELETE trigger' if @delete_trigger)
234
+ ].compact.to_sentence
235
+ end
236
+ end
237
+
207
238
  private
208
239
  def view_exists?
209
240
  @existance = relation_exists? :in => public_schema, :kind => :view
@@ -214,7 +245,7 @@ module ChronoTest::Matchers
214
245
 
215
246
  @updatable = select_value(<<-SQL, binds, 'Check updatable') == 'YES'
216
247
  SELECT is_updatable FROM information_schema.views
217
- WHERE table_schema = $1 AND table_name = $2
248
+ WHERE table_schema = ? AND table_name = ?
218
249
  SQL
219
250
  end
220
251
 
@@ -224,8 +255,8 @@ module ChronoTest::Matchers
224
255
  FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c, pg_catalog.pg_namespace n
225
256
  WHERE t.tgrelid = c.relfilenode
226
257
  AND n.oid = c.relnamespace
227
- AND n.nspname = $1
228
- AND c.relname = $2;
258
+ AND n.nspname = ?
259
+ AND c.relname = ?;
229
260
  SQL
230
261
 
231
262
  @insert_trigger = triggers.include? 'chronomodel_insert'
@@ -25,85 +25,118 @@ describe ChronoModel::TimeMachine do
25
25
  baz = Baz.create :name => 'baz', :bar => bar
26
26
 
27
27
  describe '.chrono?' do
28
+ subject { model.chrono? }
29
+
28
30
  context 'on a temporal model' do
29
- subject { Foo }
30
- it { should be_chrono }
31
+ let(:model) { Foo }
32
+ it { is_expected.to be(true) }
33
+ end
34
+
35
+ context 'on a plain model' do
36
+ let(:model) { Plain }
37
+ it { is_expected.to be(false) }
38
+ end
39
+ end
40
+
41
+ describe '.history?' do
42
+ subject { model.history? }
43
+
44
+ context 'on a temporal parent model' do
45
+ let(:model) { Foo }
46
+ it { is_expected.to be(false) }
47
+ end
48
+
49
+ context 'on a temporal history model' do
50
+ let(:model) { Foo::History }
51
+ it { is_expected.to be(true) }
31
52
  end
32
53
 
33
54
  context 'on a plain model' do
34
- subject { Plain }
35
- it { should_not be_chrono }
55
+ let(:model) { Plain }
56
+ it { expect { subject }.to raise_error(NoMethodError) }
36
57
  end
37
58
  end
38
59
 
60
+ describe '.descendants' do
61
+ subject { Element.descendants }
62
+ it { is_expected.to_not include(Element::History) }
63
+ it { is_expected.to include(Publication) }
64
+ end
65
+
66
+ describe '.descendants_with_history' do
67
+ subject { Element.descendants_with_history }
68
+ it { is_expected.to include(Element::History) }
69
+ it { is_expected.to include(Publication) }
70
+ end
71
+
39
72
  # Specs start here
40
73
  #
41
74
  describe '.chrono_models' do
42
75
  subject { ChronoModel::TimeMachine.chrono_models }
43
76
 
44
- it { should == {
77
+ it { is_expected.to eq(
45
78
  'foos' => Foo::History,
46
79
  'defoos' => Defoo::History,
47
80
  'bars' => Bar::History,
48
81
  'elements' => Element::History
49
- } }
82
+ ) }
50
83
  end
51
84
 
52
85
  describe '#as_of' do
53
86
  describe 'accepts a Time instance' do
54
- it { foo.as_of(Time.now).name.should == 'new foo' }
55
- it { bar.as_of(Time.now).name.should == 'new bar' }
87
+ it { expect(foo.as_of(Time.now).name).to eq 'new foo' }
88
+ it { expect(bar.as_of(Time.now).name).to eq 'new bar' }
56
89
  end
57
90
 
58
91
  describe 'ignores time zones' do
59
- it { foo.as_of(Time.now.in_time_zone('America/Havana')).name.should == 'new foo' }
60
- it { bar.as_of(Time.now.in_time_zone('America/Havana')).name.should == 'new bar' }
92
+ it { expect(foo.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new foo' }
93
+ it { expect(bar.as_of(Time.now.in_time_zone('America/Havana')).name).to eq 'new bar' }
61
94
  end
62
95
 
63
96
  describe 'returns records as they were before' do
64
- it { foo.as_of(foo.ts[0]).name.should == 'foo' }
65
- it { foo.as_of(foo.ts[1]).name.should == 'foo bar' }
66
- it { foo.as_of(foo.ts[2]).name.should == 'new foo' }
97
+ it { expect(foo.as_of(foo.ts[0]).name).to eq 'foo' }
98
+ it { expect(foo.as_of(foo.ts[1]).name).to eq 'foo bar' }
99
+ it { expect(foo.as_of(foo.ts[2]).name).to eq 'new foo' }
67
100
 
68
- it { bar.as_of(bar.ts[0]).name.should == 'bar' }
69
- it { bar.as_of(bar.ts[1]).name.should == 'foo bar' }
70
- it { bar.as_of(bar.ts[2]).name.should == 'bar bar' }
71
- it { bar.as_of(bar.ts[3]).name.should == 'new bar' }
101
+ it { expect(bar.as_of(bar.ts[0]).name).to eq 'bar' }
102
+ it { expect(bar.as_of(bar.ts[1]).name).to eq 'foo bar' }
103
+ it { expect(bar.as_of(bar.ts[2]).name).to eq 'bar bar' }
104
+ it { expect(bar.as_of(bar.ts[3]).name).to eq 'new bar' }
72
105
  end
73
106
 
74
107
  describe 'takes care of associated records' do
75
- it { foo.as_of(foo.ts[0]).bars.should == [] }
76
- it { foo.as_of(foo.ts[1]).bars.should == [] }
77
- it { foo.as_of(foo.ts[2]).bars.should == [bar] }
108
+ it { expect(foo.as_of(foo.ts[0]).bars).to eq [] }
109
+ it { expect(foo.as_of(foo.ts[1]).bars).to eq [] }
110
+ it { expect(foo.as_of(foo.ts[2]).bars).to eq [bar] }
78
111
 
79
- it { foo.as_of(foo.ts[2]).bars.first.name.should == 'foo bar' }
112
+ it { expect(foo.as_of(foo.ts[2]).bars.first.name).to eq 'foo bar' }
80
113
 
81
114
 
82
- it { foo.as_of(bar.ts[0]).bars.should == [bar] }
83
- it { foo.as_of(bar.ts[1]).bars.should == [bar] }
84
- it { foo.as_of(bar.ts[2]).bars.should == [bar] }
85
- it { foo.as_of(bar.ts[3]).bars.should == [bar] }
115
+ it { expect(foo.as_of(bar.ts[0]).bars).to eq [bar] }
116
+ it { expect(foo.as_of(bar.ts[1]).bars).to eq [bar] }
117
+ it { expect(foo.as_of(bar.ts[2]).bars).to eq [bar] }
118
+ it { expect(foo.as_of(bar.ts[3]).bars).to eq [bar] }
86
119
 
87
- it { foo.as_of(bar.ts[0]).bars.first.name.should == 'bar' }
88
- it { foo.as_of(bar.ts[1]).bars.first.name.should == 'foo bar' }
89
- it { foo.as_of(bar.ts[2]).bars.first.name.should == 'bar bar' }
90
- it { foo.as_of(bar.ts[3]).bars.first.name.should == 'new bar' }
120
+ it { expect(foo.as_of(bar.ts[0]).bars.first.name).to eq 'bar' }
121
+ it { expect(foo.as_of(bar.ts[1]).bars.first.name).to eq 'foo bar' }
122
+ it { expect(foo.as_of(bar.ts[2]).bars.first.name).to eq 'bar bar' }
123
+ it { expect(foo.as_of(bar.ts[3]).bars.first.name).to eq 'new bar' }
91
124
 
92
125
 
93
- it { bar.as_of(bar.ts[0]).foo.should == foo }
94
- it { bar.as_of(bar.ts[1]).foo.should == foo }
95
- it { bar.as_of(bar.ts[2]).foo.should == foo }
96
- it { bar.as_of(bar.ts[3]).foo.should == foo }
126
+ it { expect(bar.as_of(bar.ts[0]).foo).to eq foo }
127
+ it { expect(bar.as_of(bar.ts[1]).foo).to eq foo }
128
+ it { expect(bar.as_of(bar.ts[2]).foo).to eq foo }
129
+ it { expect(bar.as_of(bar.ts[3]).foo).to eq foo }
97
130
 
98
- it { bar.as_of(bar.ts[0]).foo.name.should == 'foo bar' }
99
- it { bar.as_of(bar.ts[1]).foo.name.should == 'foo bar' }
100
- it { bar.as_of(bar.ts[2]).foo.name.should == 'new foo' }
101
- it { bar.as_of(bar.ts[3]).foo.name.should == 'new foo' }
131
+ it { expect(bar.as_of(bar.ts[0]).foo.name).to eq 'foo bar' }
132
+ it { expect(bar.as_of(bar.ts[1]).foo.name).to eq 'foo bar' }
133
+ it { expect(bar.as_of(bar.ts[2]).foo.name).to eq 'new foo' }
134
+ it { expect(bar.as_of(bar.ts[3]).foo.name).to eq 'new foo' }
102
135
  end
103
136
 
104
137
  it 'doesn\'t raise RecordNotFound when no history records are found' do
105
138
  expect { foo.as_of(1.minute.ago) }.to_not raise_error
106
- foo.as_of(1.minute.ago).should be_nil
139
+ expect(foo.as_of(1.minute.ago)).to be(nil)
107
140
  end
108
141
 
109
142
 
@@ -118,164 +151,161 @@ describe ChronoModel::TimeMachine do
118
151
  hidden = ts_eval { Defoo.create! :name => 'hidden 1', :active => false }
119
152
  ts_eval(hidden) { update_attributes! :name => 'hidden 2' }
120
153
 
121
- it { Defoo.as_of(active.ts[0]).map(&:name).should == ['active 1'] }
122
- it { Defoo.as_of(active.ts[1]).map(&:name).should == ['active 2'] }
123
- it { Defoo.as_of(hidden.ts[0]).map(&:name).should == ['active 2'] }
124
- it { Defoo.as_of(hidden.ts[1]).map(&:name).should == ['active 2'] }
154
+ it { expect(Defoo.as_of(active.ts[0]).map(&:name)).to eq ['active 1'] }
155
+ it { expect(Defoo.as_of(active.ts[1]).map(&:name)).to eq ['active 2'] }
156
+ it { expect(Defoo.as_of(hidden.ts[0]).map(&:name)).to eq ['active 2'] }
157
+ it { expect(Defoo.as_of(hidden.ts[1]).map(&:name)).to eq ['active 2'] }
125
158
 
126
- it { Defoo.unscoped.as_of(active.ts[0]).map(&:name).should == ['active 1'] }
127
- it { Defoo.unscoped.as_of(active.ts[1]).map(&:name).should == ['active 2'] }
128
- it { Defoo.unscoped.as_of(hidden.ts[0]).map(&:name).should == ['active 2', 'hidden 1'] }
129
- it { Defoo.unscoped.as_of(hidden.ts[1]).map(&:name).should == ['active 2', 'hidden 2'] }
159
+ it { expect(Defoo.unscoped.as_of(active.ts[0]).map(&:name)).to eq ['active 1'] }
160
+ it { expect(Defoo.unscoped.as_of(active.ts[1]).map(&:name)).to eq ['active 2'] }
161
+ it { expect(Defoo.unscoped.as_of(hidden.ts[0]).map(&:name)).to eq ['active 2', 'hidden 1'] }
162
+ it { expect(Defoo.unscoped.as_of(hidden.ts[1]).map(&:name)).to eq ['active 2', 'hidden 2'] }
130
163
  end
131
164
 
132
165
  describe 'proxies from non-temporal models to temporal ones' do
133
- it { baz.as_of(bar.ts[0]).name.should == 'baz' }
134
- it { baz.as_of(bar.ts[1]).name.should == 'baz' }
135
- it { baz.as_of(bar.ts[2]).name.should == 'baz' }
136
- it { baz.as_of(bar.ts[3]).name.should == 'baz' }
166
+ it { expect(baz.as_of(bar.ts[0]).name).to eq 'baz' }
167
+ it { expect(baz.as_of(bar.ts[1]).name).to eq 'baz' }
168
+ it { expect(baz.as_of(bar.ts[2]).name).to eq 'baz' }
169
+ it { expect(baz.as_of(bar.ts[3]).name).to eq 'baz' }
137
170
 
138
- it { baz.as_of(bar.ts[0]).bar.name.should == 'bar' }
139
- it { baz.as_of(bar.ts[1]).bar.name.should == 'foo bar' }
140
- it { baz.as_of(bar.ts[2]).bar.name.should == 'bar bar' }
141
- it { baz.as_of(bar.ts[3]).bar.name.should == 'new bar' }
171
+ it { expect(baz.as_of(bar.ts[0]).bar.name).to eq 'bar' }
172
+ it { expect(baz.as_of(bar.ts[1]).bar.name).to eq 'foo bar' }
173
+ it { expect(baz.as_of(bar.ts[2]).bar.name).to eq 'bar bar' }
174
+ it { expect(baz.as_of(bar.ts[3]).bar.name).to eq 'new bar' }
142
175
 
143
- it { baz.as_of(bar.ts[0]).bar.foo.name.should == 'foo bar' }
144
- it { baz.as_of(bar.ts[1]).bar.foo.name.should == 'foo bar' }
145
- it { baz.as_of(bar.ts[2]).bar.foo.name.should == 'new foo' }
146
- it { baz.as_of(bar.ts[3]).bar.foo.name.should == 'new foo' }
176
+ it { expect(baz.as_of(bar.ts[0]).bar.foo.name).to eq 'foo bar' }
177
+ it { expect(baz.as_of(bar.ts[1]).bar.foo.name).to eq 'foo bar' }
178
+ it { expect(baz.as_of(bar.ts[2]).bar.foo.name).to eq 'new foo' }
179
+ it { expect(baz.as_of(bar.ts[3]).bar.foo.name).to eq 'new foo' }
147
180
  end
148
181
  end
149
182
 
150
183
  describe '#history' do
151
184
  describe 'returns historical instances' do
152
- it { foo.history.should have(3).entries }
153
- it { foo.history.map(&:name).should == ['foo', 'foo bar', 'new foo'] }
185
+ it { expect(foo.history.size).to eq(3) }
186
+ it { expect(foo.history.map(&:name)).to eq ['foo', 'foo bar', 'new foo'] }
154
187
 
155
- it { bar.history.should have(4).entries }
156
- it { bar.history.map(&:name).should == ['bar', 'foo bar', 'bar bar', 'new bar'] }
188
+ it { expect(bar.history.size).to eq(4) }
189
+ it { expect(bar.history.map(&:name)).to eq ['bar', 'foo bar', 'bar bar', 'new bar'] }
157
190
  end
158
191
 
159
192
  describe 'does not return read only records' do
160
- it { foo.history.all?(&:readonly?).should_not be_true }
161
- it { bar.history.all?(&:readonly?).should_not be_true }
193
+ it { expect(foo.history.all?(&:readonly?)).to be(false) }
194
+ it { expect(bar.history.all?(&:readonly?)).to be(false) }
162
195
  end
163
196
 
164
197
  describe 'takes care of associated records' do
165
198
  subject { foo.history.map {|f| f.bars.first.try(:name)} }
166
- it { should == [nil, 'foo bar', 'new bar'] }
199
+ it { is_expected.to eq [nil, 'foo bar', 'new bar'] }
167
200
  end
168
201
 
169
202
  describe 'does not return read only associated records' do
170
- it { foo.history[2].bars.all?(&:readonly?).should_not be_true }
171
- it { bar.history.all? {|b| b.foo.readonly?}.should_not be_true }
203
+ it { expect(foo.history[2].bars.all?(&:readonly?)).to_not be(true) }
204
+ it { expect(bar.history.all? {|b| b.foo.readonly?}).to_not be(true) }
172
205
  end
173
206
 
174
207
  describe 'allows a custom select list' do
175
- it { foo.history.select(:id).first.attributes.keys.should == %w( id as_of_time ) }
208
+ it { expect(foo.history.select(:id).first.attributes.keys).to eq %w( id ) }
176
209
  end
177
210
 
178
211
  describe 'does not add as_of_time when there are aggregates' do
179
- it { foo.history.select('max (id)').to_sql.should_not =~ /as_of_time/ } # The id is automatically added by ActiveRecord
180
- it { foo.history.select('max(id) as foo, min(id) as bar').order(nil).first.attributes.keys.should == %w( foo bar id ) }
212
+ it { expect(foo.history.select('max(id)').to_sql).to_not match /as_of_time/ }
213
+
214
+ it { expect(foo.history.except(:order).select('max(id) as foo, min(id) as bar').group('id').first.attributes.keys).to eq %w( id foo bar ) }
181
215
  end
182
216
 
183
217
  context 'with STI models' do
184
218
  pub = ts_eval { Publication.create! :title => 'wrong title' }
185
219
  ts_eval(pub) { update_attributes! :title => 'correct title' }
186
220
 
187
- it { pub.history.map(&:title).should == ['wrong title', 'correct title'] }
188
- end
189
-
190
- describe 'allows a custom order list' do
191
- it { expect { foo.history.order('id') }.to_not raise_error }
192
- it { foo.history.order('id').to_sql.should =~ /order by id/i }
221
+ it { expect(pub.history.map(&:title)).to eq ['wrong title', 'correct title'] }
193
222
  end
194
223
 
195
224
  context '.sorted' do
196
225
  describe 'orders by recorded_at, hid' do
197
- it { foo.history.sorted.to_sql.should =~ /order by .+"recorded_at", .+"hid"/i }
226
+ it { expect(foo.history.sorted.to_sql).to match /order by .+"recorded_at", .+"hid"/i }
198
227
  end
199
228
  end
200
-
201
229
  end
202
230
 
203
231
  describe '#pred' do
204
232
  context 'on the first history entry' do
205
233
  subject { foo.history.first.pred }
206
- it { should be_nil }
234
+ it { is_expected.to be(nil) }
207
235
  end
208
236
 
209
237
  context 'on the second history entry' do
210
238
  subject { foo.history.second.pred }
211
- it { should == foo.history.first }
239
+ it { is_expected.to eq foo.history.first }
212
240
  end
213
241
 
214
242
  context 'on the last history entry' do
215
243
  subject { foo.history.last.pred }
216
- it { should == foo.history[foo.history.size - 2] }
244
+ it { is_expected.to eq foo.history[foo.history.size - 2] }
217
245
  end
218
246
  end
219
247
 
220
248
  describe '#succ' do
221
249
  context 'on the first history entry' do
222
250
  subject { foo.history.first.succ }
223
- it { should == foo.history.second }
251
+ it { is_expected.to eq foo.history.second }
224
252
  end
225
253
 
226
254
  context 'on the second history entry' do
227
255
  subject { foo.history.second.succ }
228
- it { should == foo.history.third }
256
+ it { is_expected.to eq foo.history.third }
229
257
  end
230
258
 
231
259
  context 'on the last history entry' do
232
260
  subject { foo.history.last.succ }
233
- it { should be_nil }
261
+ it { is_expected.to be(nil) }
234
262
  end
235
263
  end
236
264
 
237
265
  describe '#first' do
238
266
  subject { foo.history.sample.first }
239
- it { should == foo.history.first }
267
+ it { is_expected.to eq foo.history.first }
240
268
  end
241
269
 
242
270
  describe '#last' do
243
271
  subject { foo.history.sample.last }
244
- it { should == foo.history.last }
272
+ it { is_expected.to eq foo.history.last }
245
273
  end
246
274
 
247
275
  describe '#current_version' do
248
276
  describe 'on plain records' do
249
277
  subject { foo.current_version }
250
- it { should == foo }
278
+ it { is_expected.to eq foo }
251
279
  end
252
280
 
253
281
  describe 'from #as_of' do
254
282
  subject { foo.as_of(Time.now) }
255
- it { should == foo }
283
+ it { is_expected.to eq foo }
256
284
  end
257
285
 
258
286
  describe 'on historical records' do
259
287
  subject { foo.history.sample.current_version }
260
- it { should == foo }
288
+ it { is_expected.to eq foo }
261
289
  end
262
290
  end
263
291
 
264
292
  describe '#historical?' do
293
+ subject { record.historical? }
294
+
265
295
  describe 'on plain records' do
266
- subject { foo.historical? }
267
- it { should be_false }
296
+ let(:record) { foo }
297
+ it { is_expected.to be(false) }
268
298
  end
269
299
 
270
300
  describe 'on historical records' do
271
301
  describe 'from #history' do
272
- subject { foo.history.first }
273
- it { should be_true }
302
+ let(:record) { foo.history.first }
303
+ it { is_expected.to be(true) }
274
304
  end
275
305
 
276
306
  describe 'from #as_of' do
277
- subject { foo.as_of(Time.now) }
278
- it { should be_true }
307
+ let(:record) { foo.as_of(Time.now) }
308
+ it { is_expected.to be(true) }
279
309
  end
280
310
  end
281
311
  end
@@ -302,24 +332,26 @@ describe ChronoModel::TimeMachine do
302
332
  it { expect { rec.reload }.to raise_error(ActiveRecord::RecordNotFound) }
303
333
 
304
334
  describe 'does not delete its history' do
335
+ subject { record.name }
336
+
305
337
  context do
306
- subject { rec.as_of(rec.ts.first) }
307
- its(:name) { should == 'alive foo' }
338
+ let(:record) { rec.as_of(rec.ts.first) }
339
+ it { is_expected.to eq 'alive foo' }
308
340
  end
309
341
 
310
342
  context do
311
- subject { rec.as_of(rec.ts.last) }
312
- its(:name) { should == 'dying foo' }
343
+ let(:record) { rec.as_of(rec.ts.last) }
344
+ it { is_expected.to eq 'dying foo' }
313
345
  end
314
346
 
315
347
  context do
316
- subject { Foo.as_of(rec.ts.first).where(:fooity => 42).first }
317
- its(:name) { should == 'alive foo' }
348
+ let(:record) { Foo.as_of(rec.ts.first).where(:fooity => 42).first }
349
+ it { is_expected.to eq 'alive foo' }
318
350
  end
319
351
 
320
352
  context do
321
353
  subject { Foo.history.where(:fooity => 42).map(&:name) }
322
- it { should == ['alive foo', 'dying foo'] }
354
+ it { is_expected.to eq ['alive foo', 'dying foo'] }
323
355
  end
324
356
  end
325
357
  end
@@ -339,14 +371,14 @@ describe ChronoModel::TimeMachine do
339
371
  describe 'on records having an :has_many relationship' do
340
372
  describe 'by default returns timestamps of the record only' do
341
373
  subject { split.call(foo.timeline) }
342
- its(:size) { should == foo.ts.size }
343
- it { should == timestamps_from.call(foo) }
374
+ it { expect(subject.size).to eq foo.ts.size }
375
+ it { is_expected.to eq timestamps_from.call(foo) }
344
376
  end
345
377
 
346
378
  describe 'when asked, returns timestamps including the related objects' do
347
379
  subject { split.call(foo.timeline(:with => :bars)) }
348
- its(:size) { should == foo.ts.size + bar.ts.size }
349
- it { should == timestamps_from.call(foo, *foo.bars) }
380
+ it { expect(subject.size).to eq(foo.ts.size + bar.ts.size) }
381
+ it { is_expected.to eq(timestamps_from.call(foo, *foo.bars)) }
350
382
  end
351
383
  end
352
384
 
@@ -364,8 +396,8 @@ describe ChronoModel::TimeMachine do
364
396
  }
365
397
  end
366
398
 
367
- its(:size) { should == expected.size }
368
- it { should == expected }
399
+ it { expect(subject.size).to eq expected.size }
400
+ it { is_expected.to eq expected }
369
401
  end
370
402
  end
371
403
 
@@ -373,8 +405,8 @@ describe ChronoModel::TimeMachine do
373
405
  subject { split.call(baz.timeline) }
374
406
 
375
407
  describe 'returns timestamps of its temporal associations' do
376
- its(:size) { should == bar.ts.size }
377
- it { should == timestamps_from.call(bar) }
408
+ it { expect(subject.size).to eq bar.ts.size }
409
+ it { is_expected.to eq timestamps_from.call(bar) }
378
410
  end
379
411
  end
380
412
  end
@@ -383,13 +415,13 @@ describe ChronoModel::TimeMachine do
383
415
  context 'on plain records' do
384
416
  context 'having history' do
385
417
  subject { bar.last_changes }
386
- it { should == {'name' => ['bar bar', 'new bar']} }
418
+ it { is_expected.to eq('name' => ['bar bar', 'new bar']) }
387
419
  end
388
420
 
389
421
  context 'without history' do
390
422
  let(:record) { Bar.create!(:name => 'foreveralone') }
391
423
  subject { record.last_changes }
392
- it { should be_nil }
424
+ it { is_expected.to be_nil }
393
425
  after { record.destroy.history.delete_all } # UGLY
394
426
  end
395
427
  end
@@ -397,56 +429,46 @@ describe ChronoModel::TimeMachine do
397
429
  context 'on history records' do
398
430
  context 'at the beginning of the timeline' do
399
431
  subject { bar.history.first.last_changes }
400
- it { should be_nil }
432
+ it { is_expected.to be_nil }
401
433
  end
402
434
 
403
435
  context 'in the middle of the timeline' do
404
436
  subject { bar.history.second.last_changes }
405
- it { should == {'name' => ['bar', 'foo bar']} }
437
+ it { is_expected.to eq('name' => ['bar', 'foo bar']) }
406
438
  end
407
439
  end
408
440
  end
409
441
 
410
442
  describe '#changes_against' do
411
443
  context 'can compare records against history' do
412
- it { bar.changes_against(bar.history.first).should ==
413
- {'name' => ['bar', 'new bar']} }
414
-
415
- it { bar.changes_against(bar.history.second).should ==
416
- {'name' => ['foo bar', 'new bar']} }
417
-
418
- it { bar.changes_against(bar.history.third).should ==
419
- {'name' => ['bar bar', 'new bar']} }
420
-
421
- it { bar.changes_against(bar.history.last).should == {} }
444
+ it { expect(bar.changes_against(bar.history.first)).to eq('name' => ['bar', 'new bar']) }
445
+ it { expect(bar.changes_against(bar.history.second)).to eq('name' => ['foo bar', 'new bar']) }
446
+ it { expect(bar.changes_against(bar.history.third)).to eq('name' => ['bar bar', 'new bar']) }
447
+ it { expect(bar.changes_against(bar.history.last)).to eq({}) }
422
448
  end
423
449
 
424
450
  context 'can compare history against history' do
425
- it { bar.history.first.changes_against(bar.history.third).should ==
426
- {'name' => ['bar bar', 'bar']} }
427
-
428
- it { bar.history.second.changes_against(bar.history.third).should ==
429
- {'name' => ['bar bar', 'foo bar']} }
430
-
431
- it { bar.history.third.changes_against(bar.history.third).should == {} }
451
+ it { expect(bar.history.first.changes_against(bar.history.third)).to eq('name' => ['bar bar', 'bar']) }
452
+ it { expect(bar.history.second.changes_against(bar.history.third)).to eq('name' => ['bar bar', 'foo bar']) }
453
+ it { expect(bar.history.third.changes_against(bar.history.third)).to eq({}) }
432
454
  end
433
455
  end
434
456
 
435
457
  describe '#pred' do
436
458
  context 'on records having history' do
437
459
  subject { bar.pred }
438
- its(:name) { should == 'bar bar' }
460
+ it { expect(subject.name).to eq 'bar bar' }
439
461
  end
440
462
 
441
463
  context 'when there is enough history' do
442
464
  subject { bar.pred.pred.pred.pred }
443
- its(:name) { should == 'bar' }
465
+ it { expect(subject.name).to eq 'bar' }
444
466
  end
445
467
 
446
468
  context 'when no history is recorded' do
447
469
  let(:record) { Bar.create!(:name => 'quuuux') }
448
470
  subject { record.pred }
449
- it { should be_nil }
471
+ it { is_expected.to be(nil) }
450
472
  after { record.destroy.history.delete_all }
451
473
  end
452
474
  end
@@ -462,9 +484,9 @@ describe ChronoModel::TimeMachine do
462
484
  describe ['#', attr].join do
463
485
  subject { record.public_send(attr) }
464
486
 
465
- it { should be_present }
466
- it { should be_a(Time) }
467
- it { should be_utc }
487
+ it { is_expected.to be_present }
488
+ it { is_expected.to be_a(Time) }
489
+ it { is_expected.to be_utc }
468
490
  end
469
491
  end
470
492
  end
@@ -484,7 +506,7 @@ describe ChronoModel::TimeMachine do
484
506
  describe ['#', attr].join do
485
507
  subject { record.public_send(attr) }
486
508
 
487
- it { should be_nil }
509
+ it { is_expected.to be(nil) }
488
510
  end
489
511
  end
490
512
  end
@@ -499,38 +521,38 @@ describe ChronoModel::TimeMachine do
499
521
  after(:all) { foos.each(&:destroy); bars.each(&:destroy) }
500
522
 
501
523
  describe '.as_of' do
502
- it { Foo.as_of(1.month.ago).should == [] }
524
+ it { expect(Foo.as_of(1.month.ago)).to eq [] }
503
525
 
504
- it { Foo.as_of(foos[0].ts[0]).should == [foo, foos[0]] }
505
- it { Foo.as_of(foos[1].ts[0]).should == [foo, foos[0], foos[1]] }
506
- it { Foo.as_of(Time.now ).should == [foo, foos[0], foos[1]] }
526
+ it { expect(Foo.as_of(foos[0].ts[0])).to eq [foo, foos[0]] }
527
+ it { expect(Foo.as_of(foos[1].ts[0])).to eq [foo, foos[0], foos[1]] }
528
+ it { expect(Foo.as_of(Time.now )).to eq [foo, foos[0], foos[1]] }
507
529
 
508
- it { Bar.as_of(foos[1].ts[0]).should == [bar] }
530
+ it { expect(Bar.as_of(foos[1].ts[0])).to eq [bar] }
509
531
 
510
- it { Bar.as_of(bars[0].ts[0]).should == [bar, bars[0]] }
511
- it { Bar.as_of(bars[1].ts[0]).should == [bar, bars[0], bars[1]] }
512
- it { Bar.as_of(Time.now ).should == [bar, bars[0], bars[1]] }
532
+ it { expect(Bar.as_of(bars[0].ts[0])).to eq [bar, bars[0]] }
533
+ it { expect(Bar.as_of(bars[1].ts[0])).to eq [bar, bars[0], bars[1]] }
534
+ it { expect(Bar.as_of(Time.now )).to eq [bar, bars[0], bars[1]] }
513
535
 
514
536
  # Associations
515
537
  context do
516
- subject { foos[0] }
538
+ subject { foos[0].id }
517
539
 
518
- it { Foo.as_of(foos[0].ts[0]).find(subject).bars.should == [] }
519
- it { Foo.as_of(foos[1].ts[0]).find(subject).bars.should == [] }
520
- it { Foo.as_of(bars[0].ts[0]).find(subject).bars.should == [bars[0]] }
521
- it { Foo.as_of(bars[1].ts[0]).find(subject).bars.should == [bars[0]] }
522
- it { Foo.as_of(Time.now ).find(subject).bars.should == [bars[0]] }
540
+ it { expect(Foo.as_of(foos[0].ts[0]).find(subject).bars).to eq [] }
541
+ it { expect(Foo.as_of(foos[1].ts[0]).find(subject).bars).to eq [] }
542
+ it { expect(Foo.as_of(bars[0].ts[0]).find(subject).bars).to eq [bars[0]] }
543
+ it { expect(Foo.as_of(bars[1].ts[0]).find(subject).bars).to eq [bars[0]] }
544
+ it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [bars[0]] }
523
545
  end
524
546
 
525
547
  context do
526
- subject { foos[1] }
548
+ subject { foos[1].id }
527
549
 
528
550
  it { expect { Foo.as_of(foos[0].ts[0]).find(subject) }.to raise_error(ActiveRecord::RecordNotFound) }
529
551
  it { expect { Foo.as_of(foos[1].ts[0]).find(subject) }.to_not raise_error }
530
552
 
531
- it { Foo.as_of(bars[0].ts[0]).find(subject).bars.should == [] }
532
- it { Foo.as_of(bars[1].ts[0]).find(subject).bars.should == [bars[1]] }
533
- it { Foo.as_of(Time.now ).find(subject).bars.should == [bars[1]] }
553
+ it { expect(Foo.as_of(bars[0].ts[0]).find(subject).bars).to eq [] }
554
+ it { expect(Foo.as_of(bars[1].ts[0]).find(subject).bars).to eq [bars[1]] }
555
+ it { expect(Foo.as_of(Time.now ).find(subject).bars).to eq [bars[1]] }
534
556
  end
535
557
  end
536
558
 
@@ -543,17 +565,17 @@ describe ChronoModel::TimeMachine do
543
565
  ['bar', 'foo bar', 'bar bar', 'new bar', 'bar 0', 'bar 1']
544
566
  }
545
567
 
546
- it { Foo.history.all.map(&:name).should == foo_history }
547
- it { Bar.history.all.map(&:name).should == bar_history }
568
+ it { expect(Foo.history.all.map(&:name)).to eq foo_history }
569
+ it { expect(Bar.history.all.map(&:name)).to eq bar_history }
548
570
  end
549
571
 
550
572
  describe '.time_query' do
551
- it { Foo.history.time_query(:after, :now, inclusive: true ).count.should == 3 }
552
- it { Foo.history.time_query(:after, :now, inclusive: false).count.should == 0 }
553
- it { Foo.history.time_query(:before, :now, inclusive: true ).count.should == 5 }
554
- it { Foo.history.time_query(:before, :now, inclusive: false).count.should == 2 }
573
+ it { expect(Foo.history.time_query(:after, :now, inclusive: true ).count).to eq 3 }
574
+ it { expect(Foo.history.time_query(:after, :now, inclusive: false).count).to eq 0 }
575
+ it { expect(Foo.history.time_query(:before, :now, inclusive: true ).count).to eq 5 }
576
+ it { expect(Foo.history.time_query(:before, :now, inclusive: false).count).to eq 2 }
555
577
 
556
- it { Foo.history.past.size.should == 2 }
578
+ it { expect(Foo.history.past.size).to eq 2 }
557
579
  end
558
580
 
559
581
  end
@@ -571,10 +593,10 @@ describe ChronoModel::TimeMachine do
571
593
  end
572
594
 
573
595
  it "generate only a single history record" do
574
- r1.history.should have(2).entries
596
+ expect(r1.history.size).to eq(2)
575
597
 
576
- r1.history.first.name.should == 'xact test'
577
- r1.history.last.name.should == 'does work'
598
+ expect(r1.history.first.name).to eq 'xact test'
599
+ expect(r1.history.last.name).to eq 'does work'
578
600
  end
579
601
  end
580
602
 
@@ -589,9 +611,8 @@ describe ChronoModel::TimeMachine do
589
611
  end
590
612
 
591
613
  it 'generates a single history record' do
592
- r2.history.should have(1).entry
593
-
594
- r2.history.first.name.should == 'I am Foo'
614
+ expect(r2.history.size).to eq(1)
615
+ expect(r2.history.first.name).to eq 'I am Foo'
595
616
  end
596
617
  end
597
618
 
@@ -603,7 +624,7 @@ describe ChronoModel::TimeMachine do
603
624
  end
604
625
 
605
626
  it 'does not generate any history' do
606
- Foo.history.where(:id => r3.id).should be_empty
627
+ expect(Foo.history.where(:id => r3.id)).to be_empty
607
628
  end
608
629
  end
609
630
  end
@@ -620,9 +641,8 @@ describe ChronoModel::TimeMachine do
620
641
  subject.reload
621
642
  end
622
643
 
623
- it { should be_a(Bar::History) }
624
- it { should be_true }
625
- its(:name) { should == 'modified bar history' }
644
+ it { is_expected.to be_a(Bar::History) }
645
+ it { expect(subject.name).to eq 'modified bar history' }
626
646
  end
627
647
 
628
648
  describe '#save!' do
@@ -634,9 +654,8 @@ describe ChronoModel::TimeMachine do
634
654
  subject.reload
635
655
  end
636
656
 
637
- it { should be_a(Bar::History) }
638
- it { should be_true }
639
- its(:name) { should == 'another modified bar history' }
657
+ it { is_expected.to be_a(Bar::History) }
658
+ it { expect(subject.name).to eq 'another modified bar history' }
640
659
  end
641
660
  end
642
661