terrestrial 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/Gemfile.lock +44 -53
  4. data/README.md +3 -6
  5. data/bin/test +1 -1
  6. data/features/env.rb +12 -2
  7. data/features/example.feature +23 -26
  8. data/lib/terrestrial.rb +31 -0
  9. data/lib/terrestrial/adapters/abstract_adapter.rb +6 -0
  10. data/lib/terrestrial/adapters/memory_adapter.rb +82 -6
  11. data/lib/terrestrial/adapters/sequel_postgres_adapter.rb +191 -0
  12. data/lib/terrestrial/configurations/conventional_association_configuration.rb +65 -35
  13. data/lib/terrestrial/configurations/conventional_configuration.rb +280 -124
  14. data/lib/terrestrial/configurations/mapping_config_options_proxy.rb +97 -0
  15. data/lib/terrestrial/deleted_record.rb +12 -8
  16. data/lib/terrestrial/dirty_map.rb +17 -9
  17. data/lib/terrestrial/functional_pipeline.rb +64 -0
  18. data/lib/terrestrial/inspection_string.rb +6 -1
  19. data/lib/terrestrial/lazy_object_proxy.rb +1 -0
  20. data/lib/terrestrial/many_to_many_association.rb +34 -20
  21. data/lib/terrestrial/many_to_one_association.rb +11 -3
  22. data/lib/terrestrial/one_to_many_association.rb +9 -0
  23. data/lib/terrestrial/public_conveniencies.rb +65 -82
  24. data/lib/terrestrial/record.rb +106 -0
  25. data/lib/terrestrial/relation_mapping.rb +43 -12
  26. data/lib/terrestrial/relational_store.rb +33 -11
  27. data/lib/terrestrial/upsert_record.rb +54 -0
  28. data/lib/terrestrial/version.rb +1 -1
  29. data/spec/automatic_timestamps_spec.rb +339 -0
  30. data/spec/changes_api_spec.rb +81 -0
  31. data/spec/config_override_spec.rb +28 -19
  32. data/spec/custom_serializers_spec.rb +3 -2
  33. data/spec/database_default_fields_spec.rb +213 -0
  34. data/spec/database_generated_id_spec.rb +291 -0
  35. data/spec/database_owned_fields_and_timestamps_spec.rb +200 -0
  36. data/spec/deletion_spec.rb +1 -1
  37. data/spec/error_handling/factory_error_handling_spec.rb +1 -4
  38. data/spec/error_handling/serialization_error_spec.rb +1 -4
  39. data/spec/error_handling/upsert_error_spec.rb +7 -11
  40. data/spec/graph_persistence_spec.rb +52 -18
  41. data/spec/ordered_association_spec.rb +10 -12
  42. data/spec/predefined_queries_spec.rb +14 -12
  43. data/spec/readme_examples_spec.rb +1 -1
  44. data/spec/sequel_query_efficiency_spec.rb +19 -16
  45. data/spec/spec_helper.rb +6 -1
  46. data/spec/support/blog_schema.rb +7 -3
  47. data/spec/support/object_graph_setup.rb +30 -39
  48. data/spec/support/object_store_setup.rb +16 -196
  49. data/spec/support/seed_data_setup.rb +15 -149
  50. data/spec/support/seed_records.rb +141 -0
  51. data/spec/support/sequel_test_support.rb +46 -13
  52. data/spec/terrestrial/abstract_record_spec.rb +138 -106
  53. data/spec/terrestrial/adapters/sequel_postgres_adapter_spec.rb +138 -0
  54. data/spec/terrestrial/deleted_record_spec.rb +0 -27
  55. data/spec/terrestrial/dirty_map_spec.rb +52 -77
  56. data/spec/terrestrial/functional_pipeline_spec.rb +153 -0
  57. data/spec/terrestrial/inspection_string_spec.rb +61 -0
  58. data/spec/terrestrial/upsert_record_spec.rb +29 -0
  59. data/terrestrial.gemspec +7 -8
  60. metadata +43 -40
  61. data/MissingFeatures.md +0 -64
  62. data/lib/terrestrial/abstract_record.rb +0 -99
  63. data/lib/terrestrial/association_loaders.rb +0 -52
  64. data/lib/terrestrial/upserted_record.rb +0 -15
  65. data/spec/terrestrial/public_conveniencies_spec.rb +0 -63
  66. data/spec/terrestrial/upserted_record_spec.rb +0 -59
