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
@@ -12,7 +12,7 @@ RSpec.describe "Slices / Slice configuration", :app_integration do
12
12
  class App < Hanami::App
13
13
  config.logger.stream = StringIO.new
14
14
 
15
- config.no_auto_register_paths << "structs"
15
+ config.no_auto_register_paths = ["structs"]
16
16
  end
17
17
  end
18
18
  RUBY
@@ -34,9 +34,9 @@ RSpec.describe "Slices / Slice configuration", :app_integration do
34
34
 
35
35
  require "hanami/prepare"
36
36
 
37
- expect(TestApp::App.config.no_auto_register_paths).to eq %w[entities structs]
38
- expect(Main::Slice.config.no_auto_register_paths).to eq %w[entities structs schemas]
39
- expect(Search::Slice.config.no_auto_register_paths).to eq %w[entities structs]
37
+ expect(TestApp::App.config.no_auto_register_paths).to eq %w[structs]
38
+ expect(Main::Slice.config.no_auto_register_paths).to eq %w[structs schemas]
39
+ expect(Search::Slice.config.no_auto_register_paths).to eq %w[structs]
40
40
  end
41
41
  end
42
42
  end
@@ -32,7 +32,7 @@ RSpec.describe "App view / Config / Template", :app_integration do
32
32
  subject(:template) { view_class.config.template }
33
33
  let(:view_class) { TestApp::Views::Article::Index }
34
34
 
35
- it "configures the tempalte to match the class name" do
35
+ it "configures the template to match the class name" do
36
36
  expect(template).to eq "article/index"
37
37
  end
38
38
  end
@@ -31,7 +31,7 @@ RSpec.describe "App view / Context / Request", :app_integration do
31
31
  end
32
32
  end
33
33
 
34
- describe "#sesion" do
34
+ describe "#session" do
35
35
  let(:session) { double(:session) }
36
36
 
37
37
  before do
@@ -118,6 +118,9 @@ RSpec.configure do |config|
118
118
  Hanami.instance_variable_set(:@_bundled, {})
119
119
  Hanami.remove_instance_variable(:@_app) if Hanami.instance_variable_defined?(:@_app)
120
120
 
121
+ # Clear cached DB gateways across slices
122
+ Hanami::Providers::DB.cache.clear
123
+
121
124
  $LOAD_PATH.replace(@load_paths)
122
125
 
123
126
  # Remove example-specific LOADED_FEATURES added when running each example
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/config"
4
+
5
+ RSpec.describe Hanami::Config, "#db" do
6
+ let(:config) { described_class.new(app_name: app_name, env: :development) }
7
+ let(:app_name) { "MyApp::App" }
8
+
9
+ subject(:db) { config.db }
10
+
11
+ context "hanami-router is bundled" do
12
+ it "is a full router configuration" do
13
+ is_expected.to be_an_instance_of(Hanami::Config::DB)
14
+
15
+ is_expected.to respond_to(:import_from_parent)
16
+ end
17
+
18
+ it "can be finalized" do
19
+ is_expected.to respond_to(:finalize!)
20
+ end
21
+ end
22
+
23
+ context "hanami-db is not bundled" do
24
+ before do
25
+ allow(Hanami).to receive(:bundled?).and_call_original
26
+ allow(Hanami).to receive(:bundled?).with("hanami-db").and_return(false)
27
+ end
28
+
29
+ it "does not expose any settings" do
30
+ is_expected.to be_an_instance_of(Hanami::Config::NullConfig)
31
+ is_expected.not_to respond_to(:import_from_parent)
32
+ end
33
+
34
+ it "can be finalized" do
35
+ is_expected.to respond_to(:finalize!)
36
+ end
37
+ end
38
+ end
@@ -29,7 +29,7 @@ RSpec.describe Hanami::Config, "#router" do
29
29
  context "hanami-router is not bundled" do
30
30
  before do
31
31
  allow(Hanami).to receive(:bundled?).and_call_original
32
- expect(Hanami).to receive(:bundled?).with("hanami-router").and_return(false)
32
+ allow(Hanami).to receive(:bundled?).with("hanami-router").and_return(false)
33
33
  end
34
34
 
35
35
  it "does not expose any settings" do
@@ -80,8 +80,8 @@ RSpec.describe Hanami::Helpers::FormHelper do
80
80
  end
81
81
 
82
82
  it "allows to specify HTML attributes" do
83
- html = form_for("/books", class: "form-horizonal")
84
- expect(html).to eq %(<form action="/books" class="form-horizonal" accept-charset="utf-8" method="POST"></form>)
83
+ html = form_for("/books", class: "form-horizontal")
84
+ expect(html).to eq %(<form action="/books" class="form-horizontal" accept-charset="utf-8" method="POST"></form>)
85
85
  end
