terrestrial 0.3.0 → 0.5.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.
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