@@ -0,0 +1,81 @@
1
+ require "spec_helper"
2
+
3
+ require "support/object_store_setup"
4
+ require "support/seed_data_setup"
5
+ require "support/have_persisted_matcher"
6
+ require "terrestrial"
7
+
8
+ RSpec.describe "Changes API", backend: "sequel" do
9
+ include_context "object store setup"
10
+ include_context "seed data setup"
11
+
12
+ subject(:user_store) { object_store.fetch(:users) }
13
+
14
+ let(:user) {
15
+ user_store.where(id: "users/1").first
16
+ }
17
+
18
+ describe "#changes" do
19
+ context "when there are no changes" do
20
+ it "returns an empty change set" do
21
+ expect(user_store.changes(user)).to be_empty
22
+ end
23
+ end
24
+
25
+ context "when loading and modifying only the root node" do
26
+ let(:modified_email) { "hasel+modified@gmail.com" }
27
+
28
+ it "returns changes to only that node" do
29
+ user.email = modified_email
30
+
31
+ expect(user_store.changes(user).map(&:to_h)).to eq(
32
+ [
33
+ {
34
+ id: "users/1",
35
+ email: modified_email,
36
+ }
37
+ ]
38
+ )
39
+ end
40
+
41
+ it "does not persist the changes" do
42
+ user.email = modified_email
43
+
44
+ user_store.changes(user)
45
+
46
+ expect(datastore).not_to have_persisted(
47
+ :users,
48
+ hash_including(
49
+ id: user.id,
50
+ email: modified_email,
51
+ )
52
+ )
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "#changes_sql" do
58
+ context "when there are no changes" do
59
+ it "returns an empty list" do
60
+ expect(user_store.changes_sql(user)).to be_empty
61
+ end
62
+ end
63
+
64
+ context "when loading and modifying only the root node" do
65
+ let(:modified_email) { "hasel+modified@gmail.com" }
66
+
67
+ it "returns the upsert statement for just that change" do
68
+ user.email = modified_email
69
+
70
+ expect(user_store.changes_sql(user)).to eq(
71
+ [
72
+ "INSERT INTO \"users\" (\"email\", \"id\") VALUES " \
73
+ "('hasel+modified@gmail.com', 'users/1') ON CONFLICT (\"id\") " \
74
+ "DO UPDATE SET \"email\" = 'hasel+modified@gmail.com' " \
75
+ "RETURNING *",
76
+ ]
77
+ )
78
+ end
79
+ end
80
+ end
81
+ end
@@ -12,19 +12,16 @@ RSpec.describe "Configuration override" do
12
12
  include_context "seed data setup"
13
13
 
14
14
  let(:object_store) {
15
- Terrestrial.object_store(mappings: override_config, datastore: datastore)
15
+ Terrestrial.object_store(config: override_config)
16
16
  }
17
17
 
18
18
  let(:override_config) {
19
- Terrestrial::Configurations::ConventionalConfiguration.new(datastore)
19
+ Terrestrial::config(datastore)
20
20
  .setup_mapping(:users) { |users|
21
21
  users.has_many :posts, foreign_key: :author_id
22
22
  users.fields([:id, :first_name, :last_name, :email])
23
23
  }
24
- }
25
-
26
- let(:user) {
27
- object_store[:users].where(id: "users/1").first
24
+ .setup_mapping(:posts)
28
25
  }
29
26
 
30
27
  context "override the root mapper factory" do
@@ -38,6 +35,8 @@ RSpec.describe "Configuration override" do
38
35
  let(:user_struct) { Struct.new(*User.members) }
39
36
 
40
37
  it "uses the class from the override" do
38
+ user = object_store[:users].where(id: "users/1").first
39
+
41
40
  expect(user.class).to be(user_struct)
42
41
  end
43
42
  end
@@ -58,11 +57,10 @@ RSpec.describe "Configuration override" do
58
57
  post_class.method(:new)
59
58
  }
60
59
 
61
- let(:posts) {
62
- user.posts
63
- }
64
-
65
60
  it "uses the specified factory" do
61
+ user = object_store[:users].where(id: "users/1").first
62
+ posts = user.posts
63
+
66
64
  expect(posts.first.class).to be(post_class)
67
65
  end
68
66
  end
@@ -79,12 +77,11 @@ RSpec.describe "Configuration override" do
79
77
  end
80
78
 
