hanami 2.1.1 → 2.2.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +7 -7
  4. data/hanami.gemspec +6 -6
  5. data/lib/hanami/app.rb +5 -1
  6. data/lib/hanami/config/db.rb +33 -0
  7. data/lib/hanami/config.rb +36 -9
  8. data/lib/hanami/constants.rb +4 -0
  9. data/lib/hanami/extensions/db/repo.rb +103 -0
  10. data/lib/hanami/extensions.rb +4 -0
  11. data/lib/hanami/helpers/form_helper/form_builder.rb +4 -6
  12. data/lib/hanami/provider/source.rb +16 -0
  13. data/lib/hanami/provider_registrar.rb +28 -0
  14. data/lib/hanami/providers/assets.rb +2 -20
  15. data/lib/hanami/providers/db/adapter.rb +75 -0
  16. data/lib/hanami/providers/db/adapters.rb +50 -0
  17. data/lib/hanami/providers/db/config.rb +62 -0
  18. data/lib/hanami/providers/db/gateway.rb +70 -0
  19. data/lib/hanami/providers/db/sql_adapter.rb +100 -0
  20. data/lib/hanami/providers/db.rb +298 -0
  21. data/lib/hanami/providers/db_logging.rb +22 -0
  22. data/lib/hanami/providers/inflector.rb +1 -1
  23. data/lib/hanami/providers/logger.rb +1 -1
  24. data/lib/hanami/providers/rack.rb +3 -3
  25. data/lib/hanami/providers/relations.rb +31 -0
  26. data/lib/hanami/providers/routes.rb +2 -14
  27. data/lib/hanami/rake_tasks.rb +8 -7
  28. data/lib/hanami/slice.rb +84 -4
  29. data/lib/hanami/version.rb +1 -1
  30. data/lib/hanami.rb +3 -0
  31. data/spec/integration/container/provider_environment_spec.rb +52 -0
  32. data/spec/integration/db/auto_registration_spec.rb +39 -0
  33. data/spec/integration/db/commands_spec.rb +80 -0
  34. data/spec/integration/db/db_inflector_spec.rb +57 -0
  35. data/spec/integration/db/db_slices_spec.rb +332 -0
  36. data/spec/integration/db/db_spec.rb +245 -0
  37. data/spec/integration/db/gateways_spec.rb +320 -0
  38. data/spec/integration/db/logging_spec.rb +238 -0
  39. data/spec/integration/db/mappers_spec.rb +84 -0
  40. data/spec/integration/db/provider_config_spec.rb +88 -0
  41. data/spec/integration/db/provider_spec.rb +35 -0
  42. data/spec/integration/db/relations_spec.rb +60 -0
  43. data/spec/integration/db/repo_spec.rb +215 -0
  44. data/spec/integration/db/slices_importing_from_parent.rb +130 -0
  45. data/spec/integration/slices/slice_configuration_spec.rb +4 -4
  46. data/spec/support/app_integration.rb +3 -0
  47. data/spec/unit/hanami/config/db_spec.rb +38 -0
  48. data/spec/unit/hanami/config/router_spec.rb +1 -1
  49. data/spec/unit/hanami/helpers/form_helper_spec.rb +35 -4
  50. data/spec/unit/hanami/providers/db/config/default_config_spec.rb +100 -0
  51. data/spec/unit/hanami/providers/db/config_spec.rb +156 -0
  52. data/spec/unit/hanami/slice_spec.rb +32 -0
  53. data/spec/unit/hanami/version_spec.rb +1 -1
  54. metadata +72 -20
