hanami 2.1.0 → 2.2.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -6
- data/FEATURES.md +1 -1
- data/README.md +7 -7
- data/hanami.gemspec +6 -6
- data/lib/hanami/app.rb +6 -2
- data/lib/hanami/config/actions.rb +1 -1
- data/lib/hanami/config/assets.rb +1 -1
- data/lib/hanami/config/db.rb +33 -0
- data/lib/hanami/config/logger.rb +2 -2
- data/lib/hanami/config.rb +37 -10
- data/lib/hanami/extensions/db/repo.rb +103 -0
- data/lib/hanami/extensions/view/context.rb +1 -1
- data/lib/hanami/extensions/view/part.rb +1 -1
- data/lib/hanami/extensions/view/slice_configured_helpers.rb +1 -1
- data/lib/hanami/extensions.rb +4 -0
- data/lib/hanami/helpers/assets_helper.rb +5 -5
- data/lib/hanami/helpers/form_helper/form_builder.rb +4 -6
- data/lib/hanami/middleware/public_errors_app.rb +2 -2
- data/lib/hanami/provider_registrar.rb +26 -0
- data/lib/hanami/providers/assets.rb +2 -20
- data/lib/hanami/providers/db/adapter.rb +68 -0
- data/lib/hanami/providers/db/adapters.rb +44 -0
- data/lib/hanami/providers/db/config.rb +66 -0
- data/lib/hanami/providers/db/sql_adapter.rb +80 -0
- data/lib/hanami/providers/db.rb +203 -0
- data/lib/hanami/providers/db_logging.rb +22 -0
- data/lib/hanami/providers/rack.rb +3 -3
- data/lib/hanami/providers/relations.rb +31 -0
- data/lib/hanami/providers/routes.rb +1 -13
- data/lib/hanami/rake_tasks.rb +9 -8
- data/lib/hanami/settings.rb +3 -3
- data/lib/hanami/slice.rb +90 -10
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami/web/rack_logger.rb +3 -3
- data/lib/hanami.rb +3 -0
- data/spec/integration/container/provider_environment_spec.rb +52 -0
- data/spec/integration/db/auto_registration_spec.rb +39 -0
- data/spec/integration/db/db_inflector_spec.rb +57 -0
- data/spec/integration/db/db_slices_spec.rb +327 -0
- data/spec/integration/db/db_spec.rb +220 -0
- data/spec/integration/db/logging_spec.rb +238 -0
- data/spec/integration/db/provider_config_spec.rb +88 -0
- data/spec/integration/db/provider_spec.rb +35 -0
- data/spec/integration/db/repo_spec.rb +215 -0
- data/spec/integration/db/slices_importing_from_parent.rb +130 -0
- data/spec/integration/slices/slice_configuration_spec.rb +4 -4
- data/spec/integration/view/config/template_spec.rb +1 -1
- data/spec/integration/view/context/request_spec.rb +1 -1
- data/spec/support/app_integration.rb +3 -0
- data/spec/unit/hanami/config/db_spec.rb +38 -0
- data/spec/unit/hanami/config/router_spec.rb +1 -1
- data/spec/unit/hanami/helpers/form_helper_spec.rb +33 -2
- data/spec/unit/hanami/providers/db/config/default_config_spec.rb +107 -0
- data/spec/unit/hanami/providers/db/config_spec.rb +206 -0
- data/spec/unit/hanami/slice_spec.rb +33 -1
- data/spec/unit/hanami/version_spec.rb +1 -1
- metadata +62 -20
data/lib/hanami/slice.rb
CHANGED
@@ -101,6 +101,16 @@ module Hanami
|
|
101
101
|
Hanami.app
|
102
102
|
end
|
103
103
|
|
104
|
+
# Returns true if the slice is Hanami.app
|
105
|
+
#
|
106
|
+
# @return [Boolean]
|
107
|
+
#
|
108
|
+
# @api public
|
109
|
+
# @since 2.2.0
|
110
|
+
def app?
|
111
|
+
eql?(app)
|
112
|
+
end
|
113
|
+
|
104
114
|
# Returns the slice's config.
|
105
115
|
#
|
106
116
|
# A slice's config is copied from the app config at time of first access.
|
@@ -189,7 +199,7 @@ module Hanami
|
|
189
199
|
# @api public
|
190
200
|
# @since 2.0.0
|
191
201
|
def root
|
192
|
-
#
|
202
|
+
# Provides a best guess for a root when it is not yet configured.
|
193
203
|
#
|
194
204
|
# This is particularly useful for user-defined slice classes that access `settings` inside
|
195
205
|
# the class body (since the root needed to find the settings file). In this case,
|
@@ -203,6 +213,16 @@ module Hanami
|
|
203
213
|
config.root || app.root.join(SLICES_DIR, slice_name.to_s)
|
204
214
|
end
|
205
215
|
|
216
|
+
# Returns the slice's root component directory, accounting for App as a special case.
|
217
|
+
#
|
218
|
+
# @return [Pathname]
|
219
|
+
#
|
220
|
+
# @api public
|
221
|
+
# @since 2.2.0
|
222
|
+
def source_path
|
223
|
+
app? ? root.join(APP_DIR) : root
|
224
|
+
end
|
225
|
+
|
206
226
|
# Returns the slice's configured inflector.
|
207
227
|
#
|
208
228
|
# Unless explicitly re-configured for the slice, this will be the app's inflector.
|
@@ -265,7 +285,7 @@ module Hanami
|
|
265
285
|
#
|
266
286
|
# @example
|
267
287
|
# module MySlice
|
268
|
-
# class
|
288
|
+
# class Slice < Hanami::Slice
|
269
289
|
# prepare_container do |container|
|
270
290
|
# # ...
|
271
291
|
# end
|
@@ -404,7 +424,7 @@ module Hanami
|
|
404
424
|
# @param key [String] the component's key
|
405
425
|
# @param object [Object] the object to register as the component
|
406
426
|
#
|
407
|
-
# @overload
|
427
|
+
# @overload register(key, memoize: false, &block)
|
408
428
|
# Registers the given block as the component. When the component is resolved, the return
|
409
429
|
# value of the block will be returned.
|
410
430
|
#
|
@@ -422,7 +442,7 @@ module Hanami
|
|
422
442
|
# @param memoize [Boolean]
|
423
443
|
# @yieldreturn [Object] the object to register as the component
|
424
444
|
#
|
425
|
-
# @overload
|
445
|
+
# @overload register(key, call: true, &block)
|
426
446
|
# Registers the given block as the component. When `call: false` is given, then the block
|
427
447
|
# itself will become the component.
|
428
448
|
#
|
@@ -430,7 +450,7 @@ module Hanami
|
|
430
450
|
# object for that block will be returned.
|
431
451
|
#
|
432
452
|
# @param key [String] the component's key
|
433
|
-
# @param call [
|
453
|
+
# @param call [Boolean]
|
434
454
|
#
|
435
455
|
# @return [container]
|
436
456
|
#
|
@@ -493,7 +513,7 @@ module Hanami
|
|
493
513
|
# namespace. May be an explicit string, or `true` for the namespace to be the provider's
|
494
514
|
# name
|
495
515
|
# @param from [Symbol, nil] the group for an external provider source to use, with the
|
496
|
-
# provider source name inferred from `name` or
|
516
|
+
# provider source name inferred from `name` or passed explicitly as `source:`
|
497
517
|
# @param source [Symbol, nil] the name of the external provider source to use, if different
|
498
518
|
# from the value provided as `name`
|
499
519
|
# @param if [Boolean] a boolean-returning expression to determine whether to register the
|
@@ -507,6 +527,12 @@ module Hanami
|
|
507
527
|
container.register_provider(...)
|
508
528
|
end
|
509
529
|
|
530
|
+
# @api public
|
531
|
+
# @since 2.1.0
|
532
|
+
def configure_provider(*args, **kwargs, &block)
|
533
|
+
container.register_provider(*args, **kwargs, from: :hanami, &block)
|
534
|
+
end
|
535
|
+
|
510
536
|
# @overload start(provider_name)
|
511
537
|
# Starts a provider.
|
512
538
|
#
|
@@ -559,6 +585,13 @@ module Hanami
|
|
559
585
|
container.key?(...)
|
560
586
|
end
|
561
587
|
|
588
|
+
# Required for the slice to act as a provider target
|
589
|
+
# @api public
|
590
|
+
# @since 2.2.0
|
591
|
+
def registered?(...)
|
592
|
+
container.registered?(...)
|
593
|
+
end
|
594
|
+
|
562
595
|
# Returns an array of keys for all currently registered components in the container.
|
563
596
|
#
|
564
597
|
# For a prepared slice, this will be the set of components that have been previously resolved.
|
@@ -842,6 +875,7 @@ module Hanami
|
|
842
875
|
def prepare_container_base_config
|
843
876
|
container.config.name = slice_name.to_sym
|
844
877
|
container.config.root = root
|
878
|
+
container.config.provider_registrar = ProviderRegistrar.for_slice(self)
|
845
879
|
container.config.provider_dirs = [File.join("config", "providers")]
|
846
880
|
container.config.registrations_dir = File.join("config", "registrations")
|
847
881
|
|
@@ -893,12 +927,29 @@ module Hanami
|
|
893
927
|
# point we're still in the process of preparing.
|
894
928
|
if routes
|
895
929
|
require_relative "providers/routes"
|
896
|
-
register_provider(:routes, source: Providers::Routes
|
930
|
+
register_provider(:routes, source: Providers::Routes)
|
897
931
|
end
|
898
932
|
|
899
933
|
if assets_dir? && Hanami.bundled?("hanami-assets")
|
900
934
|
require_relative "providers/assets"
|
901
|
-
register_provider(:assets, source: Providers::Assets
|
935
|
+
register_provider(:assets, source: Providers::Assets)
|
936
|
+
end
|
937
|
+
|
938
|
+
if Hanami.bundled?("hanami-db")
|
939
|
+
# Explicit require here to ensure the provider source registers itself, to allow the user
|
940
|
+
# to configure it within their own concrete provider file.
|
941
|
+
require_relative "providers/db"
|
942
|
+
|
943
|
+
if register_db_provider?
|
944
|
+
# Only register providers if the user hasn't provided their own
|
945
|
+
if !container.providers[:db]
|
946
|
+
register_provider(:db, namespace: true, source: Providers::DB)
|
947
|
+
end
|
948
|
+
|
949
|
+
if !container.providers[:relations]
|
950
|
+
register_provider(:relations, namespace: true, source: Providers::Relations)
|
951
|
+
end
|
952
|
+
end
|
902
953
|
end
|
903
954
|
end
|
904
955
|
|
@@ -1028,8 +1079,37 @@ module Hanami
|
|
1028
1079
|
private_constant :ROUTER_NOT_FOUND_HANDLER
|
1029
1080
|
|
1030
1081
|
def assets_dir?
|
1031
|
-
|
1032
|
-
|
1082
|
+
source_path.join("assets").directory?
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
def register_db_provider?
|
1086
|
+
concrete_db_provider? ||
|
1087
|
+
db_config_dir? ||
|
1088
|
+
relations_dir? ||
|
1089
|
+
db_source_dir? ||
|
1090
|
+
import_db_from_parent?
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
def concrete_db_provider?
|
1094
|
+
root.join(CONFIG_DIR, "providers", "db.rb").exist?
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
def db_config_dir?
|
1098
|
+
root.join("config", "db").directory?
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
def relations_dir?
|
1102
|
+
source_path.join("relations").directory?
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
def db_source_dir?
|
1106
|
+
source_path.join("db").directory?
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def import_db_from_parent?
|
1110
|
+
parent &&
|
1111
|
+
config.db.import_from_parent &&
|
1112
|
+
parent.container.providers[:db]
|
1033
1113
|
end
|
1034
1114
|
|
1035
1115
|
# rubocop:enable Metrics/AbcSize
|
data/lib/hanami/version.rb
CHANGED
@@ -35,8 +35,8 @@ module Hanami
|
|
35
35
|
CONTENT_LENGTH = "CONTENT_LENGTH"
|
36
36
|
private_constant :CONTENT_LENGTH
|
37
37
|
|
38
|
-
|
39
|
-
private_constant :
|
38
|
+
MILLISECOND = "ms"
|
39
|
+
private_constant :MILLISECOND
|
40
40
|
|
41
41
|
MICROSECOND = "µs"
|
42
42
|
private_constant :MICROSECOND
|
@@ -79,7 +79,7 @@ module Hanami
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def accepts_entry_payload?(logger)
|
82
|
-
logger.method(:info).parameters.
|
82
|
+
logger.method(:info).parameters.any? { |(type, _)| type == :keyrest }
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
data/lib/hanami.rb
CHANGED
@@ -17,6 +17,9 @@ module Hanami
|
|
17
17
|
# @since 2.0.0
|
18
18
|
def self.loader
|
19
19
|
@loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
|
20
|
+
loader.inflector.inflect "db" => "DB"
|
21
|
+
loader.inflector.inflect "db_logging" => "DBLogging"
|
22
|
+
loader.inflector.inflect "sql_adapter" => "SQLAdapter"
|
20
23
|
loader.ignore(
|
21
24
|
"#{loader.dirs.first}/hanami/{constants,boot,errors,extensions/router/errors,prepare,rake_tasks,setup}.rb"
|
22
25
|
)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe "Container / Provider environment", :app_integration do
|
4
|
+
let!(:app) {
|
5
|
+
module TestApp
|
6
|
+
class App < Hanami::App
|
7
|
+
class << self
|
8
|
+
attr_accessor :test_provider_target
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
before_prepare if respond_to?(:before_prepare)
|
14
|
+
|
15
|
+
Hanami.app.prepare
|
16
|
+
Hanami.app
|
17
|
+
}
|
18
|
+
|
19
|
+
context "app provider" do
|
20
|
+
before do
|
21
|
+
Hanami.app.register_provider :test_provider, namespace: true do
|
22
|
+
start do
|
23
|
+
Hanami.app.test_provider_target = target
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "exposes the app as the provider target" do
|
29
|
+
Hanami.app.start :test_provider
|
30
|
+
expect(Hanami.app.test_provider_target).to be Hanami.app
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "slice provider" do
|
35
|
+
def before_prepare
|
36
|
+
Hanami.app.register_slice :main
|
37
|
+
end
|
38
|
+
|
39
|
+
before do
|
40
|
+
Main::Slice.register_provider :test_provider, namespace: true do
|
41
|
+
start do
|
42
|
+
Hanami.app.test_provider_target = target
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "exposes the slice as the provider target" do
|
48
|
+
Main::Slice.start :test_provider
|
49
|
+
expect(Hanami.app.test_provider_target).to be Main::Slice
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe "DB / auto-registration", :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 "does not auto-register files in entities/, structs/, or db/" 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 "app/db/changesets/update_posts.rb", ""
|
25
|
+
write "app/entities/post.rb", ""
|
26
|
+
write "app/structs/post.rb", ""
|
27
|
+
|
28
|
+
ENV["DATABASE_URL"] = "sqlite::memory"
|
29
|
+
|
30
|
+
require "hanami/boot"
|
31
|
+
|
32
|
+
expect(Hanami.app.keys).not_to include(*[
|
33
|
+
"db.changesets.update_posts",
|
34
|
+
"entities.post",
|
35
|
+
"structs.post"
|
36
|
+
])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/system"
|
4
|
+
|
5
|
+
RSpec.describe "ROM::Inflector", :app_integration do
|
6
|
+
before do
|
7
|
+
@env = ENV.to_h
|
8
|
+
allow(Hanami::Env).to receive(:loaded?).and_return(false)
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
ENV.replace(@env)
|
13
|
+
end
|
14
|
+
|
15
|
+
around :each do |example|
|
16
|
+
inflector = ROM::Inflector
|
17
|
+
ROM.instance_eval {
|
18
|
+
remove_const :Inflector
|
19
|
+
const_set :Inflector, Dry::Inflector.new
|
20
|
+
}
|
21
|
+
example.run
|
22
|
+
ensure
|
23
|
+
ROM.instance_eval {
|
24
|
+
remove_const :Inflector
|
25
|
+
const_set :Inflector, inflector
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
it "replaces ROM::Inflector with the Hanami inflector" do
|
30
|
+
with_tmp_directory(Dir.mktmpdir) do
|
31
|
+
write "config/app.rb", <<~RUBY
|
32
|
+
require "hanami"
|
33
|
+
|
34
|
+
module TestApp
|
35
|
+
class App < Hanami::App
|
36
|
+
end
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
|
40
|
+
write "app/relations/posts.rb", <<~RUBY
|
41
|
+
module TestApp
|
42
|
+
module Relations
|
43
|
+
class Posts < Hanami::DB::Relation
|
44
|
+
schema :posts, infer: true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
RUBY
|
49
|
+
|
50
|
+
ENV["DATABASE_URL"] = "sqlite::memory"
|
51
|
+
|
52
|
+
require "hanami/prepare"
|
53
|
+
|
54
|
+
expect { Hanami.app.prepare :db }.to change { ROM::Inflector == Hanami.app["inflector"] }.from(false).to(true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|