81
79
  let(:override_config) {
82
- Terrestrial::Configurations::ConventionalConfiguration
83
- .new(datastore)
84
- .setup_mapping(:users) do |config|
85
- config.relation_name unconventional_table_name
86
- config.class(OpenStruct)
87
- end
80
+ Terrestrial.config(datastore)
81
+ .setup_mapping(:users) do |config|
82
+ config.relation_name unconventional_table_name
83
+ config.class(OpenStruct)
84
+ end
88
85
  }
89
86
 
90
87
  let(:unconventional_table_name) {
@@ -131,12 +128,12 @@ RSpec.describe "Configuration override" do
131
128
  config.class(OpenStruct)
132
129
  config.relation_name strange_table_name_map.fetch(:posts)
133
130
  config.belongs_to(:author, mapping_name: :users)
134
- config.has_many_through(:categories, through_mapping_name: strange_table_name_map.fetch(:categories_to_posts))
131
+ config.has_many_through(:categories, through_table_name: strange_table_name_map.fetch(:categories_to_posts))
135
132
  end
136
133
  .setup_mapping(:categories) do |config|
137
134
  config.class(OpenStruct)
138
135
  config.relation_name strange_table_name_map.fetch(:categories)
139
- config.has_many_through(:posts, through_mapping_name: strange_table_name_map.fetch(:categories_to_posts))
136
+ config.has_many_through(:posts, through_table_name: strange_table_name_map.fetch(:categories_to_posts))
140
137
  end
141
138
  end
142
139
 
@@ -150,12 +147,24 @@ RSpec.describe "Configuration override" do
150
147
  }
151
148
 
152
149
  it "maps data from the specified relation into a has many collection" do
150
+ user = object_store[:users].where(id: "users/1").first
151
+
153
152
  expect(
154
153
  user.posts.map(&:id)
155
154
  ).to eq(["posts/1", "posts/2"])
156
155
  end
157
156
 
157
+ it "maps data from the specified relation into a has many through collection" do
158
+ user = object_store[:users].where(id: "users/1").first
159
+
160
+ expect(
161
+ user.posts.flat_map(&:categories).map(&:id).uniq
162
+ ).to eq(["categories/1", "categories/2"])
163
+ end
164
+
158
165
  it "maps data from the specified relation into a `belongs_to` field" do
166
+ user = object_store[:users].where(id: "users/1").first
167
+
159
168
  expect(
160
169
  user.posts.first.author.__getobj__.object_id
161
170
  ).to eq(user.object_id)
@@ -168,7 +177,7 @@ RSpec.describe "Configuration override" do
168
177
  TypeTwoUser = Class.new(OpenStruct)
169
178
 
