hanami 2.1.1 → 2.2.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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