action_form 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93e16fca08cba7ced7cf05dfe08e026f45d9ed6fd03d1210de7b82959ee13c13
4
- data.tar.gz: 21034909b984567b9ba419474c8afc296b44c86543d881881b69f2600560743d
3
+ metadata.gz: a83d578e9d0fbf58ccb568473d8a94e1ccb087df4b8f8e5ee2871f2a4689c7b0
4
+ data.tar.gz: e155e0fada05ece59a1e501fe6d6e52ca0cbd652292f0b243e7e59999760562d
5
5
  SHA512:
6
- metadata.gz: 16162478587b84ae6cdccbc3d3f62c01c7b1c9fe2657e8f1f1ec3486df05f30d45798c46700c7790d49c68b8cade708d730dfbea2f57726e1d5e472ed87b9d78
7
- data.tar.gz: 064b00adb4bffe1b124eaf2e0e12f14028fb50648e161a98cfe933c9e19207b40f6e59fa69134018d1a540dce11c7c383a7ccc2781738a6b9aebd7097e9b49c4
6
+ metadata.gz: 91b6b047b9ac52d7e1d9162ed4b555456466fa7f59e94d54242492f2e515ad352f145aa179027d51753ec78ed1f12150628e23ab156a56de29b29998e1ae968a
7
+ data.tar.gz: e64a656d32e8d9ddc91d37d4913c30c513e37854d8f77e368a0acf27e66a1a12a579b8c81bdd4ffd79f4c1ff508850834a256ad93728d762963c8bd317a9c612
data/README.md CHANGED
@@ -8,6 +8,7 @@ This library allows you to build complex forms in Ruby with a simple DSL. It pro
8
8
 
9
9
  - A clean, declarative syntax for defining form fields and validations
10
10
  - Support for nested forms
11
+ - Custom parameter validation with the `params` method
11
12
  - Automatic form rendering with customizable HTML/CSS
12
13
  - Built-in error handling and validation
13
14
  - Integration with Rails and other Ruby web frameworks
@@ -61,6 +62,7 @@ ActionForm is built around a modular architecture that separates form definition
61
62
 
62
63
  - **Declarative DSL**: Define forms with simple, readable syntax
63
64
  - **Nested Forms**: Support for complex nested structures with `subform` and `many`
65
+ - **Custom Parameter Validation**: Use the `params` method to add custom validation logic and schema modifications
64
66
  - **Dynamic Collections**: JavaScript-powered add/remove functionality for many relationships
65
67
  - **Flexible Rendering**: Each element can be configured with custom input types, labels, and HTML attributes
66
68
  - **Error Integration**: Built-in support for displaying validation errors
@@ -86,6 +88,7 @@ ActionForm follows a bidirectional data flow pattern that handles both form disp
86
88
  #### **Key Benefits:**
87
89
  - **Single Source of Truth**: The same form definition handles both displaying existing data and processing new data