170
179
  let(:override_config) {
171
- Terrestrial::Configurations::ConventionalConfiguration.new(datastore)
180
+ Terrestrial.config(datastore)
172
181
  .setup_mapping(:t1_users) { |c|
173
182
  c.class(TypeOneUser)
174
183
  c.table_name(:users)
@@ -25,8 +25,9 @@ RSpec.describe "Config override" do
25
25
 
26
26
  before do
27
27
  mappings
28
- .fetch(:users)
29
- .instance_variable_set(:@serializer, user_serializer)
28
+ .setup_mapping(:users) { |users|
29
+ users.serializer(user_serializer)
30
+ }
30
31
  end
31
32
 
32
33
  context "when saving the object" do
@@ -0,0 +1,213 @@
1
+ require "spec_helper"
2
+
3
+ require "support/have_persisted_matcher"
4
+ require "support/object_store_setup"
5
+ require "support/seed_data_setup"
6
+
7
+ RSpec.describe "Database default fields", backend: "sequel" do
8
+ include_context "object store setup"
9
+
10
+ before(:all) do
11
+ create_db_timestamp_tables
12
+ end
13
+
14
+ after(:all) do
15
+ drop_db_timestamp_tables
16
+ end
17
+
18
+ before do
19
+ clean_db_timestamp_tables
20
+ end
21
+
22
+ let(:user_store) {
23
+ object_store[:users]
24
+ }
25
+
26
+ let(:object_store) {
27
+ Terrestrial.object_store(config: with_db_default_fields_config)
28
+ }
29
+
30
+ let(:user_with_post) {
31
+ User.new(
32
+ id: "users/1",
33
+ first_name: "Hansel",
34
+ last_name: "Trickett",
35
+ email: "hansel@tricketts.org",
36
+ posts: [post],
37
+ )
38
+ }
39
+
40
+ let(:post) {
41
+ Post.new(
42
+ id: "posts/1",
43
+ author: nil,
44
+ subject: "Biscuits",
45
+ body: "I like them",
46
+ comments: [],
47
+ categories: [],
48
+ created_at: nil,
49
+ updated_at: nil,
50
+ )
51
+ }
52
+
53
+ let(:party_time) { Time.parse("1999-01-01 00:00:00 UTC") }
54
+
55
+ let(:with_db_default_fields_config) {
56
+ Terrestrial.config(datastore)
57
+ .setup_mapping(:users) { |users|
58
+ users.has_many(:posts, foreign_key: :author_id)
59
+ }
60
+ .setup_mapping(:posts) { |posts|
61
+ posts.relation_name(:timestamped_posts)
62
+ posts.database_default_field(:created_at)
63
+ posts.database_default_field(:updated_at)
64
+ }
65
+ }
66
+
67
+ context "new objects" do
68
+ context "when the object's value is nil" do
69
+ before do
70
+ post.created_at = nil
71
+ end
72
+
73
+ it "updates the object with the new default value" do
74
+ expect(post).to receive(:created_at=).with(an_instance_of(Time))
75
+
76
+ user_store.save(user_with_post)
77
+ end
78
+ end
79
+
80
+ context "when the object's value has been set to something" do
81
+ before do
82
+ post.created_at = party_time
83
+ end
84
+
85
+ it "does not set a value on the object" do
86
+ expect(post).not_to receive(:created_at=).with(party_time)
87
+
88
+ user_store.save(user_with_post)
89
+ end
90
+
91
+ it "persists the user-defined value" do
92
+ user_store.save(user_with_post)
93
+
94
+ expect(datastore).to have_persisted(
95
+ :timestamped_posts,
96
+ hash_including(
97
+ id: post.id,
98
+ created_at: party_time,
99
+ )
100
+ )
101
+ end
102
+ end
103
+ end
104
+
105
+ context "updating existing objects" do
106
+ before do
107
+ user_store.save(user_with_post)
108
+ post.body = "new body"
109
+ end
110
+
111
+ it "regardless, updates the object with the returned value" do
112
+ expect(post).to receive(:created_at=).with(an_instance_of(Time))
113
+
114
+ user_store.save(user_with_post)
115
+ end
116
+
117
+ context "when the value changes in the database (e.g. a trigger)" do
118
+ before do
119
+ datastore[:timestamped_posts]
120
+ .where(id: post.id)
121
+ .update("created_at" => party_time)
122
+ end
123
+
124
+ it "regardless, updates the object with the returned value" do
125
+ expect(post).to receive(:created_at=).with(party_time)
126
+
127
+ user_store.save(user_with_post)
128
+ end
129
+ end
130
+
131
+ context "when the object's value is modified by the application" do
132
+ it "does not modify the object" do
133
+ original_time = post.created_at
134
+ post.created_at = party_time
135
+
136
+ expect(post).not_to receive(:created_at=)
137
+
138
+ user_store.save(user_with_post)
139
+ expect(post.created_at).to eq(party_time)
140
+ end
141
+
142
+ it "persists the object's new value" do
143
+ post.created_at = party_time
144
+
145
+ user_store.save(user_with_post)
146
+
147
+ expect(datastore).to have_persisted(
148
+ :timestamped_posts,
149
+ hash_including(
150
+ id: post.id,
151
+ created_at: party_time,
152
+ )
153
+ )
154
+ end
155
+ end
156
+ end
157
+
158
+ def schema
159
+ {
160
+ :tables => {
161
+ :timestamped_posts => [
162
+ {
163
+ :name => :id,
164
+ :type => String,
165
+ :options => {
166
+ :primary_key => true,
167
+ }
168
+ },
169
+ {
170
+ :name => :subject,
171
+ :type => String,
172
+ },
173
+ {
174
+ :name => :body,
175
+ :type => String,
176
+ },
177
+ {
178
+ :name => :author_id,
179
+ :type => String,
180
+ },
181
+ {
182
+ :name => :created_at,
183
+ :type => DateTime,
184
+ :options => {
185
+ :default => Sequel::CURRENT_TIMESTAMP,
186
+ :null => false,
187
+ },
188
+ },
189
+ {
190
+ :name => :updated_at,
191
+ :type => DateTime,
192
+ :options => {
193
+ :default => Sequel::CURRENT_TIMESTAMP,
194
+ :null => false,
195
+ },
196
+ },
197
+ ],
198
+ },
199
+ }
200
+ end
201
+
202
+ def create_db_timestamp_tables
203
+ Terrestrial::SequelTestSupport.create_tables(schema.fetch(:tables))
204
+ end
205
+
206
+ def clean_db_timestamp_tables
207
+ Terrestrial::SequelTestSupport.clean_tables(schema.fetch(:tables).keys)
208
+ end
209
+
210
+ def drop_db_timestamp_tables
211
+ Terrestrial::SequelTestSupport.drop_tables(schema.fetch(:tables).keys)
212
+ end
213
+ end
@@ -0,0 +1,291 @@
1
+ require "spec_helper"
2
+
3
+ require "support/have_persisted_matcher"
4
+ require "support/object_store_setup"
5
+ require "support/seed_data_setup"
6
+
7
+ RSpec.describe "Database generated IDs", backend: "sequel" do
8
+ include_context "object store setup"
9
+
10
+ before(:all) do
11
+ create_serial_id_tables
12
+ end
13
+
14
+ after(:all) do
15
+ drop_serial_id_tables
16
+ end
17
+
18
+ before(:each) do
19
+ clean_serial_id_tables
20
+ end
21
+
22
+ let(:user_store) {
23
+ object_store[:users]
24
+ }
25
+
26
+ let(:object_store) {
27
+ Terrestrial.object_store(config: serial_id_config)
28
+ }
29
+
30
+ let(:user) {
31
+ User.new(user_attrs)
32
+ }
33
+
34
+ let(:user_attrs) {
35
+ {
36
+ id: nil,
37
+ first_name: "Hansel",
38
+ last_name: "Trickett",
39
+ email: "hansel@tricketts.org",
40
+ posts: [],
41
+ }
42
+ }
43
+
44
+ let(:serial_id_config) {
45
+ Terrestrial.config(datastore)
46
+ .setup_mapping(:users) { |users|
47
+ users.use_database_id
48
+ users.relation_name(:serial_id_users)
49
+ users.has_many(:posts, foreign_key: :author_id)
50
+ }
51
+ .setup_mapping(:posts) { |posts|
52
+ posts.use_database_id
53
+ posts.relation_name(:serial_id_posts)
54
+ }
55
+ }
56
+
57
+ it "persists the root node" do
58
+ expected_sequence_id = get_next_sequence_value("serial_id_users")
59
+
60
+ user_store.save(user)
61
+
62
+ expect(datastore).to have_persisted(
63
+ :serial_id_users,
64
+ hash_including(
65
+ id: expected_sequence_id,
66
+ first_name: hansel.first_name,
67
+ last_name: hansel.last_name,
68
+ email: hansel.email,
69
+ )
70
+ )
71
+ end
72
+
73
+ it "updates the object with serial database ID" do
74
+ expected_sequence_id = get_next_sequence_value("serial_id_users")
75
+
76
+ user_store.save(user)
77
+
78
+ expect(user.id).to eq(expected_sequence_id)
79
+ end
80
+
81
+ context "when persisting two associated objects" do
82
+ before { user.posts.push(post) }
83
+
84
+ let(:post) { Post.new(post_attrs) }
85
+
86
+ let(:post_attrs) {
87
+ {
88
+ id: nil,
89
+ author: nil,
90
+ subject: "Biscuits",
91
+ body: "I like them",
92
+ comments: [],
93
+ categories: [],
94
+ created_at: Time.parse("2015-09-05T15:00:00+01:00"),
95
+ updated_at: Time.parse("2015-09-05T15:00:00+01:00"),
96
+ }
97
+ }
98
+
99
+ it "persists both objects" do
100
+ expected_user_sequence_id = get_next_sequence_value("serial_id_users")
101
+ expected_post_sequence_id = get_next_sequence_value("serial_id_posts")
102
+
103
+ user_store.save(user)
104
+
105
+ expect(datastore).to have_persisted(
106
+ :serial_id_users,
107
+ hash_including(
108
+ id: expected_user_sequence_id,
109
+ first_name: hansel.first_name,
110
+ last_name: hansel.last_name,
111
+ email: hansel.email,
112
+ )
113
+ )
114
+
115
+ expect(datastore).to have_persisted(
116
+ :serial_id_posts,
117
+ hash_including(
118
+ id: expected_post_sequence_id,
119
+ subject: "Biscuits",
120
+ body: "I like them",
121
+ created_at: Time.parse("2015-09-05T15:00:00+01:00"),
122
+ )
123
+ )
124
+ end
125
+
126
+ it "writes the foreign key" do
127
+ user_store.save(user)
128
+
129
+ expect(datastore[:serial_id_posts].first.fetch(:author_id)).to eq(user.id)
130
+ end
131
+ end
132
+
133
+ context "after an initial successful save of the object graph" do
134
+ before do
135
+ user_store.save(user)
136
+ end
137
+
138
+ context "when saving again without modifications" do
139
+ it "does not perform any more database writes" do
140
+ expect {
141
+ user_store.save(user)
142
+ }.not_to change { query_counter.write_count }
143
+ end
144
+
145
+ it "does not produce any change records" do
146
+ expect(user_store.changes(user)).to be_empty
147
+ end
148
+ end
149
+ end
150
+
151
+ context "when updating an existing record" do
152
+ before do
153
+ user_store.save(user)
154
+ end
155
+
156
+ it "performs an update" do
157
+ new_email = "hansel+alternate@gmail.com"
158
+ user.email = new_email
159
+
160
+ user_store.save(user)
161
+
162
+ expect(datastore).to have_persisted(
163
+ :serial_id_users,
164
+ hash_including(
165
+ id: user.id,
166
+ email: new_email,
167
+ )
168
+ )
169
+ end
170
+ end
171
+
172
+ context "when the user id must be set by an unconventional method" do
173
+ before do
174
+ change_objects_id_setter_method(user)
175
+ end
176
+
177
+ let(:serial_id_config) {
178
+ Terrestrial.config(datastore)
179
+ .setup_mapping(:users) { |users|
180
+ users.use_database_id { |object, new_id| object.unusual_id_setter(new_id) }
181
+ users.relation_name(:serial_id_users)
182
+ }
183
+ }
184
+
185
+ it "calls the user-defined config block which should update the ID" do
186
+ next_id = get_next_sequence_value(:serial_id_users)
187
+ expect(user).to receive(:unusual_id_setter).with(next_id)
188
+ expect(user).not_to receive(:id=)
189
+
190
+ user_store.save(user)
191
+ end
192
+
193
+ def change_objects_id_setter_method(user)
194
+ def user.id=(*args)
195
+ raise "This method should not be called"
196
+ end
197
+
198
+ def user.unusual_id_setter(value)
199
+ @id = value
200
+ end
201
+ end
202
+ end
203
+
204
+ def serial_id_schema
205
+ {
206
+ :tables => {
207
+ :serial_id_users => [
208
+ {
209
+ :name => :id,
210
+ :type => Integer,
211
+ :options => {
212
+ :primary_key => true,
213
+ :serial => true,
214
+ },
215
+ },
216
+ {
217
+ :name => :first_name,
218
+ :type => String,
219
+ },
220
+ {
221
+ :name => :last_name,
222
+ :type => String,
223
+ },
224
+ {
225
+ :name => :email,
226
+ :type => String,
227
+ },
228
+ ],
229
+ :serial_id_posts => [
230
+ {
231
+ :name => :id,
232
+ :type => Integer,
233
+ :options => {
234
+ :primary_key => true,
235
+ :serial => true,
236
+ },
237
+ },
238
+ {
239
+ :name => :subject,
240
+ :type => String,
241
+ },
242
+ {
243
+ :name => :body,
244
+ :type => String,
245
+ },
246
+ {
247
+ :name => :author_id,
248
+ :type => Integer,
249
+ },
250
+ {
251
+ :name => :created_at,
252
+ :type => DateTime,
253
+ },
254
+ ],
255
+ },
256
+ :foreign_keys => [
257
+ [
258
+ :posts,
259
+ :author_id,
260
+ :users,
261
+ :id,
262
+ ],
263
+ ],
264
+ }
265
+ end
266
+
267
+ def create_serial_id_tables
268
+ Terrestrial::SequelTestSupport.create_tables(serial_id_schema.fetch(:tables))
269
+ end
270
+
271
+ def drop_serial_id_tables
272
+ Terrestrial::SequelTestSupport.drop_tables(serial_id_schema.fetch(:tables).keys)
273
+ end
274
+
275
+ def clean_serial_id_tables
276
+ Terrestrial::SequelTestSupport.clean_tables(serial_id_schema.fetch(:tables).keys)
277
+ end
278
+
279
+ def get_next_sequence_value(table_name)
280
+ datastore["select currval(pg_get_serial_sequence('#{table_name}', 'id'))"]
281
+ .to_a
282
+ .fetch(0)
283
+ .fetch(:currval) + 1
284
+ rescue Sequel::DatabaseError => e
285
+ if /PG::ObjectNotInPrerequisiteState/.match?(e.message)
286
+ 1
287
+ else
288
+ raise e
289
+ end
290
+ end
291
+ end