superform 0.5.0 → 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,49 +34,164 @@ 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
+ ```
30
53
 
31
- After installing, create a form in `app/views/*/form.rb`. For example, a form for a `Post` resource might look like this.
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.
57
+
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 template(&)
37
- row field(:title).input
38
- row field(:body).textarea
39
- row 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
59
- def template(&)
176
+ # ./app/components/form.rb
177
+ class Components::Form < Superform::Rails::Form
178
+ class MyInput < Superform::Rails::Components::Input
179
+ def view_template(&)
60
180
  div class: "form-field" do
61
181
  input(**attributes)
62
182
  end
63
183
  end
64
184
  end
65
185
 
186
+ # Redefining the base Field class lets us override every field component.
66
187
  class Field < Superform::Rails::Form::Field
67
188
  def input(**attributes)
68
- MyInputComponent.new(self, attributes: attributes)
189
+ MyInput.new(self, attributes: attributes)
69
190
  end
70
191
  end
71
192
 
193
+ # Here we make a simple helper to make our syntax shorter. Given a field it
194
+ # will also render its label.
72
195
  def labeled(component)
73
196
  div class: "form-row" do
74
197
  render component.field.label
@@ -86,8 +209,8 @@ That looks like a LOT of code, and it is, but look at how easy it is to create f
86
209
 
87
210
  ```ruby
88
211
  # ./app/views/users/form.rb
89
- class Users::Form < ApplicationForm
90
- def template(&)
212
+ class Users::Form < Components::Form
213
+ def view_template(&)
91
214
  labeled field(:name).input
92
215
  labeled field(:email).input(type: :email)
93
216
 
@@ -112,23 +235,23 @@ Consider a form for an account that lets people edit the names and email of the
112
235
 
113
236
  ```ruby
114
237
  class AccountForm < Superform::Rails::Form
115
- def template
238
+ def view_template
116
239
  # Account#owner returns a single object
117
240
  namespace :owner do |owner|
118
241
  # Renders input with the name `account[owner][name]`
119
- render owner.field(:name).input
242
+ owner.Field(:name).text
120
243
  # Renders input with the name `account[owner][email]`
121
- render owner.field(:email).input(type: :email)
244
+ owner.Field(:email).email
122
245
  end
123
246
 
124
247
  # Account#members returns a collection of objects
125
248
  collection(:members).each do |member|
126
249
  # Renders input with the name `account[members][0][name]`,
127
250
  # `account[members][1][name]`, ...
128
- render member.field(:name).input
251
+ member.Field(:name).input
129
252
  # Renders input with the name `account[members][0][email]`,
130
253
  # `account[members][1][email]`, ...
131
- render member.field(:email).input(type: :email)
254
+ member.Field(:email).input(type: :email)
132
255
 
133
256
  # Member#permissions returns an array of values like
134
257
  # ["read", "write", "delete"].
@@ -161,7 +284,7 @@ By default Superform namespaces a form based on the ActiveModel model name param
161
284
 
162
285
  ```ruby
163
286
  class UserForm < Superform::Rails::Form
164
- def template
287
+ def view_template
165
288
  render field(:email).input
166
289
  end
167
290
  end
@@ -177,10 +300,10 @@ To customize the form namespace, like an ActiveRecord model nested within a modu
177
300
 
178
301
  ```ruby
179
302
  class UserForm < Superform::Rails::Form
180
- def template
303
+ def view_template
181
304
  render field(:email).input
182
305
  end
183
-
306
+
184
307
  def key
185
308
  "user"
186
309
  end
@@ -201,26 +324,26 @@ In practice, many of the calls below you'd put inside of a method. This cuts dow
201
324
 
202
325
  ```ruby
203
326
  # Everything below is intentionally verbose!
204
- class SignupForm < ApplicationForm
205
- def template
327
+ class SignupForm < Components::Form
328
+ def view_template
206
329
  # The most basic type of input, which will be autofocused.
207
- render field(:name).input.focus
330
+ Field(:name).input.focus
208
331
 
209
332
  # Input field with a lot more options on it.
210
- 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)
211
334
 
212
335
  # You can put fields in a block if that's your thing.
213
- render field(:reason) do |f|
336
+ field(:reason) do |f|
214
337
  div do
215
- f.label { "Why should we care about you?" }
216
- f.textarea(row: 3, col: 80)
338
+ render f.label { "Why should we care about you?" }
339
+ render f.textarea(row: 3, col: 80)
217
340
  end
218
341
  end
219
342
 
220
343
  # Let's get crazy with Selects. They can accept values as simple as 2 element arrays.
221
344
  div do
222
- render field(:contact).label { "Would you like us to spam you to death?" }
223
- render field(:contact).select(
345
+ Field(:contact).label { "Would you like us to spam you to death?" }
346
+ Field(:contact).select(
224
347
  [true, "Yes"], # <option value="true">Yes</option>
225
348
  [false, "No"], # <option value="false">No</option>
226
349
  "Hell no", # <option value="Hell no">Hell no</option>
@@ -229,8 +352,10 @@ class SignupForm < ApplicationForm
229
352
  end
230
353
 
231
354
  div do
232
- render field(:source).label { "How did you hear about us?" }
233
- render field(:source).select do |s|
355
+ Field(:source).label { "How did you hear about us?" }
356
+ Field(:source).select do |s|
357
+ # Renders a blank option.
358
+ s.blank_option
234
359
  # Pretend WebSources is an ActiveRecord scope with a "Social" category that has "Facebook, X, etc"
235
360
  # and a "Search" category with "AltaVista, Yahoo, etc."
236
361
  WebSources.select(:id, :name).group_by(:category) do |category, sources|
@@ -242,8 +367,8 @@ class SignupForm < ApplicationForm
242
367
  end
243
368
 
244
369
  div do
245
- render field(:agreement).label { "Check this box if you agree to give us your first born child" }
246
- 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)
247
372
  end
248
373
 
249
374
  render button { "Submit" }
@@ -255,12 +380,12 @@ end
255
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:
256
381
 
257
382
  ```ruby
258
- class User::ImageForm < ApplicationForm
259
- def template
383
+ class User::ImageForm < Components::Form
384
+ def view_template
260
385
  # render label
261
- render field(:image).label { "Choose file" }
386
+ Field(:image).label { "Choose file" }
262
387
  # render file input with accept attribute for png and jpeg images
263
- render field(:image).input(type: "file", accept: "image/png, image/jpeg")
388
+ Field(:image).input(type: "file", accept: "image/png, image/jpeg")
264
389
  end
265
390
  end
266
391
 
@@ -275,9 +400,9 @@ render User::ImageForm.new(@usermodel, enctype: "multipart/form-data")
275
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:
276
401
 
277
402
  ```ruby
278
- class AdminForm < ApplicationForm
279
- class AdminInput < ApplicationComponent
280
- def template(&)
403
+ class AdminForm < Components::Form
404
+ class AdminInput < Components::Base
405
+ def view_template(&)
281
406
  input(**attributes)
282
407
  small { admin_tool_tip_for field.key }
283
408
  end
@@ -295,7 +420,7 @@ Then, just like you did in your Erb, you create the form:
295
420
 
296
421
  ```ruby
297
422
  class Admin::Users::Form < AdminForm
298
- def template(&)
423
+ def view_template(&)
299
424
  labeled field(:name).tooltip_input
300
425
  labeled field(:email).tooltip_input(type: :email)
301
426
 
@@ -304,41 +429,50 @@ class Admin::Users::Form < AdminForm
304
429
  end
305
430
  ```
306
431
 
307
- 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!
308
433
 
309
434
  ## Automatic strong parameters
310
435
 
311
- 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:
312
437
 
313
438
  ```ruby
314
439
  class PostsController < ApplicationController
315
440
  include Superform::Rails::StrongParameters
316
441
 
442
+ # Standard Rails CRUD with automatic strong parameters
317
443
  def create
318
- @post = assign params.require(:post), to: Posts::Form.new(Post.new)
319
-
320
- if @post.save
321
- # Success path
444
+ @post = Post.new
445
+ if save Posts::Form.new(@post)
446
+ redirect_to @post, notice: 'Post created successfully.'
322
447
  else
323
- # Error path
448
+ render :new, status: :unprocessable_entity
324
449
  end
325
450
  end
326
451
 
327
452
  def update
328
453
  @post = Post.find(params[:id])
329
-
330
- assign params.require(:post), to: Posts::Form.new(@post)
331
-
332
- if @post.save
333
- # Success path
454
+ if save Posts::Form.new(@post)
455
+ redirect_to @post, notice: 'Post updated successfully.'
334
456
  else
335
- # Error path
457
+ render :edit, status: :unprocessable_entity
336
458
  end
337
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
338
467
  end
339
468
  ```
340
469
 
341
- 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
342
476
 
343
477
  ## Comparisons
344
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