superform 0.5.1 → 0.6.0

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.
data/README.md CHANGED
@@ -1,17 +1,25 @@
1
1
  # Superform
2
2
 
3
- Superform aims to be the best way to build forms in Rails applications. Here's what it does differently.
3
+ **The best Rails form library.** Whether you're using ERB, HAML, or Phlex, Superform makes building forms delightful.
4
4
 
5
- * **Everything is a component.** Superform is built on top of [Phlex](https://phlex.fun), so every bit of HTML in the form can be customized to your precise needs. Use it with your own CSS Framework or go crazy customizing every last bit of TailwindCSS.
5
+ * **No more strong parameters headaches.** Add a field to your form and it automatically gets permitted. Never again wonder why your new field isn't saving. Superform handles parameter security for you.
6
6
 
7
- * **Automatic strong parameters.** Superform automatically permits form fields so you don't have to facepalm yourself after adding a field, wondering why it doesn't persist, only to realize you forgot to add the parameter to your controller. No more! Superform was architected with safety & security in mind, meaning it can automatically permit your form parameters.
7
+ * **Works beautifully with ERB.** Start using Superform in your existing Rails app without changing a single ERB template. All the power, zero migration pain.
8
8
 
9
- * **Compose complex forms with Plain 'ol Ruby Objects.** Superform is built on top of POROs, so you can easily compose classes, modules, & ruby code together to create complex forms. You can even extend forms to create new forms with a different look and feel.
9
+ * **Concise field helpers.** `field(:publish_at).date`, `field(:email).email`, `field(:price).number` intuitive methods that generate the right input types with proper validation.
10
10
 
11
- It's a complete rewrite of Rails form's internals that's inspired by Reactive component design patterns.
11
+ * **RESTful controller helpers** Superform's `save` and `save!` methods work exactly like ActiveRecord, making controller code predictable and Rails-like.
12
+
13
+ Superform is a complete reimagining of Rails forms, built on solid Ruby foundations with modern component architecture under the hood.
12
14
 
13
15
  [![Maintainability](https://api.codeclimate.com/v1/badges/0e4dfe2a1ece26e3a59e/maintainability)](https://codeclimate.com/github/rubymonolith/superform/maintainability) [![Ruby](https://github.com/rubymonolith/superform/actions/workflows/main.yml/badge.svg)](https://github.com/rubymonolith/superform/actions/workflows/main.yml)
14
16
 
17
+ ## Video course
18
+
19
+ Support this project and [become a Superform pro](https://beautifulruby.com/phlex/forms/overview) by ordering the [Phlex on Rails video course](https://beautifulruby.com/phlex).
20
+
21
+ [![](https://immutable.terminalwire.com/NgTt6nzO1aEnExV8j6ODuKt2iZpY74ZF8ecpUSCp4A0tXA0ErpJIS4cdMX0tQQKOWwZSl65jWnpzpgCLJThhhWtZJGr42XKt7WIi.png)](https://beautifulruby.com/phlex/forms/overview)
22
+
15
23
  ## Installation
16
24
 
17
25
  Add to the Rails application's Gemfile by executing:
@@ -26,36 +34,148 @@ This will install both Phlex Rails and Superform.
26
34
 
27
35
  ## Usage
28
36
 
29
- Superform streamlines the development of forms on Rails applications by making everything a component.
37
+ ### Start with inline forms in your ERB templates
38
+
39
+ Superform works instantly in your existing Rails ERB templates. Here's what a form for a blog post might look like:
40
+
41
+ ```erb
42
+ <!-- app/views/posts/new.html.erb -->
43
+ <h1>New Post</h1>
44
+
45
+ <%= render Components::Form.new @post do
46
+ it.Field(:title).text
47
+ it.Field(:body).textarea
48
+ it.Field(:publish_at).date
49
+ it.Field(:featured).checkbox
50
+ it.submit "Create Post"
51
+ end %>
52
+ ```
53
+
54
+ The form automatically generates proper Rails form tags, includes CSRF tokens, and handles validation errors.
55
+
56
+ Notice anything missing? Superform doesn't need `<% %>` tags around every single line, unlike all other Rails form helpers.
30
57
 
31
- After installing, create a form in `app/views/*/form.rb`. For example, a form for a `Post` resource might look like this.
58
+ ### Extract inline forms to dedicated classes to use in other views
59
+
60
+ You probably want to use the same form for creating and editing resources. In Superform, you extract forms into their own Ruby classes right along with your views.
32
61
 
33
62
  ```ruby
34
- # ./app/views/posts/form.rb
35
- class Posts::Form < ApplicationForm
36
- def view_template(&)
37
- labeled field(:title).input
38
- labeled field(:body).textarea
39
- labeled field(:blog).select Blog.select(:id, :title)
63
+ # app/views/posts/form.rb
64
+ class Posts::Form < Components::Form
65
+ def view_template
66
+ Field(:title).text
67
+ Field(:body).textarea(rows: 10)
68
+ Field(:publish_at).date
69
+ Field(:featured).checkbox
70
+ submit
40
71
  end
41
72
  end
42
73
  ```
43
74
 
44
- Then render it in your templates. Here's what it looks like from an Erb file.
75
+ Then render this in your views:
45
76
 
46
77
  ```erb
47
- <h1>New post</h1>
78
+ <!-- app/views/posts/new.html.erb -->
79
+ <h1>New Post</h1>
48
80
  <%= render Posts::Form.new @post %>
49
81
  ```
50
82
 
83
+ Cool, but you're about to score a huge benefit from extracting forms into their own Ruby classes with automatic strong parameters.
84
+
85
+ ### Automatically permit strong parameters with form classes
86
+
87
+ Include `Superform::Rails::StrongParameters` in your controllers for automatic parameter handling:
88
+
89
+ ```ruby
90
+ class PostsController < ApplicationController
91
+ include Superform::Rails::StrongParameters
92
+
93
+ def create
94
+ @post = Post.new
95
+ if save Posts::Form.new(@post)
96
+ redirect_to @post, notice: 'Post created!'
97
+ else
98
+ render :new, status: :unprocessable_entity
99
+ end
100
+ end
101
+
102
+ def update
103
+ @post = Post.find(params[:id])
104
+ if save Posts::Form.new(@post)
105
+ redirect_to @post, notice: 'Post updated!'
106
+ else
107
+ render :edit, status: :unprocessable_entity
108
+ end
109
+ end
110
+ end
111
+ ```
112
+
113
+ The `save` method automatically:
114
+ - Permits only the parameters defined in your form
115
+ - Assigns them to your model
116
+ - Attempts to save the model
117
+ - Returns `true` if successful, `false` if validation fails
118
+
119
+ Use `save!` for the bang version that raises exceptions on validation failure or `permit` if you want to assign parameters to a model without saving it.
120
+
121
+ ### Concise HTML5 form helpers
122
+
123
+ Superform includes helpers for all HTML5 input types:
124
+
125
+ ```ruby
126
+ class UserForm < Components::Form
127
+ def view_template
128
+ Field(:email).email # type="email"
129
+ Field(:password).password # type="password"
130
+ Field(:website).url # type="url"
131
+ Field(:phone).tel # type="tel"
132
+ Field(:age).number(min: 18) # type="number"
133
+ Field(:birthday).date # type="date"
134
+ Field(:appointment).datetime # type="datetime-local"
135
+ Field(:favorite_color).color # type="color"
136
+ Field(:bio).textarea(rows: 5)
137
+ Field(:terms).checkbox
138
+ submit
139
+ end
140
+ end
141
+ ```
142
+
143
+ ### Works great with Phlex
144
+
145
+ Superform was built from the ground-up using Phlex components, which means you'll feel right at home using it with your existing Phlex views and components.
146
+
147
+ ```ruby
148
+ class Posts::Form < Components::Form
149
+ def view_template
150
+ div(class: "form-section") do
151
+ h2 { "Post Details" }
152
+ Field(:title).text(class: "form-control")
153
+ Field(:body).textarea(class: "form-control", rows: 10)
154
+ end
155
+
156
+ div(class: "form-section") do
157
+ h2 { "Publishing" }
158
+ Field(:publish_at).date(class: "form-control")
159
+ Field(:featured).checkbox(class: "form-check-input")
160
+ end
161
+
162
+ div(class: "form-actions") do
163
+ submit "Save Post", class: "btn btn-primary"
164
+ end
165
+ end
166
+ end
167
+ ```
168
+
169
+ This gives you complete control over markup, styling, and component composition while maintaining all the strong parameter and validation benefits.
170
+
51
171
  ## Customization
52
172
 
53
- Superforms are built out of [Phlex components](https://www.phlex.fun/html/components/). The method names correspeond with the HTML tag, its arguments are attributes, and the blocks are the contents of the tag.
173
+ Superforms are built out of [Phlex components](https://www.phlex.fun/html/components/). The method names correspond with the HTML tag, its arguments are attributes, and the blocks are the contents of the tag.
54
174
 
55
175
  ```ruby
56
- # ./app/views/forms/application_form.rb
57
- class ApplicationForm < Superform::Rails::Form
58
- class MyInputComponent < Superform::Rails::Components::InputComponent
176
+ # ./app/components/form.rb
177
+ class Components::Form < Superform::Rails::Form
178
+ class MyInput < Superform::Rails::Components::Input
59
179
  def view_template(&)
60
180
  div class: "form-field" do
61
181
  input(**attributes)
@@ -66,7 +186,7 @@ class ApplicationForm < Superform::Rails::Form
66
186
  # Redefining the base Field class lets us override every field component.
67
187
  class Field < Superform::Rails::Form::Field
68
188
  def input(**attributes)
69
- MyInputComponent.new(self, attributes: attributes)
189
+ MyInput.new(self, attributes: attributes)
70
190
  end
71
191
  end
72
192
 
@@ -89,7 +209,7 @@ That looks like a LOT of code, and it is, but look at how easy it is to create f
89
209
 
90
210
  ```ruby
91
211
  # ./app/views/users/form.rb
92
- class Users::Form < ApplicationForm
212
+ class Users::Form < Components::Form
93
213
  def view_template(&)
94
214
  labeled field(:name).input
95
215
  labeled field(:email).input(type: :email)
@@ -119,19 +239,19 @@ class AccountForm < Superform::Rails::Form
119
239
  # Account#owner returns a single object
120
240
  namespace :owner do |owner|
121
241
  # Renders input with the name `account[owner][name]`
122
- render owner.field(:name).input
242
+ owner.Field(:name).text
123
243
  # Renders input with the name `account[owner][email]`
124
- render owner.field(:email).input(type: :email)
244
+ owner.Field(:email).email
125
245
  end
126
246
 
127
247
  # Account#members returns a collection of objects
128
248
  collection(:members).each do |member|
129
249
  # Renders input with the name `account[members][0][name]`,
130
250
  # `account[members][1][name]`, ...
131
- render member.field(:name).input
251
+ member.Field(:name).input
132
252
  # Renders input with the name `account[members][0][email]`,
133
253
  # `account[members][1][email]`, ...
134
- render member.field(:email).input(type: :email)
254
+ member.Field(:email).input(type: :email)
135
255
 
136
256
  # Member#permissions returns an array of values like
137
257
  # ["read", "write", "delete"].
@@ -183,7 +303,7 @@ class UserForm < Superform::Rails::Form
183
303
  def view_template
184
304
  render field(:email).input
185
305
  end
186
-
306
+
187
307
  def key
188
308
  "user"
189
309
  end
@@ -204,13 +324,13 @@ In practice, many of the calls below you'd put inside of a method. This cuts dow
204
324
 
205
325
  ```ruby
206
326
  # Everything below is intentionally verbose!
207
- class SignupForm < ApplicationForm
327
+ class SignupForm < Components::Form
208
328
  def view_template
209
329
  # The most basic type of input, which will be autofocused.
210
- render field(:name).input.focus
330
+ Field(:name).input.focus
211
331
 
212
332
  # Input field with a lot more options on it.
213
- render field(:email).input(type: :email, placeholder: "We will sell this to third parties", required: true)
333
+ Field(:email).input(type: :email, placeholder: "We will sell this to third parties", required: true)
214
334
 
215
335
  # You can put fields in a block if that's your thing.
216
336
  field(:reason) do |f|
@@ -222,8 +342,8 @@ class SignupForm < ApplicationForm
222
342
 
223
343
  # Let's get crazy with Selects. They can accept values as simple as 2 element arrays.
224
344
  div do
225
- render field(:contact).label { "Would you like us to spam you to death?" }
226
- render field(:contact).select(
345
+ Field(:contact).label { "Would you like us to spam you to death?" }
346
+ Field(:contact).select(
227
347
  [true, "Yes"], # <option value="true">Yes</option>
228
348
  [false, "No"], # <option value="false">No</option>
229
349
  "Hell no", # <option value="Hell no">Hell no</option>
@@ -232,8 +352,8 @@ class SignupForm < ApplicationForm
232
352
  end
233
353
 
234
354
  div do
235
- render field(:source).label { "How did you hear about us?" }
236
- render field(:source).select do |s|
355
+ Field(:source).label { "How did you hear about us?" }
356
+ Field(:source).select do |s|
237
357
  # Renders a blank option.
238
358
  s.blank_option
239
359
  # Pretend WebSources is an ActiveRecord scope with a "Social" category that has "Facebook, X, etc"
@@ -247,8 +367,8 @@ class SignupForm < ApplicationForm
247
367
  end
248
368
 
249
369
  div do
250
- render field(:agreement).label { "Check this box if you agree to give us your first born child" }
251
- render field(:agreement).checkbox(checked: true)
370
+ Field(:agreement).label { "Check this box if you agree to give us your first born child" }
371
+ Field(:agreement).checkbox(checked: true)
252
372
  end
253
373
 
254
374
  render button { "Submit" }
@@ -260,12 +380,12 @@ end
260
380
  If you want to add file upload fields to your form you will need to initialize your form with the `enctype` attribute set to `multipart/form-data` as shown in the following example code:
261
381
 
262
382
  ```ruby
263
- class User::ImageForm < ApplicationForm
383
+ class User::ImageForm < Components::Form
264
384
  def view_template
265
385
  # render label
266
- render field(:image).label { "Choose file" }
386
+ Field(:image).label { "Choose file" }
267
387
  # render file input with accept attribute for png and jpeg images
268
- render field(:image).input(type: "file", accept: "image/png, image/jpeg")
388
+ Field(:image).input(type: "file", accept: "image/png, image/jpeg")
269
389
  end
270
390
  end
271
391
 
@@ -280,8 +400,8 @@ render User::ImageForm.new(@usermodel, enctype: "multipart/form-data")
280
400
  The best part? If you have forms with a completely different look and feel, you can extend the forms just like you would a Ruby class:
281
401
 
282
402
  ```ruby
283
- class AdminForm < ApplicationForm
284
- class AdminInput < ApplicationComponent
403
+ class AdminForm < Components::Form
404
+ class AdminInput < Components::Base
285
405
  def view_template(&)
286
406
  input(**attributes)
287
407
  small { admin_tool_tip_for field.key }
@@ -309,41 +429,50 @@ class Admin::Users::Form < AdminForm
309
429
  end
310
430
  ```
311
431
 
312
- Since Superforms are just Ruby objects, you can organize them however you want. You can keep your view component classes embedded in your Superform file if you prefer for everything to be in one place, keep the forms in the `app/views/forms/*.rb` folder and the components in `app/views/forms/**/*_component.rb`, use Ruby's `include` and `extend` features to modify different form classes, or put them in a gem and share them with an entire organization or open source community. It's just Ruby code!
432
+ Since Superforms are just Ruby objects, you can organize them however you want. You can keep your view component classes embedded in your Superform file if you prefer for everything to be in one place, keep the forms in the `app/components/forms/*.rb` folder and the components in `app/components/forms/**/*_component.rb`, use Ruby's `include` and `extend` features to modify different form classes, or put them in a gem and share them with an entire organization or open source community. It's just Ruby code!
313
433
 
314
434
  ## Automatic strong parameters
315
435
 
316
- Guess what? Superform eliminates the need for Strong Parameters in Rails by assigning the values of the `params` hash _through_ your form via the `assign` method. Here's what it looks like.
436
+ Superform eliminates the need to manually define strong parameters. Just include `Superform::Rails::StrongParameters` in your controllers and use the `save`, `save!`, and `permit` methods:
317
437
 
318
438
  ```ruby
319
439
  class PostsController < ApplicationController
320
440
  include Superform::Rails::StrongParameters
321
441
 
442
+ # Standard Rails CRUD with automatic strong parameters
322
443
  def create
323
- @post = assign params.require(:post), to: Posts::Form.new(Post.new)
324
-
325
- if @post.save
326
- # Success path
444
+ @post = Post.new
445
+ if save Posts::Form.new(@post)
446
+ redirect_to @post, notice: 'Post created successfully.'
327
447
  else
328
- # Error path
448
+ render :new, status: :unprocessable_entity
329
449
  end
330
450
  end
331
451
 
332
452
  def update
333
453
  @post = Post.find(params[:id])
334
-
335
- assign params.require(:post), to: Posts::Form.new(@post)
336
-
337
- if @post.save
338
- # Success path
454
+ if save Posts::Form.new(@post)
455
+ redirect_to @post, notice: 'Post updated successfully.'
339
456
  else
340
- # Error path
457
+ render :edit, status: :unprocessable_entity
341
458
  end
342
459
  end
460
+
461
+ # For cases where you want to assign params without saving
462
+ def preview
463
+ @post = Post.new
464
+ permit Posts::Form.new(@post) # Assigns params but doesn't save
465
+ render :preview
466
+ end
343
467
  end
344
468
  ```
345
469
 
346
- How does it work? An instance of the form is created, then the hash is assigned to it. If the params include data outside of what a form accepts, it will be ignored.
470
+ **How it works:** Superform automatically permits only the parameters that correspond to fields defined in your form. Attempts to mass-assign other parameters are safely ignored, protecting against parameter pollution attacks.
471
+
472
+ **Available methods:**
473
+ - `save(form)` - Assigns permitted params and saves the model, returns `true`/`false`
474
+ - `save!(form)` - Same as `save` but raises exception on validation failure
475
+ - `permit(form)` - Assigns permitted params without saving, returns the model
347
476
 
348
477
  ## Comparisons
349
478
 
@@ -0,0 +1,146 @@
1
+ # Spec Style Guide
2
+
3
+ This document outlines the preferred testing patterns for Superform. Follow these patterns to maintain consistency and readability across the test suite.
4
+
5
+ ## Generator Testing
6
+
7
+ ### ✅ Preferred Pattern
8
+
9
+ Use integration-style testing that actually runs the generator:
10
+
11
+ ```ruby
12
+ RSpec.describe SomeGenerator, type: :generator do
13
+ let(:destination_root) { Dir.mktmpdir }
14
+ let(:generator) { described_class.new([], {}, { destination_root: destination_root }) }
15
+
16
+ before do
17
+ FileUtils.mkdir_p(destination_root)
18
+ allow(Rails).to receive(:root).and_return(Pathname.new(destination_root))
19
+ end
20
+
21
+ after do
22
+ FileUtils.rm_rf(destination_root) if File.exist?(destination_root)
23
+ end
24
+
25
+ context "when dependencies are met" do
26
+ before do
27
+ allow(generator).to receive(:gem_in_bundle?).with("some-gem").and_return(true)
28
+ end
29
+
30
+ it "creates the expected file" do
31
+ generator.invoke_all
32
+ expect(File.exist?(File.join(destination_root, "path/to/file.rb"))).to be true
33
+ end
34
+
35
+ describe "generated file" do
36
+ subject { File.read(File.join(destination_root, "path/to/file.rb")) }
37
+
38
+ before { generator.invoke_all }
39
+
40
+ it { is_expected.to include("class SomeClass") }
41
+ it { is_expected.to include("def some_method") }
42
+ end
43
+ end
44
+ end
45
+ ```
46
+
47
+ ### ❌ Avoid This Pattern
48
+
49
+ Don't unit test individual generator methods in isolation:
50
+
51
+ ```ruby
52
+ # DON'T DO THIS
53
+ it "creates file with correct content" do
54
+ generator.create_some_file
55
+
56
+ content = File.read(file_path)
57
+ expect(content).to include("class SomeClass")
58
+ expect(content).to include("def some_method")
59
+ expect(content).to include("def another_method")
60
+ # ... more expectations
61
+ end
62
+ ```
63
+
64
+ ## File Content Testing
65
+
66
+ ### ✅ Use Subject Blocks
67
+
68
+ When testing generated file content, use `subject` blocks with `is_expected` matchers:
69
+
70
+ ```ruby
71
+ describe "generated file" do
72
+ subject { File.read(file_path) }
73
+
74
+ before { run_generator_or_setup }
75
+
76
+ it { is_expected.to include("essential content") }
77
+ it { is_expected.to include("other essential content") }
78
+ end
79
+ ```
80
+
81
+ ### ❌ Don't Repeat File Reading
82
+
83
+ Avoid reading the same file multiple times in different tests:
84
+
85
+ ```ruby
86
+ # DON'T DO THIS
87
+ it "includes class definition" do
88
+ content = File.read(file_path)
89
+ expect(content).to include("class SomeClass")
90
+ end
91
+
92
+ it "includes method definition" do
93
+ content = File.read(file_path) # Reading same file again
94
+ expect(content).to include("def some_method")
95
+ end
96
+ ```
97
+
98
+ ## Test Focus
99
+
100
+ ### ✅ Test What Matters
101
+
102
+ Focus on essential functionality, not implementation details:
103
+
104
+ ```ruby
105
+ # Test the important stuff
106
+ it { is_expected.to include("class Base < Superform::Rails::Form") }
107
+ it { is_expected.to include("def row(component)") }
108
+ ```
109
+
110
+ ### ❌ Don't Over-Test
111
+
112
+ Avoid brittle, line-by-line assertions:
113
+
114
+ ```ruby
115
+ # DON'T DO THIS - too brittle
116
+ expect(lines[0]).to eq("module Components")
117
+ expect(lines[1]).to eq(" module Forms")
118
+ expect(lines[2]).to eq(" class Base < Superform::Rails::Form")
119
+ ```
120
+
121
+ ## Test Structure
122
+
123
+ ### ✅ Good Test Organization
124
+
125
+ - Use contexts to group related scenarios
126
+ - Use descriptive test names
127
+ - Keep tests focused and minimal
128
+ - Use `before` blocks to set up common state
129
+
130
+ ### ✅ Integration Over Unit
131
+
132
+ - Test generators by actually running them
133
+ - Test the user experience, not internal methods
134
+ - Mock external dependencies, not internal logic
135
+
136
+ ## Key Principles
137
+
138
+ 1. **Integration over Unit**: Test how components work together, not in isolation
139
+ 2. **User Experience**: Test what users actually do and see
140
+ 3. **Essential over Exhaustive**: Test what matters, not every edge case
141
+ 4. **Readable over Clever**: Clear, simple tests are better than complex ones
142
+ 5. **DRY but Clear**: Eliminate repetition without sacrificing readability
143
+
144
+ ## Example: Good Generator Spec
145
+
146
+ See `spec/generators/superform/install_generator_spec.rb` for a complete example of these patterns in action.
@@ -1,8 +1,12 @@
1
1
  Description:
2
- Installs Phlex Rails and Superform
2
+ Installs Superform with proper component structure
3
3
 
4
4
  Example:
5
5
  bin/rails generate superform:install
6
6
 
7
7
  This will create:
8
- app/views/forms/application_form.rb
8
+ app/components/form.rb
9
+
10
+ Prerequisites:
11
+ phlex-rails must be installed. If not installed, run:
12
+ bundle add phlex-rails
@@ -3,31 +3,20 @@ require "bundler"
3
3
  class Superform::InstallGenerator < Rails::Generators::Base
4
4
  source_root File.expand_path("templates", __dir__)
5
5
 
6
- APPLICATION_CONFIGURATION_PATH = Rails.root.join("config/application.rb")
7
-
8
- def install_phlex_rails
9
- return if gem_in_bundle? "phlex-rails"
10
-
11
- gem "phlex-rails"
12
- generate "phlex:install"
13
- end
14
-
15
- def autoload_components
16
- return unless APPLICATION_CONFIGURATION_PATH.exist?
17
-
18
- inject_into_class(
19
- APPLICATION_CONFIGURATION_PATH,
20
- "Application",
21
- %( config.autoload_paths << "\#{root}/app/views/forms"\n)
22
- )
6
+ def check_phlex_rails_dependency
7
+ unless gem_in_bundle?("phlex-rails")
8
+ say "ERROR: phlex-rails is not installed. Please run 'bundle add phlex-rails' first.", :red
9
+ exit 1
10
+ end
23
11
  end
24
12
 
25
13
  def create_application_form
26
- template "application_form.rb", Rails.root.join("app/views/forms/application_form.rb")
14
+ template "base.rb", Rails.root.join("app/components/form.rb")
27
15
  end
28
16
 
29
17
  private
30
- def gem_in_bundle?(gem_name)
31
- Bundler.load.specs.any? { |spec| spec.name == gem_name }
32
- end
33
- end
18
+
19
+ def gem_in_bundle?(gem_name)
20
+ Bundler.load.specs.any? { |spec| spec.name == gem_name }
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ module Components
2
+ class Form < Superform::Rails::Form
3
+ include Phlex::Rails::Helpers::Pluralize
4
+
5
+ def row(component)
6
+ div do
7
+ render component.field.label(style: "display: block;")
8
+ render component
9
+ end
10
+ end
11
+
12
+ def around_template(&)
13
+ super do
14
+ error_messages
15
+ yield if block_given?
16
+ submit
17
+ end
18
+ end
19
+
20
+ def error_messages
21
+ if model.errors.any?
22
+ div(style: "color: red;") do
23
+ h2 { "#{pluralize model.errors.count, "error"} prohibited this post from being saved:" }
24
+ ul do
25
+ model.errors.each do |error|
26
+ li { error.full_message }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end