@@ -0,0 +1,320 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "DB / Gateways", :app_integration do
4
+ before do
5
+ @env = ENV.to_h
6
+ allow(Hanami::Env).to receive(:loaded?).and_return(false)
7
+ end
8
+
9
+ after do
10
+ ENV.replace(@env)
11
+ end
12
+
13
+ it "configures gateways by detecting ENV vars" do
14
+ with_tmp_directory(@dir = Dir.mktmpdir) do
15
+ write "config/app.rb", <<~RUBY
16
+ require "hanami"
17
+
18
+ module TestApp
19
+ class App < Hanami::App
20
+ end
21
+ end
22
+ RUBY
23
+
24
+ write "db/.keep", ""
25
+ write "app/relations/.keep", ""
26
+ write "slices/admin/relations/.keep", ""
27
+
28
+ ENV["DATABASE_URL"] = "sqlite://db/default.sqlite3"
29
+ ENV["DATABASE_URL__EXTRA"] = "sqlite://db/extra.sqlite3"
30
+ ENV["ADMIN__DATABASE_URL__DEFAULT"] = "sqlite://db/admin.sqlite3"
31
+ ENV["ADMIN__DATABASE_URL__SPECIAL"] = "sqlite://db/admin_special.sqlite3"
32
+
33
+ require "hanami/prepare"
34
+
35
+ expect(Hanami.app["db.rom"].gateways[:default]).to be
36
+ expect(Hanami.app["db.rom"].gateways[:extra]).to be
37
+ expect(Hanami.app["db.gateway"]).to be Hanami.app["db.rom"].gateways[:default]
38
+ expect(Hanami.app["db.gateways.default"]).to be Hanami.app["db.rom"].gateways[:default]
39
+ expect(Hanami.app["db.gateways.extra"]).to be Hanami.app["db.rom"].gateways[:extra]
40
+
41
+ expect(Admin::Slice["db.rom"].gateways[:default]).to be
42
+ expect(Admin::Slice["db.rom"].gateways[:special]).to be
43
+ expect(Admin::Slice["db.gateway"]).to be Admin::Slice["db.rom"].gateways[:default]
44
+ expect(Admin::Slice["db.gateways.default"]).to be Admin::Slice["db.rom"].gateways[:default]
45
+ expect(Admin::Slice["db.gateways.special"]).to be Admin::Slice["db.rom"].gateways[:special]
46
+ end
47
+ end
48
+
49
+ it "configures gateways from explicit config in the provider" do
50
+ with_tmp_directory(@dir = Dir.mktmpdir) do
51
+ write "config/app.rb", <<~RUBY
52
+ require "hanami"
53
+
54
+ module TestApp
55
+ class App < Hanami::App
56
+ end
57
+ end
58
+ RUBY
59
+
60
+ write "db/.keep", ""
61
+
62
+ write "config/providers/db.rb", <<~RUBY
63
+ Hanami.app.configure_provider :db do
64
+ config.gateway :default do |gw|
65
+ gw.database_url = "sqlite://db/default.sqlite3"
66
+ end
67
+
68
+ config.gateway :extra do |gw|
69
+ gw.database_url = "sqlite://db/extra.sqlite3"
70
+ end
71
+ end
72
+ RUBY
73
+
74
+ require "hanami/prepare"
75
+
76
+ expect(Hanami.app["db.rom"].gateways[:default]).to be
77
+ expect(Hanami.app["db.rom"].gateways[:extra]).to be
78
+ expect(Hanami.app["db.gateway"]).to be Hanami.app["db.rom"].gateways[:default]
79
+ expect(Hanami.app["db.gateways.default"]).to be Hanami.app["db.rom"].gateways[:default]
80
+ expect(Hanami.app["db.gateways.extra"]).to be Hanami.app["db.rom"].gateways[:extra]
81
+ end
82
+ end
83
+
84
+
85
+
86
+ it "exposes all database URLs as #database_urls on the provider source (for CLI commands)" do
87
+ with_tmp_directory(@dir = Dir.mktmpdir) do
88
+ write "config/app.rb", <<~RUBY
89
+ require "hanami"
90
+
91
+ module TestApp
92
+ class App < Hanami::App
93
+ end
94
+ end
95
+ RUBY
96
+
97
+ write "config/providers/db.rb", <<~RUBY
98
+ Hanami.app.configure_provider :db do
99
+ config.gateway :special do |gw|
100
+ gw.database_url = "sqlite://db/special.sqlite3"
101
+ end
102
+ end
103
+ RUBY
104
+
105
+ ENV["DATABASE_URL"] = "sqlite://db/default.sqlite3"
106
+ ENV["DATABASE_URL__EXTRA"] = "sqlite://db/extra.sqlite3"
107
+
108
+ require "hanami/prepare"
109
+
110
+ database_urls = Hanami.app.container.providers[:db].source.finalize_config.database_urls
111
+
112
+ expect(database_urls).to eq(
113
+ default: "sqlite://db/default.sqlite3",
114
+ extra: "sqlite://db/extra.sqlite3",
115
+ special: "sqlite://db/special.sqlite3"
116
+ )
117
+ end
118
+ end
119
+
120
+ it "applies extensions from the default adapter to explicitly configured gateway adapters" do
121
+ with_tmp_directory(@dir = Dir.mktmpdir) do
122
+ write "config/app.rb", <<~RUBY
123
+ require "hanami"
124
+
125
+ module TestApp
126
+ class App < Hanami::App
127
+ end
128
+ end
129
+ RUBY
130
+
131
+ write "config/providers/db.rb", <<~RUBY
132
+ Hanami.app.configure_provider :db do
133
+ config.adapter :sql do |a|
134
+ a.extension :is_distinct_from
135
+ end
136
+
137
+ config.gateway :default do |gw|
138
+ gw.adapter :sql do |a|
139
+ a.extension :exclude_or_null
140
+ end
141
+ end
142
+ end
143
+ RUBY
144
+
145
+ ENV["DATABASE_URL"] = "sqlite::memory"
146
+ ENV["DATABASE_URL__SPECIAL"] = "sqlite::memory"
147
+
148
+ require "hanami/prepare"
149
+
150
+ expect(Hanami.app["db.gateways.default"].options[:extensions])
151
+ .to eq [:exclude_or_null, :is_distinct_from, :caller_logging, :error_sql, :sql_comments]
152
+
153
+ expect(Hanami.app["db.gateways.special"].options[:extensions])
154
+ .to eq [:is_distinct_from, :caller_logging, :error_sql, :sql_comments]
155
+ end
156
+ end
157
+
158
+ it "combines ROM plugins from the default adapter and all gateways" do
159
+ with_tmp_directory(@dir = Dir.mktmpdir) do
160
+ write "config/app.rb", <<~RUBY
161
+ require "hanami"
162
+
163
+ module TestApp
164
+ class App < Hanami::App
165
+ end
166
+ end
167
+ RUBY
168
+
169
+ write "config/providers/db.rb", <<~RUBY
170
+ Hanami.app.configure_provider :db do
171
+ config.adapter :sql do |a|
172
+ a.plugin command: :associates
173
+ end
174
+
175
+ config.gateway :default do |gw|
176
+ gw.database_url = "sqlite::memory"
177
+ gw.adapter :sql do |a|
178
+ a.plugin relation: :nullify
179
+ end
180
+ end
181
+
182
+ config.gateway :special do |gw|
183
+ gw.adapter :sql do |a|
184
+ a.plugin relation: :pagination
185
+ end
186
+ end
187
+ end
188
+ RUBY
189
+
190
+ ENV["DATABASE_URL"] = "sqlite::memory"
191
+ ENV["DATABASE_URL__SPECIAL"] = "sqlite::memory"
192
+
193
+ require "hanami/prepare"
194
+
195
+ expect(Hanami.app["db.config"].setup.plugins.length).to eq 5
196
+ expect(Hanami.app["db.config"].setup.plugins).to include(
197
+ satisfying { |plugin| plugin.type == :command && plugin.name == :associates },
198
+ satisfying { |plugin| plugin.type == :relation && plugin.name == :nullify },
199
+ satisfying { |plugin| plugin.type == :relation && plugin.name == :pagination },
200
+ satisfying { |plugin| plugin.type == :relation && plugin.name == :auto_restrictions },
201
+ satisfying { |plugin| plugin.type == :relation && plugin.name == :instrumentation }
202
+ )
203
+ end
204
+ end
205
+
206
+ it "configures gateway adapters for their specific database types" do
207
+ with_tmp_directory(@dir = Dir.mktmpdir) do
208
+ write "config/app.rb", <<~RUBY
209
+ require "hanami"
210
+
211
+ module TestApp
212
+ class App < Hanami::App
213
+ end
214
+ end
215
+ RUBY
216
+
217
+ write "config/providers/db.rb", <<~RUBY
218
+ Hanami.app.configure_provider :db do
219
+ config.gateway :default do |gw|
220
+ gw.database_url = "sqlite::memory"
221
+ end
222
+ end
223
+ RUBY
224
+
225
+ ENV["DATABASE_URL"] = "sqlite::memory"
226
+ ENV["DATABASE_URL__SPECIAL"] = "postgres://localhost/database"
227
+
228
+ require "hanami/prepare"
229
+
230
+ # Get the provider source and finalize config, because the tests here aren't set up to handle
231
+ # connections to a running postgres database
232
+ allow(Hanami).to receive(:bundled?).and_call_original
233
+ allow(Hanami).to receive(:bundled?).with("pg").and_return true
234
+ provider_source = Hanami.app.container.providers[:db].source
235
+ provider_source.finalize_config
236
+
237
+ expect(provider_source.config.gateways[:default].config.adapter.extensions)
238
+ .to eq [:caller_logging, :error_sql, :sql_comments]
239
+
240
+ expect(provider_source.config.gateways[:special].config.adapter.extensions)
241
+ .to eq [
242
+ :caller_logging, :error_sql, :sql_comments,
243
+ :pg_array, :pg_enum, :pg_json, :pg_range
244
+ ]
245
+ end
246
+ end
247
+
248
+ it "makes the gateways available to relations" do
249
+ with_tmp_directory(@dir = Dir.mktmpdir) do
250
+ write "config/app.rb", <<~RUBY
251
+ require "hanami"
252
+
253
+ module TestApp
254
+ class App < Hanami::App
255
+ config.logger.stream = File::NULL
256
+ end
257
+ end
258
+ RUBY
259
+
260
+ write "app/relations/posts.rb", <<~RUBY
261
+ module TestApp
262
+ module Relations
263
+ class Posts < Hanami::DB::Relation
264
+ schema :posts, infer: true
265
+ end
266
+ end
267
+ end
268
+ RUBY
269
+
270
+ write "app/relations/users.rb", <<~RUBY
271
+ module TestApp
272
+ module Relations
273
+ class Users < Hanami::DB::Relation
274
+ gateway :extra
275
+ schema :users, infer: true
276
+ end
277
+ end
278
+ end
279
+ RUBY
280
+
281
+ write "db/.keep", ""
282
+ ENV["DATABASE_URL"] = "sqlite://db/default.sqlite3"
283
+ ENV["DATABASE_URL__EXTRA"] = "sqlite://db/extra.sqlite3"
284
+
285
+ require "hanami/prepare"
286
+
287
+ Hanami.app.prepare :db
288
+
289
+ # Manually run a migration and add a test record
290
+ default_gateway = Hanami.app["db.gateways.default"]
291
+ migration = default_gateway.migration do
292
+ change do
293
+ create_table :posts do
294
+ primary_key :id
295
+ column :title, :text, null: false
296
+ end
297
+ end
298
+ end
299
+ migration.apply(default_gateway, :up)
300
+ default_gateway.connection.execute("INSERT INTO posts (title) VALUES ('Together breakfast')")
301
+
302
+ extra_gateway = Hanami.app["db.gateways.extra"]
303
+ migration = extra_gateway.migration do
304
+ change do
305
+ create_table :users do
306
+ primary_key :id
307
+ column :name, :text, null: false
308
+ end
309
+ end
310
+ end
311
+ migration.apply(extra_gateway, :up)
312
+ extra_gateway.connection.execute("INSERT INTO users (name) VALUES ('Jane Doe')")
313
+
314
+ Hanami.app.boot
315
+
316
+ expect(Hanami.app["relations.posts"].to_a).to eq [{id: 1, title: "Together breakfast"}]
317
+ expect(Hanami.app["relations.users"].to_a).to eq [{id: 1, name: "Jane Doe"}]
318
+ end
319
+ end
320
+ end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "DB / Logging", :app_integration do
4
+ before do
5
+ @env = ENV.to_h
6
+ allow(Hanami::Env).to receive(:loaded?).and_return(false)
7
+ end
8
+
9
+ after do
10
+ ENV.replace(@env)
11
+ end
12
+
13
+ it "logs SQL queries" do
14
+ with_tmp_directory(Dir.mktmpdir) do
15
+ write "config/app.rb", <<~RUBY
16
+ require "hanami"
17
+
18
+ module TestApp
19
+ class App < Hanami::App
20
+ end
21
+ end
22
+ RUBY
23
+
24
+ write "app/relations/posts.rb", <<~RUBY
25
+ module TestApp
26
+ module Relations
27
+ class Posts < Hanami::DB::Relation
28
+ schema :posts, infer: true
29
+ end
30
+ end
31
+ end
32
+ RUBY
33
+
34
+ ENV["DATABASE_URL"] = "sqlite::memory"
35
+
36
+ require "hanami/setup"
37
+
38
+ logger_stream = StringIO.new
39
+ Hanami.app.config.logger.stream = logger_stream
40
+
41
+ require "hanami/prepare"
42
+
43
+ Hanami.app.prepare :db
44
+
45
+ # Manually run a migration and add a test record
46
+ gateway = Hanami.app["db.gateway"]
47
+ migration = gateway.migration do
48
+ change do
49
+ create_table :posts do
50
+ primary_key :id
51
+ column :title, :text, null: false
52
+ end
53
+
54
+ create_table :authors do
55
+ primary_key :id
56
+ end
57
+ end
58
+ end
59
+ migration.apply(gateway, :up)
60
+ gateway.connection.execute("INSERT INTO posts (title) VALUES ('Together breakfast')")
61
+
62
+ relation = Hanami.app["relations.posts"]
63
+ expect(relation.select(:title).to_a).to eq [{:title=>"Together breakfast"}]
64
+
65
+ logger_stream.rewind
66
+ log_lines = logger_stream.read.split("\n")
67
+
68
+ expect(log_lines.length).to eq 1
69
+ expect(log_lines.first).to match /Loaded :sqlite in \d+ms SELECT `posts`.`title` FROM `posts` ORDER BY `posts`.`id`/
70
+ end
71
+ end
72
+
73
+ describe "slices sharing app db config" do
74
+ it "logs SQL queries" do
75
+ with_tmp_directory(Dir.mktmpdir) do
76
+ write "config/app.rb", <<~RUBY
77
+ require "hanami"
78
+
79
+ module TestApp
80
+ class App < Hanami::App
81
+ end
82
+ end
83
+ RUBY
84
+
85
+ write "config/providers/db.rb", <<~RUBY
86
+ Hanami.app.configure_provider :db do
87
+ end
88
+ RUBY
89
+
90
+ ENV["DATABASE_URL"] = "sqlite::memory"
91
+
92
+ write "slices/admin/relations/posts.rb", <<~RUBY
93
+ module Admin
94
+ module Relations
95
+ class Posts < Hanami::DB::Relation
96
+ schema :posts, infer: true
97
+ end
98
+ end
99
+ end
100
+ RUBY
101
+
102
+ write "slices/main/relations/posts.rb", <<~RUBY
103
+ module Main
104
+ module Relations
105
+ class Posts < Hanami::DB::Relation
106
+ schema :posts, infer: true
107
+ end
108
+ end
109
+ end
110
+ RUBY
111
+
112
+ require "hanami/setup"
113
+
114
+ logger_stream = StringIO.new
115
+ Hanami.app.config.logger.stream = logger_stream
116
+
117
+ require "hanami/prepare"
118
+
119
+ Admin::Slice.prepare :db
120
+
121
+ # Manually run a migration
122
+ gateway = Admin::Slice["db.gateway"]
123
+ migration = gateway.migration do
124
+ change do
125
+ create_table :posts do
126
+ primary_key :id
127
+ column :title, :text, null: false
128
+ end
129
+ end
130
+ end
131
+ migration.apply(gateway, :up)
132
+ gateway.connection.execute("INSERT INTO posts (title) VALUES ('Together breakfast')")
133
+
134
+ Hanami.app.boot
135
+
136
+ # Booting the app will have the ROM schemas infer themself via some `PRAGMA` queries to the
137
+ # database, which themselves will emit logs. Clear those logs, since we want to focus on
138
+ # individual query logging in this test.
139
+ logger_stream.truncate(0)
140
+
141
+ relation = Admin::Slice["relations.posts"]
142
+ relation.select(:title).to_a
143
+
144
+ log_lines = logger_stream.string.split("\n")
145
+ expect(log_lines.length).to eq 1
146
+ expect(log_lines.last).to match /Loaded :sqlite in \d+ms SELECT `posts`.`title` FROM `posts` ORDER BY `posts`.`id`/
147
+
148
+ relation = Main::Slice["relations.posts"]
149
+ relation.select(:id).to_a
150
+
151
+ log_lines = logger_stream.string.split("\n")
152
+ expect(log_lines.length).to eq 2
153
+ expect(log_lines.last).to match /Loaded :sqlite in \d+ms SELECT `posts`.`id` FROM `posts` ORDER BY `posts`.`id`/
154
+ end
155
+ end
156
+ end
157
+
158
+ describe "slices with independent db config" do
159
+ # This is the same test as above, except without the config/providers/db.rb file.
160
+ it "logs SQL queries" do
161
+ with_tmp_directory(Dir.mktmpdir) do
162
+ write "config/app.rb", <<~RUBY
163
+ require "hanami"
164
+
165
+ module TestApp
166
+ class App < Hanami::App
167
+ end
168
+ end
169
+ RUBY
170
+
171
+ ENV["DATABASE_URL"] = "sqlite::memory"
172
+
173
+ write "slices/admin/relations/posts.rb", <<~RUBY
174
+ module Admin
175
+ module Relations
176
+ class Posts < Hanami::DB::Relation
177
+ schema :posts, infer: true
178
+ end
179
+ end
180
+ end
181
+ RUBY
182
+
183
+ write "slices/main/relations/posts.rb", <<~RUBY
184
+ module Main
185
+ module Relations
186
+ class Posts < Hanami::DB::Relation
187
+ schema :posts, infer: true
188
+ end
189
+ end
190
+ end
191
+ RUBY
192
+
193
+ require "hanami/setup"
194
+
195
+ logger_stream = StringIO.new
196
+ Hanami.app.config.logger.stream = logger_stream
197
+
198
+ require "hanami/prepare"
199
+
200
+ Admin::Slice.prepare :db
201
+
202
+ # Manually run a migration
203
+ gateway = Admin::Slice["db.gateway"]
204
+ migration = gateway.migration do
205
+ change do
206
+ create_table :posts do
207
+ primary_key :id
208
+ column :title, :text, null: false
209
+ end
210
+ end
211
+ end
212
+ migration.apply(gateway, :up)
213
+ gateway.connection.execute("INSERT INTO posts (title) VALUES ('Together breakfast')")
214
+
215
+ Hanami.app.boot
216
+
217
+ # Booting the app will have the ROM schemas infer themself via some `PRAGMA` queries to the
218
+ # database, which themselves will emit logs. Clear those logs, since we want to focus on
219
+ # individual query logging in this test.
220
+ logger_stream.truncate(0)
221
+
222
+ relation = Admin::Slice["relations.posts"]
223
+ relation.select(:title).to_a
224
+
225
+ log_lines = logger_stream.string.split("\n")
226
+ expect(log_lines.length).to eq 1
227
+ expect(log_lines.last).to match /Loaded :sqlite in \d+ms SELECT `posts`.`title` FROM `posts` ORDER BY `posts`.`id`/
228
+
229
+ relation = Main::Slice["relations.posts"]
230
+ relation.select(:id).to_a
231
+
232
+ log_lines = logger_stream.string.split("\n")
233
+ expect(log_lines.length).to eq 2
234
+ expect(log_lines.last).to match /Loaded :sqlite in \d+ms SELECT `posts`.`id` FROM `posts` ORDER BY `posts`.`id`/
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "DB / Mappers", :app_integration do
4
+ before do
5
+ @env = ENV.to_h
6
+ allow(Hanami::Env).to receive(:loaded?).and_return(false)
7
+ end
8
+
9
+ after do
10
+ ENV.replace(@env)
11
+ end
12
+
13
+ it "registers custom mappers" do
14
+ with_tmp_directory(@dir = Dir.mktmpdir) do
15
+ write "config/app.rb", <<~RUBY
16
+ require "hanami"
17
+
18
+ module TestApp
19
+ class App < Hanami::App
20
+ config.logger.stream = File::NULL
21
+ end
22
+ end
23
+ RUBY
24
+
25
+ write "app/relations/posts.rb", <<~RUBY
26
+ module TestApp
27
+ module Relations
28
+ class Posts < Hanami::DB::Relation
29
+ schema :posts, infer: true
30
+ end
31
+ end
32
+ end
33
+ RUBY
34
+
35
+ write "app/db/mappers/nested/default_title_mapper.rb", <<~RUBY
36
+ require "rom/transformer"
37
+
38
+ module TestApp
39
+ module DB
40
+ module Mappers
41
+ module Nested
42
+ class DefaultTitleMapper < ROM::Transformer
43
+ relation :posts
44
+ register_as :default_title_mapper
45
+
46
+ map do
47
+ set_default_title
48
+ end
49
+
50
+ def set_default_title(row)
51
+ row[:title] ||= "Default title from mapper"
52
+ row
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ RUBY
60
+
61
+ ENV["DATABASE_URL"] = "sqlite::memory"
62
+
63
+ require "hanami/prepare"
64
+
65
+ Hanami.app.prepare :db
66
+
67
+ # Manually run a migration and add a test record
68
+ gateway = TestApp::App["db.gateway"]
69
+ migration = gateway.migration do
70
+ change do
71
+ create_table :posts do
72
+ primary_key :id
73
+ column :title, :text
74
+ end
75
+ end
76
+ end
77
+ migration.apply(gateway, :up)
78
+ gateway.connection.execute("INSERT INTO posts (title) VALUES (NULL)")
79
+
80
+ post = TestApp::App["relations.posts"].map_with(:default_title_mapper).to_a[0]
81
+ expect(post[:title]).to eq "Default title from mapper"
82
+ end
83
+ end
84
+ end