86
86
 
87
87
  context "input name" do
@@ -393,6 +393,29 @@ RSpec.describe Hanami::Helpers::FormHelper do
393
393
 
394
394
  expect(html).to eq_html(expected)
395
395
  end
396
+
397
+ context "with base name" do
398
+ let(:params) { {book: {categories: [{name: "foo"}, {name: "bar"}]}} }
399
+
400
+ it "renders" do
401
+ html = render(<<~ERB)
402
+ <%= form_for("book", "/books") do |f| %>
403
+ <% f.fields_for_collection "categories" do |fa| %>
404
+ <%= fa.text_field :name %>
405
+ <% end %>
406
+ <% end %>
407
+ ERB
408
+
409
+ expected = <<~HTML
410
+ <form action="/books" accept-charset="utf-8" method="POST">
411
+ <input type="text" name="book[categories][][name]" id="book-categories-0-name" value="foo">
412
+ <input type="text" name="book[categories][][name]" id="book-categories-1-name" value="bar">
413
+ </form>
414
+ HTML
415
+
416
+ expect(html).to eq_html(expected)
417
+ end
418
+ end
396
419
  end
397
420
 
398
421
  describe "#label" do
@@ -404,6 +427,14 @@ RSpec.describe Hanami::Helpers::FormHelper do
404
427
  expect(html).to include %(<label for="book-free-shipping">Free shipping</label>)
405
428
  end
406
429
 
430
+ it "accepts a symbol" do
431
+ html = form_for("/books") do |f|
432
+ f.label :free_shipping
433
+ end
434
+
435
+ expect(html).to include %(<label for="free-shipping">Free shipping</label>)
436
+ end
437
+
407
438
  it "accepts a string as custom content" do
408
439
  html = form_for("/books") do |f|
