hanami 2.1.1 → 2.2.0.beta2

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 (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