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,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "DB / Provider / Config", :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
+ describe "default config" do
14
+ it "provides default plugins and extensions" do
15
+ with_tmp_directory(Dir.mktmpdir) do
16
+ write "config/app.rb", <<~RUBY
17
+ require "hanami"
18
+
19
+ module TestApp
20
+ class App < Hanami::App
21
+ end
22
+ end
23
+ RUBY
24
+
25
+ write "config/db/.keep", ""
26
+
27
+ ENV["DATABASE_URL"] = "sqlite::memory"
28
+
29
+ require "hanami/prepare"
30
+
31
+ Hanami.app.prepare :db
32
+
33
+ plugins = Hanami.app["db.config"].setup.plugins
34
+ expect(plugins).to match [
35
+ an_object_satisfying {
36
+ _1.name == :instrumentation && _1.type == :relation &&
37
+ _1.config.notifications == Hanami.app["notifications"]
38
+ },
39
+ an_object_satisfying { _1.name == :auto_restrictions && _1.type == :relation }
40
+ ]
41
+
42
+ extensions = Hanami.app["db.gateway"].options[:extensions]
43
+ expect(extensions).to eq [:caller_logging, :error_sql, :sql_comments]
44
+ end
45
+ end
46
+ end
47
+
48
+ it "evaluates plugin config blocks in the context of the provider" do
49
+ with_tmp_directory(Dir.mktmpdir) do
50
+ write "config/app.rb", <<~RUBY
51
+ require "hanami"
52
+
53
+ module TestApp
54
+ class App < Hanami::App
55
+ end
56
+ end
57
+ RUBY
58
+
59
+ write "config/providers/db.rb", <<~RUBY
60
+ Hanami.app.configure_provider :db do
61
+ config.adapter :sql do |a|
62
+ a.skip_defaults :plugins
63
+
64
+ a.plugin relations: :instrumentation do |plugin|
65
+ plugin.notifications = target["custom_notifications"]
66
+ end
67
+ end
68
+ end
69
+ RUBY
70
+
71
+ write "app/custom_notifications.rb", <<~RUBY
72
+ module TestApp
73
+ class CustomNotifications
74
+ end
75
+ end
76
+ RUBY
77
+
78
+ ENV["DATABASE_URL"] = "sqlite::memory"
79
+
80
+ require "hanami/prepare"
81
+
82
+ Hanami.app.prepare :db
83
+
84
+ plugin = Hanami.app["db.config"].setup.plugins.find { _1.name == :instrumentation }
85
+ expect(plugin.config.notifications).to be_an_instance_of TestApp::CustomNotifications
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "DB / Provider", :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 "is registered when only a config/db/ dir exists" 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 "config/db/.keep", ""
25
+
26
+ ENV["DATABASE_URL"] = "sqlite::memory"
27
+
28
+ require "hanami/prepare"
29
+
30
+ Hanami.app.prepare :db
31
+
32
+ expect(Hanami.app["db.gateway"]).to be
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "DB / Relations", :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 nested relations" 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/nested/posts.rb", <<~RUBY
26
+ module TestApp
27
+ module Relations
28
+ module Nested
29
+ class Posts < Hanami::DB::Relation
30
+ schema :posts, infer: true
31
+ end
32
+ end
33
+ end
34
+ end
35
+ RUBY
36
+
37
+ ENV["DATABASE_URL"] = "sqlite::memory"
38
+
39
+ require "hanami/prepare"
40
+
41
+ Hanami.app.prepare :db
42
+
43
+ # Manually run a migration and add a test record
44
+ gateway = TestApp::App["db.gateway"]
45
+ migration = gateway.migration do
46
+ change do
47
+ create_table :posts do
48
+ primary_key :id
49
+ column :title, :text
50
+ end
51
+ end
52
+ end
53
+ migration.apply(gateway, :up)
54
+ gateway.connection.execute("INSERT INTO posts (title) VALUES ('Hi from nested relation')")
55
+
56
+ post = TestApp::App["relations.posts"].to_a[0]
57
+ expect(post[:title]).to eq "Hi from nested relation"
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "DB / Repo", :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
+ specify "repos have a root inferred from their name, or can set their own" 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/repo.rb", <<~RUBY
25
+ module TestApp
26
+ class Repo < Hanami::DB::Repo
27
+ end
28
+ end
29
+ RUBY
30
+
31
+ write "app/relations/posts.rb", <<~RUBY
32
+ module TestApp
33
+ module Relations
34
+ class Posts < Hanami::DB::Relation
35
+ schema :posts, infer: true
36
+ end
37
+ end
38
+ end
39
+ RUBY
40
+
41
+ write "app/repos/post_repo.rb", <<~RUBY
42
+ module TestApp
43
+ module Repos
44
+ class PostRepo < Repo
45
+ def get(id)
46
+ posts.by_pk(id).one!
47
+ end
48
+ end
49
+ end
50
+ end
51
+ RUBY
52
+
53
+ write "app/repos/no_implicit_root_relation_repo.rb", <<~RUBY
54
+ module TestApp
55
+ module Repos
56
+ class NoImplicitRootRelationRepo < Repo
57
+ end
58
+ end
59
+ end
60
+ RUBY
61
+
62
+ write "app/repos/explicit_root_relation_repo.rb", <<~RUBY
63
+ module TestApp
64
+ module Repos
65
+ class ExplicitRootRelationRepo < Repo[:posts]
66
+ end
67
+ end
68
+ end
69
+ RUBY
70
+
71
+ ENV["DATABASE_URL"] = "sqlite::memory"
72
+
73
+ require "hanami/prepare"
74
+
75
+ Hanami.app.prepare :db
76
+
77
+ # Manually run a migration and add a test record
78
+ gateway = Hanami.app["db.gateway"]
79
+ migration = gateway.migration do
80
+ change do
81
+ # drop_table? :posts
82
+ create_table :posts do
83
+ primary_key :id
84
+ column :title, :text, null: false
85
+ end
86
+ end
87
+ end
88
+ migration.apply(gateway, :up)
89
+ gateway.connection.execute("INSERT INTO posts (title) VALUES ('Together breakfast')")
90
+
91
+ # Repos use a matching root relation automatically
92
+ repo = Hanami.app["repos.post_repo"]
93
+ expect(repo.get(1).title).to eq "Together breakfast"
94
+ expect(repo.root).to eql Hanami.app["relations.posts"]
95
+
96
+ # Non-matching repos still work, just with no root relation
97
+ repo = Hanami.app["repos.no_implicit_root_relation_repo"]
98
+ expect(repo.root).to be nil
99
+
100
+ # Repos can provide an explicit root relation
101
+ repo = Hanami.app["repos.explicit_root_relation_repo"]
102
+ expect(repo.root).to eql Hanami.app["relations.posts"]
103
+ end
104
+ end
105
+
106
+ specify "repos use relations and structs only from their own slice" do
107
+ with_tmp_directory(Dir.mktmpdir) do
108
+ write "config/app.rb", <<~RUBY
109
+ require "hanami"
110
+
111
+ module TestApp
112
+ class App < Hanami::App
113
+ end
114
+ end
115
+ RUBY
116
+
117
+ ENV["DATABASE_URL"] = "sqlite::memory"
118
+
119
+ write "slices/admin/db/struct.rb", <<~RUBY
120
+ module Admin
121
+ module DB
122
+ class Struct < Hanami::DB::Struct
123
+ end
124
+ end
125
+ end
126
+ RUBY
127
+
128
+ write "slices/admin/relations/posts.rb", <<~RUBY
129
+ module Admin
130
+ module Relations
131
+ class Posts < Hanami::DB::Relation
132
+ schema :posts, infer: true
133
+ end
134
+ end
135
+ end
136
+ RUBY
137
+
138
+ write "slices/admin/repo.rb", <<~RUBY
139
+ module Admin
140
+ class Repo < Hanami::DB::Repo
141
+ end
142
+ end
143
+ RUBY
144
+
145
+ write "slices/admin/repos/post_repo.rb", <<~RUBY
146
+ module Admin
147
+ module Repos
148
+ class PostRepo < Repo
149
+ end
150
+ end
151
+ end
152
+ RUBY
153
+
154
+ write "slices/admin/structs/post.rb", <<~RUBY
155
+ module Admin
156
+ module Structs
157
+ class Post < DB::Struct
158
+ end
159
+ end
160
+ end
161
+ RUBY
162
+
163
+ write "slices/main/relations/posts.rb", <<~RUBY
164
+ module Main
165
+ module Relations
166
+ class Posts < Hanami::DB::Relation
167
+ schema :posts, infer: true
168
+ end
169
+ end
170
+ end
171
+ RUBY
172
+
173
+ write "slices/main/repo.rb", <<~RUBY
174
+ module Main
175
+ class Repo < Hanami::DB::Repo
176
+ end
177
+ end
178
+ RUBY
179
+
180
+ write "slices/main/repos/post_repo.rb", <<~RUBY
181
+ module Main
182
+ module Repos
183
+ class PostRepo < Repo
184
+ end
185
+ end
186
+ end
187
+ RUBY
188
+
189
+ require "hanami/prepare"
190
+
191
+ Admin::Slice.prepare :db
192
+
193
+ # Manually run a migration
194
+ gateway = Admin::Slice["db.gateway"]
195
+ migration = gateway.migration do
196
+ change do
197
+ # drop_table? :posts
198
+ create_table :posts do
199
+ primary_key :id
200
+ column :title, :text, null: false
201
+ end
202
+ end
203
+ end
204
+ migration.apply(gateway, :up)
205
+ gateway.connection.execute("INSERT INTO posts (title) VALUES ('Together breakfast')")
206
+
207
+ expect(Admin::Slice["repos.post_repo"].posts).to eql Admin::Slice["relations.posts"]
208
+ expect(Admin::Slice["repos.post_repo"].posts.by_pk(1).one!.class).to be < Admin::Structs::Post
209
+
210
+ expect(Main::Slice["repos.post_repo"].posts).to eql Main::Slice["relations.posts"]
211
+ # Slice struct namespace used even when no concrete struct classes are defined
212
+ expect(Main::Slice["repos.post_repo"].posts.by_pk(1).one!.class).to be < Main::Structs::Post
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "DB / Slices / Importing from app", :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
+ specify "app DB components do not import into slices by default" 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
+ write "slices/admin/.keep", ""
35
+
36
+ require "hanami/prepare"
37
+
38
+ expect { Admin::Slice.start :db }.to raise_error Dry::System::ProviderNotFoundError
39
+ end
40
+ end
41
+
42
+ specify "importing app DB components into slices via config.db.import_from_parent = true" do
43
+ with_tmp_directory(Dir.mktmpdir) do
44
+ write "config/app.rb", <<~RUBY
45
+ require "hanami"
46
+
47
+ module TestApp
48
+ class App < Hanami::App
49
+ config.db.import_from_parent = true
50
+ end
51
+ end
52
+ RUBY
53
+
54
+ write "app/relations/posts.rb", <<~RUBY
55
+ module TestApp
56
+ module Relations
57
+ class Posts < Hanami::DB::Relation
58
+ schema :posts, infer: true
59
+ end
60
+ end
61
+ end
62
+ RUBY
63
+
64
+ write "slices/admin/.keep", ""
65
+
66
+ ENV["DATABASE_URL"] = "sqlite::memory"
67
+
68
+ require "hanami/prepare"
69
+
70
+ Hanami.app.prepare :db
71
+
72
+ # Manually run a migration and add a test record
73
+ gateway = Hanami.app["db.gateway"]
74
+ migration = gateway.migration do
75
+ change do
76
+ # drop_table? :posts
77
+ create_table :posts do
78
+ primary_key :id
79
+ column :title, :text, null: false
80
+ end
81
+ end
82
+ end
83
+ migration.apply(gateway, :up)
84
+ gateway.connection.execute("INSERT INTO posts (title) VALUES ('Together breakfast')")
85
+
86
+ Admin::Slice.start :db
87
+
88
+ expect(Admin::Slice["db.rom"]).to be(Hanami.app["db.rom"])
89
+ expect(Admin::Slice["relations.posts"]).to be(Hanami.app["relations.posts"])
90
+
91
+ expect(Admin::Slice["relations.posts"].to_a).to eq [{id: 1, title: "Together breakfast"}]
92
+ end
93
+ end
94
+
95
+ specify "disabling import of the DB components within a specific slice" do
96
+ with_tmp_directory(Dir.mktmpdir) do
97
+ write "config/app.rb", <<~RUBY
98
+ require "hanami"
99
+
100
+ module TestApp
101
+ class App < Hanami::App
102
+ config.db.import_from_parent = true
103
+ end
104
+ end
105
+ RUBY
106
+
107
+ write "config/slices/admin.rb", <<~RUBY
108
+ module Admin
109
+ class Slice < Hanami::Slice
110
+ config.db.import_from_parent = false
111
+ end
112
+ end
113
+ RUBY
114
+
115
+ write "app/relations/posts.rb", <<~RUBY
116
+ module TestApp
117
+ module Relations
118
+ class Posts < Hanami::DB::Relation
119
+ schema :posts, infer: true
120
+ end
121
+ end
122
+ end
123
+ RUBY
124
+
125
+ require "hanami/prepare"
126
+
127
+ expect { Admin::Slice.start :db }.to raise_error Dry::System::ProviderNotFoundError
128
+ end
129
+ end
130
+ end
@@ -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
@@ -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
@@ -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"
@@ -2628,7 +2659,7 @@ RSpec.describe Hanami::Helpers::FormHelper do
2628
2659
  f.select "book.store", option_values, options: {prompt: "Select a store"}