88
90
  - **Automatic Parameter Handling**: [EasyParams](https://github.com/andriy-baran/easy_params) classes are automatically generated to mirror your form structure
91
+ - **Custom Parameter Validation**: Use the `params` method to add custom validation logic and schema modifications
89
92
  - **Error Integration**: Failed validations can re-render the form with submitted data and error messages
90
93
  - **Nested Support**: Both phases support complex nested structures through `subform` and `many` relationships
91
94
 
@@ -250,6 +253,374 @@ class UserForm < ActionForm::Base
250
253
  end
251
254
  ```
252
255
 
256
+ ### Custom Parameter Validation
257
+
258
+ ActionForm provides a `params` method that allows you to add custom validation logic and schema modifications to your form's parameter handling. This is particularly useful for complex validation rules that depend on context or require cross-field validation.
259
+
260
+ #### **Basic Usage**
261
+
262
+ Use the `params` method to define custom validation logic:
263
+
264
+ ```ruby
265
+ class UserForm < ActionForm::Base
266
+ element :email do
267
+ input type: :email
268
+ output type: :string, presence: true
269
+ end
270
+
271
+ element :password do
272
+ input type: :password
273
+ output type: :string
274
+ end
275
+
276
+ element :password_confirmation do
277
+ input type: :password
278
+ output type: :string
279
+ end
280
+
281
+ # Custom parameter validation
282
+ params do
283
+ validates :password, presence: true
284
+ validates :password_confirmation, presence: true
285
+ validates :password, confirmation: true
286
+ end
287
+ end
288
+ ```
289
+
290
+ #### **Conditional Validation**
291
+
292
+ You can add conditional validation logic based on context:
293
+
294
+ ```ruby
295
+ class UserForm < ActionForm::Base
296
+ element :email do
297
+ input type: :email
298
+ output type: :string, presence: true
299
+ end
300
+
301
+ element :password do
302
+ input type: :password
303
+ output type: :string
304
+ end
305
+
306
+ # Conditional validation based on external context
307
+ params do
308
+ secure_mode = true # This could come from configuration or request context
309
+
310
+ validates :password, presence: true, if: -> { secure_mode }
311
+ validates :password, length: { minimum: 8 }, if: -> { secure_mode }
312
+ end
313
+ end
314
+ ```
315
+
316
+ #### **Nested Form Validation**
317
+
318
+ The `params` method also supports validation for nested forms using schema blocks:
319
+
320
+ ```ruby
321
+ class UserForm < ActionForm::Base
322
+ element :email do
323
+ input type: :email
324
+ output type: :string, presence: true
325
+ end
326
+
327
+ subform :profile, default: {} do
328
+ element :name do
329
+ input type: :text
330
+ output type: :string
331
+ end
332
+ end
333
+
334
+ many :addresses, default: [{}] do
335
+ subform do
336
+ element :street do
337
+ input type: :text
338
+ output type: :string
339
+ end
340
+
341
+ element :city do
342
+ input type: :text
343
+ output type: :string
344
+ end
345
+ end
346
+ end
347
+
348
+ params do
349
+ # Validate nested subform
350
+ profile_attributes_schema do
351
+ validates :name, presence: true
352
+ end
353
+
354
+ # Validate nested collection
355
+ addresses_attributes_schema do
356
+ validates :street, presence: true
357
+ validates :city, presence: true
358
+ end
359
+ end
360
+ end
361
+ ```
362
+
363
+ #### **Dynamic Form Classes**
364
+
365
+ You can create dynamic form classes with different validation rules:
366
+
367
+ ```ruby
368
+ class BaseUserForm < ActionForm::Base
369
+ element :email do
370
+ input type: :email
371
+ output type: :string, presence: true
372
+ end
373
+
374
+ element :password do
375
+ input type: :password
376
+ output type: :string
377
+ end
378
+ end
379
+
380
+ # Create a secure version with additional validation
381
+ SecureUserForm = Class.new(BaseUserForm)
382
+ SecureUserForm.params do
383
+ validates :password, presence: true, length: { minimum: 8 }
384
+ validates :password, format: { with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/ }
385
+ end
386
+
387
+ # Create a basic version with minimal validation
388
+ BasicUserForm = Class.new(BaseUserForm)
389
+ BasicUserForm.params do
390
+ validates :password, presence: true
391
+ end
392
+ ```
393
+
394
+ #### **Integration with Controllers**
395
+
396
+ The custom parameter validation integrates seamlessly with your controllers:
397
+
398
+ ```ruby
399
+ class UsersController < ApplicationController
400
+ def create
401
+ user_params = UserForm.params_definition.new(params)
402
+
403
+ if user_params.valid?
404
+ @user = User.create!(user_params.to_h)
405
+ redirect_to @user
406
+ else
407
+ @form = @form.with_params(user_params)
408
+ render :new
409
+ end
410
+ end
411
+ end
412
+ ```
413
+
414
+ The `params` method provides a powerful way to extend ActionForm's parameter handling with custom validation logic while maintaining the declarative nature of form definition.
415
+
416
+ ### Modifying Element Definitions
417
+
418
+ ActionForm allows you to modify existing element, subform, and `many` definitions after they have been declared. This feature enables you to extend or customize form definitions without altering the original class structure, making it perfect for conditional modifications, inheritance patterns, and dynamic form customization.
419
+
420
+ #### **Modifying Elements**
421
+
422
+ Use the `{element_name}_element` method to modify an existing element definition:
423
+
424
+ ```ruby
425
+ class UserForm < ActionForm::Base
426
+ element :email do
427
+ input type: :email
428
+ output type: :string
429
+ end
430
+
431
+ element :password do
432
+ input type: :password
433
+ output type: :string
434
+ end
435
+ end
436
+
437
+ # Modify existing element definitions
438
+ UserForm.email_element do
439
+ output type: :string, presence: true, format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
440
+ end
441
+
442
+ UserForm.password_element do
443
+ output type: :string, presence: true, length: { minimum: 8 }
444
+ end
445
+ ```
446
+
447
+ #### **Modifying Subforms**
448
+
449
+ Use the `{subform_name}_subform` method to modify an existing subform definition:
450
+
451
+ ```ruby
452
+ class UserForm < ActionForm::Base
453
+ subform :profile do
454
+ element :bio do
455
+ input type: :textarea
456
+ output type: :string
457
+ end
458
+ end
459
+ end
460
+
461
+ # Modify the subform to add validation or change defaults
462
+ UserForm.profile_subform default: {} do
463
+ bio_element do
464
+ output type: :string, presence: true
465
+ end
466
+
467
+ # You can also add new elements
468
+ element :avatar do
469
+ input type: :file
470
+ output type: :string
471
+ end
472
+ end
473
+ ```
474
+
475
+ #### **Modifying Many Relationships**
476
+
477
+ Use the `{many_name}_subforms` method to modify an existing `many` definition:
478
+
479
+ ```ruby
480
+ class OrderForm < ActionForm::Base
481
+ many :items do
482
+ subform do
483
+ element :name do
484
+ input type: :text
485
+ output type: :string
486
+ end
487
+
488
+ element :quantity do
489
+ input type: :number
490
+ output type: :integer
491
+ end
492
+ end
493
+ end
494
+ end
495
+
496
+ # Modify the many relationship to add validation or change defaults
497
+ OrderForm.items_subforms default: [{}] do
498
+ subform do
499
+ name_element do
500
+ output type: :string, presence: true
501
+ end
502
+
503
+ quantity_element do
504
+ output type: :integer, presence: true, inclusion: { in: 1..100 }
505
+ end
506
+
507
+ # Add new elements to existing many subforms
508
+ element :price do
509
+ input type: :number
510
+ output type: :float, presence: true
511
+ end
512
+ end
513
+ end
514
+ ```
515
+
516
+ #### **Inheritance and Modifications**
517
+
518
+ Element modifications work seamlessly with class inheritance:
519
+
520
+ ```ruby
521
+ class BaseForm < ActionForm::Base
522
+ element :name do
523
+ input type: :text
524
+ output type: :string
525
+ end
526
+ end
527
+
528
+ class UserForm < BaseForm
529
+ element :email do
530
+ input type: :email
531
+ output type: :string
532
+ end
533
+ end
534
+
535
+ # Modify inherited elements
536
+ UserForm.name_element do
537
+ output type: :string, presence: true
538
+ end
539
+
540
+ # Modify elements defined in subclass
541
+ UserForm.email_element do
542
+ output type: :string, presence: true
543
+ end
544
+ ```
545
+
546
+ Here's a complete example showing element modifications in action:
547
+
548
+ ```ruby
549
+ class OrderForm < ActionForm::Base
550
+ element :name do
551
+ input(type: :text)
552
+ output(type: :string)
553
+ end
554
+
555
+ subform :customer do
556
+ element :name do
557
+ input(type: :text)
558
+ output(type: :string)
559
+ end
560
+ end
561
+
562
+ many :items do
563
+ subform do
564
+ element :name do
565
+ input(type: :text)
566
+ output(type: :string)
567
+ end
568
+
569
+ element :quantity do
570
+ input(type: :number)
571
+ output(type: :integer)
572
+ end
573
+
574
+ element :price do
575
+ input(type: :number)
576
+ output(type: :float)
577
+ end
578
+ end
579
+ end
580
+ end
581
+
582
+ # Apply modifications to add validation
583
+ secure = true
584
+
585
+ OrderForm.name_element do
586
+ output(type: :string, presence: true, if: -> { secure })
587
+ end
588
+
589
+ OrderForm.customer_subform default: {} do
590
+ name_element do
591
+ output(type: :string, presence: true, if: -> { secure })
592
+ end
593
+ end
594
+
595
+ OrderForm.items_subforms default: [{}] do
596
+ subform do
597
+ name_element do
598
+ output(type: :string, presence: true, if: -> { secure })
599
+ end
600
+ quantity_element do
601
+ output(type: :integer, presence: true, if: -> { secure })
602
+ end
603
+ price_element do
604
+ output(type: :float, presence: true, if: -> { secure })
605
+ end
606
+ end
607
+ end
608
+
609
+ # Now the form has validation enabled
610
+ params = OrderForm.params_definition.new({})
611
+ expect(params).to be_invalid
612
+ ```
613
+
614
+ #### **Key Benefits**
615
+
616
+ - **Non-Destructive**: Modify form definitions without changing the original class
617
+ - **Conditional Logic**: Apply modifications based on runtime conditions
618
+ - **Inheritance Support**: Works seamlessly with class inheritance
619
+ - **Flexible Extension**: Add validation, change defaults, or add new elements to existing definitions
620
+ - **Reusability**: Create base forms and customize them for specific use cases
621
+
622
+ This feature provides a powerful way to customize and extend form definitions while maintaining the declarative nature of ActionForm.
623
+
253
624
  ### Tagging system
254
625
 
255
626
  ActionForm includes a flexible tagging system that allows you to add custom metadata to form elements and control rendering behavior. Tags serve multiple purposes:
@@ -800,6 +1171,22 @@ class UserForm < ActionForm::Rails::Base
800
1171
  input type: :email
801
1172
  output type: :string, presence: true
802
1173
  end
1174
+
1175
+ element :password do
1176
+ input type: :password
1177
+ output type: :string
1178
+ end
1179
+
1180
+ element :password_confirmation do
1181
+ input type: :password
1182
+ output type: :string
1183
+ end
1184
+
1185
+ # Custom parameter validation for Rails integration
1186
+ params do
1187
+ validates :password, presence: true, length: { minimum: 6 }
1188
+ validates :password, confirmation: true
1189
+ end
803
1190
  end
804
1191
  ```
805
1192
 
@@ -910,6 +1297,7 @@ class UsersController < ApplicationController
910
1297
  @user = User.create!(user_params.user.to_h)
911
1298
  redirect_to @user
912
1299
  else
1300
+ # Custom validation errors are automatically available
913
1301
  @form = @form.with_params(user_params)
914
1302
  render :new
915
1303
  end
@@ -927,6 +1315,7 @@ class UsersController < ApplicationController
927
1315
  @user.update!(user_params.user.to_h)
928
1316
  redirect_to @user
929
1317
  else
1318
+ # Custom validation errors (like password confirmation) are displayed
930
1319
  @form = @form.with_params(user_params)
931
1320
  render :edit
932
1321
  end
@@ -26,6 +26,9 @@ module ActionForm
26
26
  def element(name, &block)
27
27
  elements[name] = Class.new(ActionForm::Element)
28
28
  elements[name].class_eval(&block)
29
+ define_singleton_method(:"#{name}_element") do |klass = nil, &block|
30
+ update_element_definition(name, klass, &block)
31
+ end
29
32
  end
30
33
 
31
34
  def many(name, default: nil, &block)
@@ -34,12 +37,26 @@ module ActionForm
34
37
  subform_definition.class_eval(&block) if block
35
38
  elements[name] = subform_definition
36
39
  elements[name].default = default if default
40
+ define_singleton_method(:"#{name}_subforms") do |klass = nil, default: nil, &block|
41
+ update_element_definition(name, klass, default: default, &block)
42
+ end
37
43
  end
38
44
 
39
45
  def subform(name, default: nil, &block)
40
46
  elements[name] = Class.new(subform_class)
41
47
  elements[name].class_eval(&block)
42
48
  elements[name].default = default if default
49
+ define_singleton_method(:"#{name}_subform") do |klass = nil, default: nil, &block|
50
+ update_element_definition(name, klass, default: default, &block)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def update_element_definition(name, klass = nil, default: nil, &block)
57
+ elements[name] = klass if klass
58
+ elements[name] = Class.new(elements[name], &block) if block
59
+ elements[name].default = default if default
43
60
  end
44
61
  end
45
62
  end
@@ -11,6 +11,13 @@ module ActionForm
11
11
 
12
12
  class << self
13
13
  attr_accessor :default
14
+ attr_writer :elements
15
+
16
+ def inherited(subclass)
17
+ super
18
+ subclass.elements = elements.dup
19
+ subclass.default = default.dup
20
+ end
14
21
  end
15
22
 
16
23
  attr_reader :elements_instances, :tags, :name, :object
@@ -12,13 +12,19 @@ module ActionForm
12
12
  attr_accessor :helpers
13
13
 
14
14
  class << self
15
- attr_reader :subform_definition
16
- attr_accessor :default, :host_class
15
+ attr_accessor :default, :host_class, :subform_definition
17
16
 
18
17
  def subform(subform_class = nil, &block)
19
- @subform_definition = subform_class || Class.new(host_class.subform_class)
18
+ @subform_definition ||= subform_class || Class.new(host_class.subform_class)
20
19
  @subform_definition.class_eval(&block) if block
21
20
  end
21
+
22
+ def inherited(subclass)
23
+ super
24
+ subclass.subform_definition = subform_definition
25
+ subclass.default = default
26
+ subclass.host_class = host_class
27
+ end
22
28
  end
23
29
 
24
30
  def initialize(name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionForm
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_form
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrii Baran