409
440
  f.label "Free Shipping!", for: "book.free_shipping"
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system"
4
+ require "hanami/providers/db"
5
+
6
+ RSpec.describe "Hanami::Providers::DB / Config / Default config", :app_integration do
7
+ subject(:config) { provider.source.config }
8
+
9
+ let(:provider) {
10
+ Hanami.app.configure_provider(:db)
11
+ Hanami.app.container.providers[:db]
12
+ }
13
+
14
+ before do
15
+ module TestApp
16
+ class App < Hanami::App
17
+ end
18
+ end
19
+ end
20
+
21
+ specify "database_url = nil" do
22
+ expect(config.database_url).to be nil
23
+ end
24
+
25
+ specify "adapter = :sql" do
26
+ expect(config.adapter).to eq :sql
27
+ end
28
+
29
+ specify %(relations_path = "relations") do
30
+ expect(config)
31
+ end
32
+
33
+ describe "sql adapter" do
34
+ before do
35
+ skip_defaults if respond_to?(:skip_defaults)
36
+ config.adapter(:sql).configure_for_database("mysql://localhost/test_app_development")
37
+ end
38
+
39
+ describe "plugins" do
40
+ specify do
41
+ expect(config.adapter(:sql).plugins).to match [
42
+ [{relations: :instrumentation}, instance_of(Proc)],
43
+ [{relations: :auto_restrictions}, nil],
44
+ ]
45
+ end
46
+
47
+ describe "skipping defaults" do
48
+ def skip_defaults
49
+ config.adapter(:sql).skip_defaults :plugins
50
+ end
51
+
52
+ it "configures no plugins" do
53
+ expect(config.adapter(:sql).plugins).to eq []
54
+ end
55
+ end
56
+ end
57
+
58
+ describe "extensions" do
59
+ specify do
60
+ expect(config.adapter(:sql).extensions).to eq [
61
+ :caller_logging,
62
+ :error_sql,
63
+ :sql_comments
64
+ ]
65
+ end
66
+
67
+ describe "skipping defaults" do
68
+ def skip_defaults
69
+ config.adapter(:sql).skip_defaults :extensions
70
+ end
71
+
72
+ it "configures no extensions" do
73
+ expect(config.adapter(:sql).extensions).to eq []
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "skipping all defaults" do
79
+ def skip_defaults
80
+ config.adapter(:sql).skip_defaults
81
+ end
82
+
83
+ it "configures no plugins or extensions" do
84
+ expect(config.adapter(:sql).plugins).to eq []
85
+ expect(config.adapter(:sql).extensions).to eq []
86
+ end
87
+ end
88
+ end
89
+
90
+ describe "sql adapter for postgres" do
91
+ before do
92
+ config.adapter(:sql).configure_for_database("postgresql://localhost/test_app_development")
93
+ end
94
+
95
+ specify "extensions" do
96
+ expect(config.adapters[:sql].extensions).to eq [
97
+ :caller_logging,
98
+ :error_sql,
99
+ :sql_comments,
100
+ :pg_array,
101
+ :pg_enum,
102
+ :pg_json,
103
+ :pg_range
104
+ ]
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system"
4
+ require "hanami/providers/db"
5
+
6
+ RSpec.describe "Hanami::Providers::DB.config", :app_integration do
7
+ subject(:config) { provider.source.config }
8
+
9
+ let(:provider) {
10
+ Hanami.app.configure_provider(:db)
11
+ Hanami.app.container.providers[:db]
12
+ }
13
+
14
+ before do
15
+ module TestApp
16
+ class App < Hanami::App
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "#adapter_name" do
22
+ it "aliases #adapter" do
23
+ expect { config.adapter = :yaml }
24
+ .to change { config.adapter_name }
25
+ .to :yaml
26
+ end
27
+ end
28
+
29
+ describe "#adapter" do
30
+ it "adds an adapter" do
31
+ expect { config.adapter(:yaml) }
32
+ .to change { config.adapters.to_h }
33
+ .to hash_including(:yaml)
34
+ end
35
+
36
+ it "yields the adapter for configuration" do
37
+ expect { |b| config.adapter(:yaml, &b) }
38
+ .to yield_with_args(an_instance_of(Hanami::Providers::DB::Adapter))
39
+ end
40
+ end
41
+
42
+ describe "#any_adapter" do
43
+ it "adds an adapter keyed without a name" do
44
+ expect { config.any_adapter }
45
+ .to change { config.adapters.to_h }
46
+ .to hash_including(nil)
47
+ end
48
+
49
+ it "yields the adapter for configuration" do
50
+ expect { |b| config.any_adapter(&b) }
51
+ .to yield_with_args(an_instance_of(Hanami::Providers::DB::Adapter))
52
+ end
53
+ end
54
+
55
+ describe "adapters" do
56
+ subject(:adapter) { config.adapter(:yaml) }
57
+
58
+ describe "#plugin" do
59
+ it "adds a plugin without a block" do
60
+ expect { adapter.plugin relations: :foo }
61
+ .to change { adapter.plugins }
62
+ .to [[{relations: :foo}, nil]]
63
+ end
64
+
65
+ it "adds a plugin with a block" do
66
+ block = -> plugin_config { }
67
+
68
+ expect {
69
+ adapter.plugin(relations: :foo, &block)
70
+ }
71
+ .to change { adapter.plugins }
72
+ .to [[{relations: :foo}, block]]
73
+ end
74
+ end
75
+
76
+ describe "#plugins" do
77
+ it "can be cleared" do
78
+ adapter.plugin relations: :foo
79
+
80
+ expect { adapter.plugins.clear }
81
+ .to change { adapter.plugins }
82
+ .to []
83
+ end
84
+ end
85
+
86
+ describe "#gateway_cache_keys" do
87
+ it "includes the configured extensions" do
88
+ expect(adapter.gateway_cache_keys).to eq({})
89
+ end
90
+ end
91
+
92
+ describe "#gateway_options" do
93
+ specify do
94
+ expect(adapter.gateway_options).to eq({})
95
+ end
96
+ end
97
+
98
+ describe "#clear" do
99
+ it "clears previously configured plugins" do
100
+ adapter.plugin relations: :foo
101
+
102
+ expect { adapter.clear }.to change { adapter.plugins }.to([])
103
+ end
104
+ end
105
+
106
+ describe ":sql adapter" do
107
+ subject(:adapter) { config.adapter(:sql) }
108
+
109
+ describe "#extension" do
110
+ it "adds an extension" do
111
+ adapter.clear
112
+ expect { adapter.extension :foo }
113
+ .to change { adapter.extensions }
114
+ .to [:foo]
115
+ end
116
+
117
+ it "adds multiple extensions" do
118
+ adapter.clear
119
+ expect { adapter.extension :foo, :bar }
120
+ .to change { adapter.extensions }
121
+ .to [:foo, :bar]
122
+ end
123
+ end
124
+
125
+ describe "#extensions" do
126
+ it "can be cleareed" do
127
+ adapter.extension :foo
128
+
129
+ expect { adapter.extensions.clear }
130
+ .to change { adapter.extensions }
131
+ .to []
132
+ end
133
+ end
134
+
135
+ describe "#gateway_cache_keys" do
136
+ it "includes the configured extensions" do
137
+ adapter.clear
138
+ adapter.extension :foo, :bar
139
+ expect(adapter.gateway_cache_keys).to eq(extensions: [:foo, :bar])
140
+ end
141
+ end
142
+
143
+ describe "#gateway_options" do
144
+ it "includes the configured extensions" do
145
+ adapter.clear
146
+ adapter.extension :foo, :bar
147
+ expect(adapter.gateway_options).to eq(extensions: [:foo, :bar])
148
+ end
149
+ end
150
+
151
+ describe "#clear" do
152
+ it "clears previously configured plugins and extensions" do
153
+ adapter.plugin relations: :foo
154
+ adapter.extension :foo
155
+
156
+ expect { adapter.clear }
157
+ .to change { adapter.plugins }.to([])
158
+ .and change { adapter.extensions }.to([])
159
+ end
160
+ end
161
+
162
+ # TODO clear
163
+ end
164
+ end
165
+
166
+ describe "#gateway_cache_keys" do
167
+ it "returns the cache keys from the currently configured adapter" do
168
+ config.adapter(:sql) { |a| a.clear; a.extension :foo }
169
+ config.adapter = :sql
170
+
171
+ expect(config.gateway_cache_keys).to eq(config.adapter(:sql).gateway_cache_keys)
172
+ end
173
+ end
174
+
175
+ describe "#gateway_options" do
176
+ it "returns the options from the currently configured adapter" do
177
+ config.adapter(:sql) { |a| a.clear; a.extension :foo }
178
+ config.adapter = :sql
179
+
180
+ expect(config.gateway_options).to eq(config.adapter(:sql).gateway_options)
181
+ end
182
+ end
183
+
184
+ describe "#each_plugin" do
185
+ before do
186
+ config.any_adapter { |a| a.plugin relations: :any_foo }
187
+ config.adapter(:yaml) { |a| a.plugin relations: :yaml_foo }
188
+ config.adapter = :yaml
189
+ end
190
+
191
+ it "yields the plugins specified for any adapter as well as the currently configured adapter" do
192
+ expect { |b| config.each_plugin(&b) }
193
+ .to yield_successive_args(
194
+ [{relations: :any_foo}, nil],
195
+ [{relations: :yaml_foo}, nil]
196
+ )
197
+ end
198
+
199
+ it "returns the plugins as an enumerator if no block is given" do
200
+ expect(config.each_plugin.to_a).to eq [
201
+ [{relations: :any_foo}, nil],
202
+ [{relations: :yaml_foo}, nil]
203
+ ]
204
+ end
205
+ end
206
+ end
@@ -8,7 +8,27 @@ RSpec.describe Hanami::Slice, :app_integration do
8
8
  end
