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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -6
  3. data/FEATURES.md +1 -1
  4. data/README.md +7 -7
  5. data/hanami.gemspec +6 -6
  6. data/lib/hanami/app.rb +6 -2
  7. data/lib/hanami/config/actions.rb +1 -1
  8. data/lib/hanami/config/assets.rb +1 -1
  9. data/lib/hanami/config/db.rb +33 -0
  10. data/lib/hanami/config/logger.rb +2 -2
  11. data/lib/hanami/config.rb +37 -10
  12. data/lib/hanami/extensions/db/repo.rb +103 -0
  13. data/lib/hanami/extensions/view/context.rb +1 -1
  14. data/lib/hanami/extensions/view/part.rb +1 -1
  15. data/lib/hanami/extensions/view/slice_configured_helpers.rb +1 -1
  16. data/lib/hanami/extensions.rb +4 -0
  17. data/lib/hanami/helpers/assets_helper.rb +5 -5
  18. data/lib/hanami/helpers/form_helper/form_builder.rb +4 -6
  19. data/lib/hanami/middleware/public_errors_app.rb +2 -2
  20. data/lib/hanami/provider_registrar.rb +26 -0
  21. data/lib/hanami/providers/assets.rb +2 -20
  22. data/lib/hanami/providers/db/adapter.rb +68 -0
  23. data/lib/hanami/providers/db/adapters.rb +44 -0
  24. data/lib/hanami/providers/db/config.rb +66 -0
  25. data/lib/hanami/providers/db/sql_adapter.rb +80 -0
  26. data/lib/hanami/providers/db.rb +203 -0
  27. data/lib/hanami/providers/db_logging.rb +22 -0
  28. data/lib/hanami/providers/rack.rb +3 -3
  29. data/lib/hanami/providers/relations.rb +31 -0
  30. data/lib/hanami/providers/routes.rb +1 -13
  31. data/lib/hanami/rake_tasks.rb +9 -8
  32. data/lib/hanami/settings.rb +3 -3
  33. data/lib/hanami/slice.rb +90 -10
  34. data/lib/hanami/version.rb +1 -1
  35. data/lib/hanami/web/rack_logger.rb +3 -3
  36. data/lib/hanami.rb +3 -0
  37. data/spec/integration/container/provider_environment_spec.rb +52 -0
  38. data/spec/integration/db/auto_registration_spec.rb +39 -0
  39. data/spec/integration/db/db_inflector_spec.rb +57 -0
  40. data/spec/integration/db/db_slices_spec.rb +327 -0
  41. data/spec/integration/db/db_spec.rb +220 -0
  42. data/spec/integration/db/logging_spec.rb +238 -0
  43. data/spec/integration/db/provider_config_spec.rb +88 -0
  44. data/spec/integration/db/provider_spec.rb +35 -0
  45. data/spec/integration/db/repo_spec.rb +215 -0
  46. data/spec/integration/db/slices_importing_from_parent.rb +130 -0
  47. data/spec/integration/slices/slice_configuration_spec.rb +4 -4
  48. data/spec/integration/view/config/template_spec.rb +1 -1
  49. data/spec/integration/view/context/request_spec.rb +1 -1
  50. data/spec/support/app_integration.rb +3 -0
  51. data/spec/unit/hanami/config/db_spec.rb +38 -0
  52. data/spec/unit/hanami/config/router_spec.rb +1 -1
  53. data/spec/unit/hanami/helpers/form_helper_spec.rb +33 -2
  54. data/spec/unit/hanami/providers/db/config/default_config_spec.rb +107 -0
  55. data/spec/unit/hanami/providers/db/config_spec.rb +206 -0
  56. data/spec/unit/hanami/slice_spec.rb +33 -1
  57. data/spec/unit/hanami/version_spec.rb +1 -1
  58. 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
- # Provide a best guess for a root when it is not yet configured.
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 Sliice < Hanami::Slice
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 reigster(key, memoize: false, &block)
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 reigster(key, call: true, &block)
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 [Booelan]
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 passsed explicitly as `source:`
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.for_slice(self))
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.for_slice(self))
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
- assets_path = app.eql?(self) ? root.join("app", "assets") : root.join("assets")
1032
- assets_path.directory?
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
@@ -7,7 +7,7 @@ module Hanami
7
7
  # @api private
8
8
  module Version
9
9
  # @api public
10
- VERSION = "2.1.0"
10
+ VERSION = "2.2.0.beta1"
11
11
 
12
12
  # @since 0.9.0
13
13
  # @api private
@@ -35,8 +35,8 @@ module Hanami
35
35
  CONTENT_LENGTH = "CONTENT_LENGTH"
36
36
  private_constant :CONTENT_LENGTH
37
37
 
38
- MILISECOND = "ms"
39
- private_constant :MILISECOND
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.last.then { |type, _| type == :keyrest }
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