2629
2660
  end
2630
2661
 
2631
- expect(html).to include %(<select name="book[store]" id="book-store"><option disabled="disabled">Select a store</option><option value="it">Italy</option><option value="us">United States</option></select>)
2662
+ expect(html).to include %(<select name="book[store]" id="book-store"><option>Select a store</option><option value="it">Italy</option><option value="us">United States</option></select>)
2632
2663
  end
2633
2664
 
2634
2665
  it "allows blank string" do
@@ -2636,7 +2667,7 @@ RSpec.describe Hanami::Helpers::FormHelper do
2636
2667
  f.select "book.store", option_values, options: {prompt: ""}
2637
2668
  end
2638
2669
 
2639
- expect(html).to include %(<select name="book[store]" id="book-store"><option disabled="disabled"></option><option value="it">Italy</option><option value="us">United States</option></select>)
2670
+ expect(html).to include %(<select name="book[store]" id="book-store"><option></option><option value="it">Italy</option><option value="us">United States</option></select>)
2640
2671
  end
2641
2672
 
2642
2673
  context "with values" do
@@ -2648,7 +2679,7 @@ RSpec.describe Hanami::Helpers::FormHelper do
2648
2679
  f.select "book.store", option_values, options: {prompt: "Select a store"}
2649
2680
  end
2650
2681
 
2651
- expect(html).to include %(<select name="book[store]" id="book-store"><option disabled="disabled">Select a store</option><option value="it" selected="selected">Italy</option><option value="us">United States</option></select>)
2682
+ expect(html).to include %(<select name="book[store]" id="book-store"><option>Select a store</option><option value="it" selected="selected">Italy</option><option value="us">United States</option></select>)
2652
2683
  end
2653
2684
  end
2654
2685
 
@@ -2662,7 +2693,7 @@ RSpec.describe Hanami::Helpers::FormHelper do
2662
2693
  f.select "book.store", option_values, options: {prompt: "Select a store"}
2663
2694
  end
2664
2695
 
2665
- expect(html).to include %(<select name="book[store]" id="book-store"><option disabled="disabled">Select a store</option><option value="it" selected="selected">Italy</option><option value="us">United States</option></select>)
2696
+ expect(html).to include %(<select name="book[store]" id="book-store"><option>Select a store</option><option value="it" selected="selected">Italy</option><option value="us">United States</option></select>)
2666
2697
  end
2667
2698
  end
2668
2699