9
9
  end
10
10
 
11
- describe ".environemnt" do
11
+ describe ".app" do
12
+ subject(:slice) { Hanami.app.register_slice(:main) }
13
+
14
+ it "returns the top-level Hanami App slice" do
15
+ expect(slice.app).to eq Hanami.app
16
+ end
17
+ end
18
+
19
+ describe ".app?" do
20
+ it "returns true if the slice is Hanami.app" do
21
+ subject = Hanami.app
22
+ expect(subject.app?).to eq true
23
+ end
24
+
25
+ it "returns false if the slice is not Hanami.app" do
26
+ subject = Hanami.app.register_slice(:main)
27
+ expect(subject.app?).to eq false
28
+ end
29
+ end
30
+
31
+ describe ".environment" do
12
32
  subject(:slice) { Hanami.app.register_slice(:main) }
13
33
 
14
34
  before do
@@ -46,4 +66,16 @@ RSpec.describe Hanami::Slice, :app_integration do
46
66
  .to raise_error Hanami::SliceLoadError, /Slice must have a class name/
47
67
  end
48
68
  end
69
+
70
+ describe ".source_path" do
71
+ it "provides a path to the app directory for Hanami.app" do
72
+ subject = Hanami.app
73
+ expect(subject.source_path).to eq Hanami.app.root.join("app")
74
+ end
75
+
76
+ it "provides a path to the slice root for a Slice" do
77
+ subject = Hanami.app.register_slice(:main)
78
+ expect(subject.source_path).to eq subject.root
79
+ end
80
+ end
49
81
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  RSpec.describe "Hanami::VERSION" do
4
4
  it "returns current version" do
5
- expect(Hanami::VERSION).to eq("2.1.0")
5
+ expect(Hanami::VERSION).to eq("2.2.0.beta1")
6
6
  end
7
7
  end